diff --git a/3B2/3b2_cpu.c b/3B2/3b2_cpu.c index 516e78cb..65e7bab4 100644 --- a/3B2/3b2_cpu.c +++ b/3B2/3b2_cpu.c @@ -1,4878 +1,4878 @@ -/* 3b2_cpu.c: WE32100 and WE32200 CPU - - Copyright (c) 2017-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -/* - * This is an implementation of the WE 32100 and WE 32200 CPUs, used - * in the Rev 2 (e.g. 3B2/400) and Rev 3 (e.g. 3B2/700) AT&T 3B2 - * computers, respectively. - * - * The WE 32K series of microprocessors were fully 32-bit, general - * purpose CISC architecture microprocessors with features designed to - * support UNIX as a primary operating system. In addition to the CPU, - * other members of the WE 32K chipset included: - * - * - WE 32101 and WE 32201 Memory Management Units - * - WE 32106 and WE 32206 Math Accelerator Units - * - * The architecture is fully described in the following books: - * - * - "WE 32100 Microprocessor Information Manual" (AT&T, 1985) - * - "WE 32200 Microprocessor Information Manual" (AT&T, 1988) - * - */ - -#include "3b2_cpu.h" - -#if defined(REV3) -#include "3b2_if.h" -#else -#include "3b2_id.h" -#endif /* defined(REV3) */ - -#include "3b2_csr.h" -#include "3b2_dmac.h" -#include "3b2_io.h" -#include "3b2_iu.h" -#include "3b2_mau.h" -#include "3b2_mem.h" -#include "3b2_mmu.h" -#include "3b2_stddev.h" -#include "3b2_timer.h" - -/* Up to 128KB ROM allowed */ -#define MAX_SUB_RETURN_SKIP 9 - -/* Kernel-privileged registers on write */ -#if defined(REV3) -#define PRIVREG(V) ((V) == NUM_PSW || (V) == NUM_PCBP || \ - (V) == NUM_ISP || (V) == NUM_PC || (V) >= 24) -#else -#define PRIVREG(V) ((V) == NUM_PSW || (V) == NUM_PCBP || \ - (V) == NUM_ISP || (V) == NUM_PC) -#endif - -/* Static function declarations */ -static uint32 cpu_effective_address(operand * op); -static uint32 cpu_read_op(operand * op); -static void cpu_write_op(operand * op, t_uint64 val); -static void cpu_set_nz_flags(t_uint64 data, operand * op); -static SIM_INLINE void cpu_on_normal_exception(uint8 isc); -static SIM_INLINE void cpu_on_stack_exception(uint8 isc); -static SIM_INLINE void cpu_on_process_exception(uint8 isc); -static SIM_INLINE void cpu_on_reset_exception(uint8 isc); -static SIM_INLINE void cpu_perform_gate(uint32 index1, uint32 index2); -static SIM_INLINE void clear_instruction(instr *inst); -static SIM_INLINE int8 op_type(operand *op); -static SIM_INLINE t_bool op_signed(operand *op); -static SIM_INLINE uint32 sign_extend_b(uint8 val); -static SIM_INLINE uint32 sign_extend_h(uint16 val); -static SIM_INLINE t_bool cpu_z_flag(); -static SIM_INLINE t_bool cpu_n_flag(); -static SIM_INLINE t_bool cpu_c_flag(); -static SIM_INLINE t_bool cpu_v_flag(); -static SIM_INLINE void cpu_set_z_flag(t_bool val); -static SIM_INLINE void cpu_set_n_flag(t_bool val); -static SIM_INLINE void cpu_set_c_flag(t_bool val); -static SIM_INLINE void cpu_set_v_flag(t_bool val); -static SIM_INLINE void cpu_set_v_flag_op(t_uint64 val, operand *op); -static SIM_INLINE uint8 cpu_execution_level(); -static SIM_INLINE void cpu_push_word(uint32 val); -static SIM_INLINE uint32 cpu_pop_word(); -static SIM_INLINE void irq_push_word(uint32 val); -static SIM_INLINE uint32 irq_pop_word(); -static SIM_INLINE void cpu_context_switch_1(uint32 pcbp); -static SIM_INLINE void cpu_context_switch_2(uint32 pcbp); -static SIM_INLINE void cpu_context_switch_3(uint32 pcbp); -static SIM_INLINE t_bool op_is_psw(operand *op); -static SIM_INLINE void add(t_uint64 a, t_uint64 b, operand *dst); -static SIM_INLINE void sub(t_uint64 a, t_uint64 b, operand *dst); -#if defined(REV3) -static SIM_INLINE uint8 add_bcd(uint8 a, uint8 b); -static SIM_INLINE uint8 sub_bcd(uint8 a, uint8 b); -#endif - -/* RO memory. */ -uint8 *ROM = NULL; - -/* Main memory. */ -uint8 *RAM = NULL; - -/* Save environment for setjmp/longjmp */ -jmp_buf save_env; -volatile uint32 abort_context; - -/* Pointer to the last decoded instruction */ -instr *cpu_instr; - -/* The instruction to use if there is no history storage */ -instr inst; - -/* Circular buffer of instructions */ -instr *INST = NULL; -uint32 cpu_hist_size = 0; -uint32 cpu_hist_p = 0; - -t_bool cpu_in_wait = FALSE; - -volatile size_t cpu_exception_stack_depth = 0; -volatile int32 stop_reason; -volatile uint32 abort_reason; - -/* Register data */ -uint32 R[NUM_REGISTERS]; - -/* Other global CPU state */ - -t_bool rom_loaded = FALSE; /* True if ROM has been loaded, false otherwise */ - -/* Interrupt request bitfield */ -/* Note: Only the lowest 8 bits are used by Rev 2, and only the lowest - 12 bits are used by Rev 3 */ -uint16 sbd_int_req = 0; /* Currently set interrupt sources */ -uint8 int_map[INT_MAP_LEN]; /* Map of interrupt sources to highest - priority IPL */ -t_bool cpu_nmi = FALSE; /* If set, there has been an NMI */ -int32 pc_incr = 0; /* Length (in bytes) of instruction - currently being executed */ -t_bool cpu_ex_halt = FALSE; /* Flag to halt on exceptions / traps */ -t_bool cpu_km = FALSE; /* If true, kernel mode has been forced - for memory access */ -uint16 cpu_int_ack; /* The most recently acknowledged - interrupt */ - -CTAB sys_cmd[] = { - { "BOOT", &sys_boot, RU_BOOT, - "bo{ot} boot simulator\n", NULL, &run_cmd_message }, - { NULL } -}; - -BITFIELD psw_bits[] = { - BITFFMT(ET,2,%d), /* Exception Type */ - BIT(TM), /* Trace Mask */ - BITFFMT(ISC,4,%d), /* Internal State Code */ - BIT(I), /* Register Initial Context (I) */ - BIT(R), /* Register Initial Context (R) */ - BITFFMT(PM,2,%d), /* Previous Execution Level */ - BITFFMT(CM,2,%d), /* Current Execution Level */ - BITFFMT(IPL,4,%d), /* Interrupt Priority Level */ - BIT(TE), /* Trace Enable */ - BIT(C), /* Carry */ - BIT(V), /* Overflow */ - BIT(Z), /* Zero */ - BIT(N), /* Negative */ - BIT(OE), /* Enable Overflow Trap */ - BIT(CD), /* Cache Disable */ - BIT(QIE), /* Quick-Interrupt Enable */ - BIT(CFD), /* Cache Flush Disable */ -#if defined(REV3) - BIT(X), /* Extend Carry / Borrow */ - BIT(AR), /* Additional Register Save */ - BIT(EXUC), /* Exception/User Call Option */ - BIT(EA), /* Enable Arbitrary Alignment */ - BITNCF(2), /* Unused */ -#else - BITNCF(6), /* Unused */ -#endif - ENDBITS -}; - -BITFIELD sbd_int_req_bits[] = { -#if defined(REV3) - BIT(CLOK), /* UNIX Interval Timer */ - BIT(PWRD), /* Power Down Request */ - BIT(BUSO), /* UBUS or BUB Operational Interrupt */ - BIT(SBER), /* Single Bit Memory Error */ - BIT(MBER), /* Multiple Bit Memory Error */ - BIT(BRXF), /* UBUS, BUB, EIO Bus Received Fail */ - BIT(BTMO), /* UBUS Timer Timeout */ - BIT(UDMA), /* UART DMA Complete */ - BIT(UART), /* UART Interrupt */ - BIT(FDMA), /* Floppy DMA Complete */ - BIT(FLOP), /* Floppy Interrupt */ - BIT(PIR9), /* PIR 9 */ - BIT(PIR8), /* PIR 8 */ - BITNCF(3), /* Unused */ - ENDBITS -#else - BIT(SERR), /* System Error */ - BIT(CLOK), /* UNIX Interval Timer */ - BIT(DMAC), /* DMA Complete */ - BIT(UART), /* UART */ - BIT(DISK), /* Integrated Disk Drive (Winchester) */ - BIT(FLOP), /* Integrated Floppy Drive */ - BIT(PIR9), /* PIR 9 */ - BIT(PIR8), /* PIR 8 */ - BITNCF(8), /* Unused */ - ENDBITS -#endif -}; - -/* Registers. */ -REG cpu_reg[] = { - { HRDATAD (R0, R[0], 32, "General purpose register 0") }, - { HRDATAD (R1, R[1], 32, "General purpose register 1") }, - { HRDATAD (R2, R[2], 32, "General purpose register 2") }, - { HRDATAD (R3, R[3], 32, "General purpose register 3") }, - { HRDATAD (R4, R[4], 32, "General purpose register 4") }, - { HRDATAD (R5, R[5], 32, "General purpose register 5") }, - { HRDATAD (R6, R[6], 32, "General purpose register 6") }, - { HRDATAD (R7, R[7], 32, "General purpose register 7") }, - { HRDATAD (R8, R[8], 32, "General purpose register 8") }, - { HRDATAD (FP, R[NUM_FP], 32, "Frame Pointer") }, - { HRDATAD (AP, R[NUM_AP], 32, "Argument Pointer") }, - { HRDATADF (PSW, R[NUM_PSW], 32, "Processor Status Word", psw_bits) }, - { HRDATAD (SP, R[NUM_SP], 32, "Stack Pointer") }, - { HRDATAD (PCBP, R[NUM_PCBP], 32, "Process Control Block Pointer") }, - { HRDATAD (ISP, R[NUM_ISP], 32, "Interrupt Stack Pointer") }, - { HRDATAD (PC, R[NUM_PC], 32, "Program Counter") }, -#if defined(REV3) - { HRDATAD (R16, R[16], 32, "General purpose register 16")}, - { HRDATAD (R17, R[17], 32, "General purpose register 17")}, - { HRDATAD (R18, R[18], 32, "General purpose register 18")}, - { HRDATAD (R19, R[19], 32, "General purpose register 19")}, - { HRDATAD (R20, R[20], 32, "General purpose register 20")}, - { HRDATAD (R21, R[21], 32, "General purpose register 21")}, - { HRDATAD (R22, R[22], 32, "General purpose register 22")}, - { HRDATAD (R23, R[23], 32, "General purpose register 23")}, - { HRDATAD (R24, R[24], 32, "Privileged register 24")}, - { HRDATAD (R25, R[25], 32, "Privileged register 25")}, - { HRDATAD (R26, R[26], 32, "Privileged register 26")}, - { HRDATAD (R27, R[27], 32, "Privileged register 27")}, - { HRDATAD (R28, R[28], 32, "Privileged register 28")}, - { HRDATAD (R29, R[29], 32, "Privileged register 29")}, - { HRDATAD (R30, R[30], 32, "Privileged register 30")}, - { HRDATAD (R31, R[31], 32, "Privileged register 31")}, -#endif - { HRDATADF (SBD_INT, sbd_int_req, 16, "Interrupt Requests", sbd_int_req_bits) }, - { NULL } -}; - -static DEBTAB cpu_deb_tab[] = { - { "READ", READ_MSG, "Memory read activity" }, - { "WRITE", WRITE_MSG, "Memory write activity" }, - { "DECODE", DECODE_MSG, "Instruction decode" }, - { "EXECUTE", EXECUTE_MSG, "Instruction execute" }, - { "INIT", INIT_MSG, "Initialization" }, - { "IRQ", IRQ_MSG, "Interrupt Handling" }, - { "IO", IO_DBG, "I/O Dispatch" }, - { "CIO", CIO_DBG, "Common I/O Interface" }, - { "TRACE", TRACE_DBG, "Call Trace" }, - { "ERROR", ERR_MSG, "Error" }, - { NULL, 0, NULL } -}; - -UNIT cpu_unit = { - UDATA (NULL, UNIT_FIX|UNIT_BINK|UNIT_IDLE, DEFMEMSIZE) -}; - -/* - * The following commands deposit a small calibration program into - * mainstore at 0x2000000 and then set the program counter to the - * start address. Simulator calibration will execute this program to - * establish a baseline execution rate. - * - * Program: - * 84 01 46 MOVW &0x1,%r6 - * 84 46 47 MOVW %r6,%r7 - * 84 47 48 MOVW %r7,%r8 - * 90 48 INCW %r8 - * 28 48 TSTW %r8 - * 4f 0b BLEB 0xb - * e4 07 48 40 MODW3 &0x7,%r8,%r0 - * 84 40 47 MOVW %r0,%r7 - * 7b 0b BRB 0xb - * 8c 48 40 MNEGW %r8,%r0 - * a4 07 40 MODW2 &0x7,%r0 - * 84 40 47 MOVW %r0,%r7 - * e8 47 48 40 MULW3 %r7,%r8,%r0 - * 9c 07 40 ADDW2 &0x7,%r0 - * 84 40 46 MOVW %r0,%r6 - * 28 48 TSTW %r8 - * 4f 05 BLEB 0x5 - * a8 03 47 MULW2 &0x3,%r7 - * d0 01 46 46 LLSW3 &0x1,%r6,%r6 - * 28 46 TSTW %r6 - * 4f 09 BLEB 0x9 - * ec 46 47 40 DIVW3 %r6,%r7,%r0 - * 84 40 48 MOVW %r0,%r8 - * d4 01 47 47 LRSW3 &0x1,%r7,%r7 - * 3c 48 47 CMPW %r8,%r7 - * 4f 05 BLEB 0x5 - * bc 48 47 SUBW2 %r8,%r7 - * 7b bc BRB -0x44 - */ -static const char *att3b2_clock_precalibrate_commands[] = { - "-v 2000000 84014684", - "-v 2000004 46478447", - "-v 2000008 48904828", - "-v 200000c 484f0be4", - "-v 2000010 07484084", - "-v 2000014 40477b0b", - "-v 2000018 8c4840a4", - "-v 200001c 07408440", - "-v 2000020 47e84748", - "-v 2000024 409c0740", - "-v 2000028 84404628", - "-v 200002c 484f05a8", - "-v 2000030 0347d001", - "-v 2000034 46462846", - "-v 2000038 4f09ec46", - "-v 200003c 47408440", - "-v 2000040 48d40147", - "-v 2000044 473c4847", - "-v 2000048 4f05bc48", - "-v 200004c 477bbc00", - "PC 2000000", - NULL -}; - -MTAB cpu_mod[] = { -#if defined(REV2) - { UNIT_MSIZE, (1u << 20), NULL, "1M", - &cpu_set_size, NULL, NULL, "Set Memory to 1M bytes" }, - { UNIT_MSIZE, (1u << 21), NULL, "2M", - &cpu_set_size, NULL, NULL, "Set Memory to 2M bytes" }, - { UNIT_MSIZE, (1u << 22), NULL, "4M", - &cpu_set_size, NULL, NULL, "Set Memory to 4M bytes" }, -#endif -#if defined(REV3) - { UNIT_MSIZE, (1u << 23), NULL, "8M", - &cpu_set_size, NULL, NULL, "Set Memory to 8M bytes" }, - { UNIT_MSIZE, (1u << 24), NULL, "16M", - &cpu_set_size, NULL, NULL, "Set Memory to 16M bytes" }, - { UNIT_MSIZE, (1u << 25), NULL, "32M", - &cpu_set_size, NULL, NULL, "Set Memory to 32M bytes" }, - { UNIT_MSIZE, (1u << 26), NULL, "64M", - &cpu_set_size, NULL, NULL, "Set Memory to 64M bytes" }, -#endif - { MTAB_XTD|MTAB_VDV|MTAB_NMO|MTAB_SHP, 0, "HISTORY", "HISTORY", - &cpu_set_hist, &cpu_show_hist, NULL, "Displays instruction history" }, - { MTAB_XTD|MTAB_VDV|MTAB_NMO|MTAB_SHP, 0, "VIRTUAL", NULL, - NULL, &cpu_show_virt, NULL, "Show translation for virtual address" }, - { MTAB_XTD|MTAB_VDV|MTAB_NMO|MTAB_SHP, 0, "STACK", NULL, - NULL, &cpu_show_stack, NULL, "Display the current stack with optional depth" }, - { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "CIO", NULL, - NULL, &cpu_show_cio, NULL, "Display backplane configuration" }, - { MTAB_XTD|MTAB_VDV, 0, "IDLE", "IDLE", &sim_set_idle, &sim_show_idle }, - { MTAB_XTD|MTAB_VDV, 0, NULL, "NOIDLE", &sim_clr_idle, NULL }, - { UNIT_EXBRK, UNIT_EXBRK, "Break on exceptions", "EXBRK", - NULL, NULL, NULL, "Enable break on exceptions and traps" }, - { UNIT_EXBRK, 0, "No break on exceptions", "NOEXBRK", - NULL, NULL, NULL, "Disable break on exceptions and traps" }, - { UNIT_OPBRK, UNIT_OPBRK, "Break on invalid opcodes", "OPBRK", - NULL, NULL, NULL, "Enable break on invalid opcodes" }, - { UNIT_OPBRK, 0, "No break on invalid opcodes", "NOOPBRK", - NULL, NULL, NULL, "Disable break on invalid opcodes" }, - { 0 } -}; - -DEVICE cpu_dev = { - "CPU", /* Name */ - &cpu_unit, /* Units */ - cpu_reg, /* Registers */ - cpu_mod, /* Modifiers */ - 1, /* Number of Units */ - 16, /* Address radix */ - 32, /* Address width */ - 1, /* Addr increment */ - 16, /* Data radix */ - 8, /* Data width */ - &cpu_ex, /* Examine routine */ - &cpu_dep, /* Deposit routine */ - &cpu_reset, /* Reset routine */ - &cpu_boot, /* Boot routine */ - NULL, /* Attach routine */ - NULL, /* Detach routine */ - NULL, /* Context */ - DEV_DYNM|DEV_DEBUG, /* Flags */ - 0, /* Debug control flags */ - cpu_deb_tab, /* Debug flag names */ - &cpu_set_size, /* Memory size change */ - NULL, /* Logical names */ - &cpu_help, /* Help routine */ - NULL, /* Attach Help Routine */ - NULL, /* Help Context */ - &cpu_description /* Device Description */ -}; - -mnemonic hword_ops[HWORD_OP_COUNT] = { - {0x3009, 0, OP_NONE, NA, "MVERNO", -1, -1, -1, -1}, - {0x300d, 0, OP_NONE, NA, "ENBVJMP", -1, -1, -1, -1}, - {0x3013, 0, OP_NONE, NA, "DISVJMP", -1, -1, -1, -1}, - {0x3019, 0, OP_NONE, NA, "MOVBLW", -1, -1, -1, -1}, - {0x301f, 0, OP_NONE, NA, "STREND", -1, -1, -1, -1}, - {0x302f, 1, OP_DESC, WD, "INTACK", -1, -1, -1, -1}, - {0x3035, 0, OP_NONE, NA, "STRCPY", -1, -1, -1, -1}, - {0x3045, 0, OP_NONE, NA, "RETG", -1, -1, -1, -1}, - {0x3061, 0, OP_NONE, NA, "GATE", -1, -1, -1, -1}, - {0x30ac, 0, OP_NONE, NA, "CALLPS", -1, -1, -1, -1}, -#if defined(REV3) - {0x30c0, 0, OP_NONE, NA, "UCALLPS", -1, -1, -1, -1}, -#endif - {0x30c8, 0, OP_NONE, NA, "RETPS", -1, -1, -1, -1} -}; - -/* Lookup table of operand types. */ -mnemonic ops[256] = { - {0x00, 0, OP_NONE, NA, "halt", -1, -1, -1, -1}, - {0x01, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x02, 2, OP_COPR, WD, "SPOPRD", 1, -1, -1, -1}, - {0x03, 3, OP_COPR, WD, "SPOPD2", 1, -1, -1, 2}, - {0x04, 2, OP_DESC, WD, "MOVAW", 0, -1, -1, 1}, - {0x05, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x06, 2, OP_COPR, WD, "SPOPRT", 1, -1, -1, -1}, - {0x07, 3, OP_COPR, WD, "SPOPT2", 1, -1, -1, 2}, - {0x08, 0, OP_NONE, NA, "RET", -1, -1, -1, -1}, -#if defined(REV3) - {0x09, 3, OP_DESC, WD, "CASWI", 0, 1, -1, 2}, - {0x0a, 0, OP_NONE, NA, "SETX", -1, -1, -1, -1}, - {0x0b, 0, OP_NONE, NA, "CLRX", -1, -1, -1, -1}, -#else - {0x09, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x0a, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x0b, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, -#endif - {0x0c, 2, OP_DESC, WD, "MOVTRW", 0, -1, -1, 1}, -#if defined(REV3) - {0x0d, 2, OP_DESH, HW, "TEDTH", 1, -1, -1, 0}, - {0x0e, 2, OP_DESC, HW, "PACKB", 0, -1, -1, 1}, - {0x0f, 3, OP_DESC, HW, "UNPACKB", 0, 1, -1, 2}, -#else - {0x0d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x0e, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x0f, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, -#endif - {0x10, 1, OP_DESC, WD, "SAVE", 0, -1, -1, -1}, - {0x11, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x12, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x13, 2, OP_COPR, WD, "SPOPWD", -1, -1, -1, 1}, - {0x14, 1, OP_BYTE, NA, "EXTOP", -1, -1, -1, -1}, - {0x15, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x16, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x17, 2, OP_COPR, WD, "SPOPWT", -1, -1, -1, 1}, - {0x18, 1, OP_DESC, WD, "RESTORE", 0, -1, -1, -1}, -#if defined(REV3) - {0x19, 2, OP_DESH, HW, "DTH", 1, -1, -1, 0}, -#else - {0x19, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, -#endif - {0x1a, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x1b, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x1c, 1, OP_DESC, WD, "SWAPWI", -1, -1, -1, 0}, -#if defined(REV3) - {0x1d, 2, OP_DESH, HW, "TGEDTH", 1, -1, -1, 0}, -#else - {0x1d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, -#endif - {0x1e, 1, OP_DESC, HW, "SWAPHI", -1, -1, -1, 0}, - {0x1f, 1, OP_DESC, BT, "SWAPBI", -1, -1, -1, 0}, - {0x20, 1, OP_DESC, WD, "POPW", -1, -1, -1, 0}, - {0x21, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x22, 2, OP_COPR, WD, "SPOPRS", 1, -1, -1, -1}, - {0x23, 3, OP_COPR, WD, "SPOPS2", 1, -1, -1, 2}, - {0x24, 1, OP_DESC, NA, "JMP", -1, -1, -1, 0}, - {0x25, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x26, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x27, 0, OP_NONE, NA, "CFLUSH", -1, -1, -1, -1}, - {0x28, 1, OP_DESC, WD, "TSTW", 0, -1, -1, -1}, -#if defined(REV3) - {0x29, 2, OP_DESB, BT, "DTB", 1, -1, -1, 0}, -#else - {0x29, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, -#endif - {0x2a, 1, OP_DESC, HW, "TSTH", 0, -1, -1, -1}, - {0x2b, 1, OP_DESC, BT, "TSTB", 0, -1, -1, -1}, - {0x2c, 2, OP_DESC, WD, "CALL", 0, -1, -1, 1}, -#if defined(REV3) - {0x2d, 2, OP_DESH, HW, "TGDTH", 1, -1, -1, 0}, -#else - {0x2d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, -#endif - {0x2e, 0, OP_NONE, NA, "BPT", -1, -1, -1, -1}, /* TODO: Verify */ - {0x2f, 0, OP_NONE, NA, "WAIT", -1, -1, -1, -1}, - {0x30, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, /* Two-byte instructions */ - {0x31, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x32, 1, OP_COPR, WD, "SPOP", -1, -1, -1, -1}, - {0x33, 2, OP_COPR, WD, "SPOPWS", -1, -1, -1, 1}, - {0x34, 1, OP_DESC, WD, "JSB", -1, -1, -1, 0}, - {0x35, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x36, 1, OP_HALF, NA, "BSBH", -1, -1, -1, 0}, - {0x37, 1, OP_BYTE, NA, "BSBB", -1, -1, -1, 0}, - {0x38, 2, OP_DESC, WD, "BITW", 0, 1, -1, -1}, - {0x39, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x3a, 2, OP_DESC, HW, "BITH", 0, 1, -1, -1}, - {0x3b, 2, OP_DESC, BT, "BITB", 0, 1, -1, -1}, - {0x3c, 2, OP_DESC, WD, "CMPW", 0, 1, -1, -1}, -#if defined(REV3) - {0x3d, 2, OP_DESH, HW, "TNEDTH", 1, -1, -1, 0}, -#else - {0x3d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, -#endif - {0x3e, 2, OP_DESC, HW, "CMPH", 0, 1, -1, -1}, - {0x3f, 2, OP_DESC, BT, "CMPB", 0, 1, -1, -1}, - {0x40, 0, OP_NONE, NA, "RGEQ", -1, -1, -1, -1}, - {0x41, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x42, 1, OP_HALF, NA, "BGEH", -1, -1, -1, 0}, - {0x43, 1, OP_BYTE, NA, "BGEB", -1, -1, -1, 0}, - {0x44, 0, OP_NONE, NA, "RGTR", -1, -1, -1, -1}, - {0x45, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x46, 1, OP_HALF, NA, "BGH", -1, -1, -1, 0}, - {0x47, 1, OP_BYTE, NA, "BGB", -1, -1, -1, 0}, - {0x48, 0, OP_NONE, NA, "RLSS", -1, -1, -1, 0}, - {0x49, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x4a, 1, OP_HALF, NA, "BLH", -1, -1, -1, 0}, - {0x4b, 1, OP_BYTE, NA, "BLB", -1, -1, -1, 0}, - {0x4c, 0, OP_NONE, NA, "RLEQ", -1, -1, -1, -1}, -#if defined(REV3) - {0x4d, 2, OP_DESB, BT, "TEDTB", 1, -1, -1, 0}, -#else - {0x4d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, -#endif - {0x4e, 1, OP_HALF, NA, "BLEH", -1, -1, -1, 0}, - {0x4f, 1, OP_BYTE, NA, "BLEB", -1, -1, -1, 0}, - {0x50, 0, OP_NONE, NA, "RGEQU", -1, -1, -1, 0}, /* aka RCC */ - {0x51, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x52, 1, OP_HALF, NA, "BGEUH", -1, -1, -1, 0}, /* aka BCCH */ - {0x53, 1, OP_BYTE, NA, "BGEUB", -1, -1, -1, 0}, /* aka BCCB */ - {0x54, 0, OP_NONE, NA, "RGTRU", -1, -1, -1, -1}, /* TODO: Implement */ - {0x55, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x56, 1, OP_HALF, NA, "BGUH", -1, -1, -1, 0}, - {0x57, 1, OP_BYTE, NA, "BGUB", -1, -1, -1, 0}, - {0x58, 0, OP_NONE, NA, "RLSSU", -1, -1, -1, 0}, /* aka RCS */ /* TODO: Implement */ - {0x59, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x5a, 1, OP_HALF, NA, "BLUH", -1, -1, -1, 0}, /* aka BCSH */ - {0x5b, 1, OP_BYTE, NA, "BLUB", -1, -1, -1, 0}, /* aka BCSB */ - {0x5c, 0, OP_NONE, NA, "RLEQU", -1, -1, -1, -1}, -#if defined(REV3) - {0x5d, 2, OP_DESB, HW, "TGEDTB", 1, -1, -1, 0}, -#else - {0x5d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, -#endif - {0x5e, 1, OP_HALF, NA, "BLEUH", -1, -1, -1, 0}, - {0x5f, 1, OP_BYTE, NA, "BLEUB", -1, -1, -1, 0}, - {0x60, 0, OP_NONE, NA, "RVC", -1, -1, -1, -1}, /* TODO: Implement */ - {0x61, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x62, 1, OP_HALF, NA, "BVCH", -1, -1, -1, 0}, - {0x63, 1, OP_BYTE, NA, "BVCB", -1, -1, -1, 0}, - {0x64, 0, OP_NONE, NA, "RNEQU", -1, -1, -1, -1}, - {0x65, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x66, 1, OP_HALF, NA, "BNEH", -1, -1, -1, 0}, /* duplicate of 76 */ - {0x67, 1, OP_BYTE, NA, "BNEB", -1, -1, -1, 0}, /* duplicate of 77 */ - {0x68, 0, OP_NONE, NA, "RVS", -1, -1, -1, -1}, /* TODO: Implement */ - {0x69, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x6a, 1, OP_HALF, NA, "BVSH", -1, -1, -1, 0}, - {0x6b, 1, OP_BYTE, NA, "BVSB", -1, -1, -1, 0}, - {0x6c, 0, OP_NONE, NA, "REQLU", -1, -1, -1, -1}, -#if defined(REV3) - {0x6d, 2, OP_DESB, BT, "TGDTB", 1, -1, -1, 0}, -#else - {0x6d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, -#endif - {0x6e, 1, OP_HALF, NA, "BEH", -1, -1, -1, 0}, /* duplicate of 7e */ - {0x6f, 1, OP_BYTE, NA, "BEB", -1, -1, -1, 0}, /* duplicate of 7f */ - {0x70, 0, OP_NONE, NA, "NOP", -1, -1, -1, -1}, - {0x71, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x72, 0, OP_NONE, NA, "NOP3", -1, -1, -1, -1}, - {0x73, 0, OP_NONE, NA, "NOP2", -1, -1, -1, -1}, - {0x74, 0, OP_NONE, NA, "RNEQ", -1, -1, -1, -1}, - {0x75, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x76, 1, OP_HALF, NA, "BNEH", -1, -1, -1, 0}, /* duplicate of 66 */ - {0x77, 1, OP_BYTE, NA, "BNEB", -1, -1, -1, 0}, /* duplicate of 67 */ - {0x78, 0, OP_NONE, NA, "RSB", -1, -1, -1, -1}, - {0x79, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x7a, 1, OP_HALF, NA, "BRH", -1, -1, -1, 0}, - {0x7b, 1, OP_BYTE, NA, "BRB", -1, -1, -1, 0}, - {0x7c, 0, OP_NONE, NA, "REQL", -1, -1, -1, -1}, -#if defined(REV3) - {0x7d, 2, OP_DESB, BT, "TNEDTB", 1, -1, -1, 0}, -#else - {0x7d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, -#endif - {0x7e, 1, OP_HALF, NA, "BEH", -1, -1, -1, 0}, /* duplicate of 6e */ - {0x7f, 1, OP_BYTE, NA, "BEB", -1, -1, -1, 0}, /* duplicate of 6f */ - {0x80, 1, OP_DESC, WD, "CLRW", -1, -1, -1, 0}, - {0x81, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x82, 1, OP_DESC, HW, "CLRH", -1, -1, -1, 0}, - {0x83, 1, OP_DESC, BT, "CLRB", -1, -1, -1, 0}, - {0x84, 2, OP_DESC, WD, "MOVW", 0, -1, -1, 1}, - {0x85, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x86, 2, OP_DESC, HW, "MOVH", 0, -1, -1, 1}, - {0x87, 2, OP_DESC, BT, "MOVB", 0, -1, -1, 1}, - {0x88, 2, OP_DESC, WD, "MCOMW", 0, -1, -1, 1}, - {0x89, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x8a, 2, OP_DESC, HW, "MCOMH", 0, -1, -1, 1}, - {0x8b, 2, OP_DESC, BT, "MCOMB", 0, -1, -1, 1}, - {0x8c, 2, OP_DESC, WD, "MNEGW", 0, -1, -1, 1}, - {0x8d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x8e, 2, OP_DESC, HW, "MNEGH", 0, -1, -1, 1}, - {0x8f, 2, OP_DESC, BT, "MNEGB", 0, -1, -1, 1}, - {0x90, 1, OP_DESC, WD, "INCW", -1, -1, -1, 0}, - {0x91, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x92, 1, OP_DESC, HW, "INCH", -1, -1, -1, 0}, - {0x93, 1, OP_DESC, BT, "INCB", -1, -1, -1, 0}, - {0x94, 1, OP_DESC, WD, "DECW", -1, -1, -1, 0}, - {0x95, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x96, 1, OP_DESC, HW, "DECH", -1, -1, -1, 0}, - {0x97, 1, OP_DESC, BT, "DECB", -1, -1, -1, 0}, -#if defined(REV3) - {0x98, 0, OP_NONE, NA, "RETQINT",-1, -1, -1, -1}, /* TODO: Implement */ -#else - {0x98, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, -#endif - {0x99, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x9a, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, -#if defined(REV3) - {0x9b, 2, OP_DESC, BT, "SUBPB2", 0, -1, -1, 1}, -#else - {0x9b, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, -#endif - {0x9c, 2, OP_DESC, WD, "ADDW2", 0, -1, -1, 1}, - {0x9d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x9e, 2, OP_DESC, HW, "ADDH2", 0, -1, -1, 1}, - {0x9f, 2, OP_DESC, BT, "ADDB2", 0, -1, -1, 1}, - {0xa0, 1, OP_DESC, WD, "PUSHW", 0, -1, -1, -1}, - {0xa1, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xa2, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, -#if defined(REV3) - {0xa3, 2, OP_DESC, BT, "ADDPB2", 0, -1, -1, 1}, -#else - {0xa3, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, -#endif - {0xa4, 2, OP_DESC, WD, "MODW2", 0, -1, -1, 1}, - {0xa5, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xa6, 2, OP_DESC, HW, "MODH2", 0, -1, -1, 1}, - {0xa7, 2, OP_DESC, BT, "MODB2", 0, -1, -1, 1}, - {0xa8, 2, OP_DESC, WD, "MULW2", 0, -1, -1, 1}, - {0xa9, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xaa, 2, OP_DESC, HW, "MULH2", 0, -1, -1, 1}, - {0xab, 2, OP_DESC, BT, "MULB2", 0, -1, -1, 1}, - {0xac, 2, OP_DESC, WD, "DIVW2", 0, -1, -1, 1}, - {0xad, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xae, 2, OP_DESC, HW, "DIVH2", 0, -1, -1, 1}, - {0xaf, 2, OP_DESC, BT, "DIVB2", 0, -1, -1, 1}, - {0xb0, 2, OP_DESC, WD, "ORW2", 0, -1, -1, 1}, - {0xb1, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xb2, 2, OP_DESC, HW, "ORH2", 0, -1, -1, 1}, - {0xb3, 2, OP_DESC, BT, "ORB2", 0, -1, -1, 1}, - {0xb4, 2, OP_DESC, WD, "XORW2", 0, -1, -1, 1}, - {0xb5, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xb6, 2, OP_DESC, HW, "XORH2", 0, -1, -1, 1}, - {0xb7, 2, OP_DESC, BT, "XORB2", 0, -1, -1, 1}, - {0xb8, 2, OP_DESC, WD, "ANDW2", 0, -1, -1, 1}, - {0xb9, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xba, 2, OP_DESC, HW, "ANDH2", 0, -1, -1, 1}, - {0xbb, 2, OP_DESC, BT, "ANDB2", 0, -1, -1, 1}, - {0xbc, 2, OP_DESC, WD, "SUBW2", 0, -1, -1, 1}, - {0xbd, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xbe, 2, OP_DESC, HW, "SUBH2", 0, -1, -1, 1}, - {0xbf, 2, OP_DESC, BT, "SUBB2", 0, -1, -1, 1}, - {0xc0, 3, OP_DESC, WD, "ALSW3", 0, 1, -1, 2}, - {0xc1, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xc2, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xc3, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xc4, 3, OP_DESC, WD, "ARSW3", 0, 1, -1, 2}, - {0xc5, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xc6, 3, OP_DESC, HW, "ARSH3", 0, 1, -1, 2}, - {0xc7, 3, OP_DESC, BT, "ARSB3", 0, 1, -1, 2}, - {0xc8, 4, OP_DESC, WD, "INSFW", 0, 1, 2, 3}, - {0xc9, -1, OP_DESC, NA, "???", -1, -1, -1, -1}, - {0xca, 4, OP_DESC, HW, "INSFH", 0, 1, 2, 3}, - {0xcb, 4, OP_DESC, BT, "INSFB", 0, 1, 2, 3}, - {0xcc, 4, OP_DESC, WD, "EXTFW", 0, 1, 2, 3}, - {0xcd, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xce, 4, OP_DESC, HW, "EXTFH", 0, 1, 2, 3}, - {0xcf, 4, OP_DESC, BT, "EXTFB", 0, 1, 2, 3}, - {0xd0, 3, OP_DESC, WD, "LLSW3", 0, 1, -1, 2}, - {0xd1, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xd2, 3, OP_DESC, HW, "LLSH3", 0, 1, -1, 2}, - {0xd3, 3, OP_DESC, BT, "LLSB3", 0, 1, -1, 2}, - {0xd4, 3, OP_DESC, WD, "LRSW3", 0, 1, -1, 2}, - {0xd5, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xd6, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xd7, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xd8, 3, OP_DESC, WD, "ROTW", 0, 1, -1, 2}, - {0xd9, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xda, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, -#if defined(REV3) - {0xdb, 3, OP_DESC, BT, "SUBPB3", 0, 1, -1, 2}, -#else - {0xdb, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, -#endif - {0xdc, 3, OP_DESC, WD, "ADDW3", 0, 1, -1, 2}, - {0xdd, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xde, 3, OP_DESC, HW, "ADDH3", 0, 1, -1, 2}, - {0xdf, 3, OP_DESC, BT, "ADDB3", 0, 1, -1, 2}, - {0xe0, 1, OP_DESC, WD, "PUSHAW", 0, -1, -1, -1}, - {0xe1, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xe2, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, -#if defined(REV3) - {0xe3, 3, OP_DESC, BT, "ADDPB3", 0, 1, -1, 2}, -#else - {0xe3, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, -#endif - {0xe4, 3, OP_DESC, WD, "MODW3", 0, 1, -1, 2}, - {0xe5, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xe6, 3, OP_DESC, HW, "MODH3", 0, 1, -1, 2}, - {0xe7, 3, OP_DESC, BT, "MODB3", 0, 1, -1, 2}, - {0xe8, 3, OP_DESC, WD, "MULW3", 0, 1, -1, 2}, - {0xe9, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xea, 3, OP_DESC, HW, "MULH3", 0, 1, -1, 2}, - {0xeb, 3, OP_DESC, BT, "MULB3", 0, 1, -1, 2}, - {0xec, 3, OP_DESC, WD, "DIVW3", 0, 1, -1, 2}, - {0xed, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xee, 3, OP_DESC, HW, "DIVH3", 0, 1, -1, 2}, - {0xef, 3, OP_DESC, BT, "DIVB3", 0, 1, -1, 2}, - {0xf0, 3, OP_DESC, WD, "ORW3", 0, 1, -1, 2}, - {0xf1, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xf2, 3, OP_DESC, HW, "ORH3", 0, 1, -1, 2}, - {0xf3, 3, OP_DESC, BT, "ORB3", 0, 1, -1, 2}, - {0xf4, 3, OP_DESC, WD, "XORW3", 0, 1, -1, 2}, - {0xf5, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xf6, 3, OP_DESC, HW, "XORH3", 0, 1, -1, 2}, - {0xf7, 3, OP_DESC, BT, "XORB3", 0, 1, -1, 2}, - {0xf8, 3, OP_DESC, WD, "ANDW3", 0, 1, -1, 2}, - {0xf9, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xfa, 3, OP_DESC, HW, "ANDH3", 0, 1, -1, 2}, - {0xfb, 3, OP_DESC, BT, "ANDB3", 0, 1, -1, 2}, - {0xfc, 3, OP_DESC, WD, "SUBW3", 0, 1, -1, 2}, - {0xfd, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xfe, 3, OP_DESC, HW, "SUBH3", 0, 1, -1, 2}, - {0xff, 3, OP_DESC, BT, "SUBB3", 0, 1, -1, 2} -}; - -/* from MAME (src/devices/cpu/m68000/m68kcpu.c) */ -const uint8 shift_8_table[65] = -{ - 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 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, 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 -}; -const uint16 shift_16_table[65] = -{ - 0x0000, 0x8000, 0xc000, 0xe000, 0xf000, 0xf800, 0xfc00, 0xfe00, 0xff00, - 0xff80, 0xffc0, 0xffe0, 0xfff0, 0xfff8, 0xfffc, 0xfffe, 0xffff, 0xffff, - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, - 0xffff, 0xffff -}; -const uint32 shift_32_table[65] = -{ - 0x00000000, 0x80000000, 0xc0000000, 0xe0000000, 0xf0000000, 0xf8000000, - 0xfc000000, 0xfe000000, 0xff000000, 0xff800000, 0xffc00000, 0xffe00000, - 0xfff00000, 0xfff80000, 0xfffc0000, 0xfffe0000, 0xffff0000, 0xffff8000, - 0xffffc000, 0xffffe000, 0xfffff000, 0xfffff800, 0xfffffc00, 0xfffffe00, - 0xffffff00, 0xffffff80, 0xffffffc0, 0xffffffe0, 0xfffffff0, 0xfffffff8, - 0xfffffffc, 0xfffffffe, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, - 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, - 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, - 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, - 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, - 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff -}; - -t_stat cpu_show_stack(FILE *st, UNIT *uptr, int32 val, CONST void *desc) -{ - uint32 i, j; - uint32 addr, v, count; - uint8 tmp; - char *cptr = (char *) desc; - t_stat result; - - if (cptr) { - count = (size_t) get_uint(cptr, 10, 128, &result); - if ((result != SCPE_OK) || (count == 0)) { - return SCPE_ARG; - } - } else { - count = 8; - } - - for (i = 0; i < (count * 4); i += 4) { - v = 0; - addr = R[NUM_SP] - i; - - for (j = 0; j < 4; j++) { - result = examine(addr + j, &tmp); - if (result != SCPE_OK) { - return result; - } - v |= (uint32) tmp << ((3 - j) * 8); - } - - fprintf(st, " %08x: %08x\n", addr, v); - } - - return SCPE_OK; -} - -t_stat cpu_show_cio(FILE *st, UNIT *uptr, int32 val, CONST void *desc) -{ - uint32 slot; - - fprintf(st, " SLOT DEVICE\n"); - fprintf(st, "---------------------\n"); - for (slot = 0; slot < CIO_SLOTS; slot++) { - if (cio[slot].populated) { - fprintf(st, " %2d %s\n", slot, cio[slot].name); - } else { - fprintf(st, " %2d -\n", slot); - } - } - - return SCPE_OK; -} - -#if defined(REV3) -t_stat sys_boot(int32 flag, CONST char *ptr) -{ - char gbuf[CBUFSIZE]; - - if ((ptr = get_sim_sw(ptr)) == NULL) { - return SCPE_INVSW; - } - - get_glyph(ptr, gbuf, 0); - if (gbuf[0] && strcmp(gbuf, "CPU")) { - return SCPE_ARG; - } - - return run_cmd(flag, "CPU"); -} -#else -t_stat sys_boot(int32 flag, CONST char *ptr) -{ - char gbuf[CBUFSIZE]; - size_t len = ROM_SIZE; - t_stat r; - - if ((ptr = get_sim_sw(ptr)) == NULL) { - return SCPE_INVSW; - } - - do { - ptr = get_glyph(ptr, gbuf, 0); - - if (gbuf[0] && (strcmp(gbuf, "CPU"))) { - return SCPE_ARG; - } - } while (gbuf[0]); - - return run_cmd(flag, "CPU"); -} -#endif /* Rev 2 boot */ - -t_stat cpu_boot(int32 unit_num, DEVICE *dptr) -{ - /* - * page 2-52 (pdf page 85) - * - * 1. Change to physical address mode - * 2. Fetch the word at physical address 0x80 and store it in - * the PCBP register. - * 3. Fetch the word at the PCB address and store it in the - * PSW. - * 4. Fetch the word at PCB address + 4 bytes and store it - * in the PC. - * 5. Fetch the word at PCB address + 8 bytes and store it - * in the SP. - * 6. Fetch the word at PCB address + 12 bytes and store it - * in the PCB, if bit I in PSW is set. - */ - - if (!rom_loaded) { - sim_messagef(SCPE_NXM, "Cannot boot, ROM not loaded.\n"); - return SCPE_STOP; - } - - sim_debug(EXECUTE_MSG, &cpu_dev, - "CPU Boot/Reset Initiated. PC=%08x SP=%08x\n", - R[NUM_PC], R[NUM_SP]); - - mmu_disable(); - - R[NUM_PCBP] = pread_w(0x80, BUS_CPU); - R[NUM_PSW] = pread_w(R[NUM_PCBP], BUS_CPU); - R[NUM_PC] = pread_w(R[NUM_PCBP] + 4, BUS_CPU); - R[NUM_SP] = pread_w(R[NUM_PCBP] + 8, BUS_CPU); - - if (R[NUM_PSW] & PSW_I_MASK) { - R[NUM_PSW] &= ~PSW_I_MASK; - R[NUM_PCBP] += 12; - } - - /* set ISC to External Reset */ - R[NUM_PSW] &= ~PSW_ISC_MASK; - R[NUM_PSW] |= 3 << PSW_ISC ; - - return SCPE_OK; -} - -t_stat cpu_ex(t_value *vptr, t_addr addr, UNIT *uptr, int32 sw) -{ - uint32 uaddr = (uint32) addr; - uint8 value; - t_stat succ; - - if (vptr == NULL) { - return SCPE_ARG; - } - - if (sw & EX_V_FLAG) { - succ = examine(uaddr, &value); - *vptr = value; - return succ; - } else { - if (IS_ROM(uaddr) || IS_RAM(uaddr)) { - *vptr = (uint32) pread_b(uaddr, BUS_CPU); - return SCPE_OK; - } else { - *vptr = 0; - return SCPE_NXM; - } - } -} - -t_stat cpu_dep(t_value val, t_addr addr, UNIT *uptr, int32 sw) -{ - uint32 uaddr = (uint32) addr; - - if (sw & EX_V_FLAG) { - return deposit(uaddr, (uint8) val); - } else { - if (IS_RAM(uaddr)) { - pwrite_b(uaddr, (uint8) val, BUS_CPU); - return SCPE_OK; - } else { - return SCPE_NXM; - } - } -} - -/* - * Pre-populate the interrupt->IPL map "int_map" - */ -static void build_int_map() -{ - int i; - uint8 ipl; - - for (i = 0; i < INT_MAP_LEN; i++) { -#if defined(REV3) - if (i & (INT_PWRDWN|INT_BUS_OP|INT_SBERR| - INT_MBERR|INT_BUS_RXF|INT_BUS_TMO| - INT_CLOCK)) { - ipl = CPU_IPL_15; - } else if (i & (INT_UART|INT_UART_DMA)) { - ipl = CPU_IPL_13; - } else if (i & (INT_FLOPPY|INT_FLOPPY_DMA)) { - ipl = CPU_IPL_11; - } else if (i & INT_PIR9) { - ipl = CPU_IPL_9; - } else if (i & INT_PIR8) { - ipl = CPU_IPL_8; - } else { - ipl = 0; - } -#else - if (i & (INT_CLOCK|INT_SERR)) { - ipl = CPU_IPL_15; - } else if (i & (INT_UART|INT_DMA)) { - ipl = CPU_IPL_13; - } else if (i & (INT_DISK|INT_FLOPPY)) { - ipl = CPU_IPL_11; - } else if (i & INT_PIR9) { - ipl = CPU_IPL_9; - } else if (i & INT_PIR8) { - ipl = CPU_IPL_8; - } else { - ipl = 0; - } -#endif - - int_map[i] = ipl; - } - - sim_debug(EXECUTE_MSG, &cpu_dev, - "Built interrupt->IPL map of length %d\n", INT_MAP_LEN); -} - -t_stat cpu_reset(DEVICE *dptr) -{ - int i; - t_stat r; - - sim_debug(EXECUTE_MSG, &cpu_dev, "CPU Reset.\n"); - - if (!sim_is_running) { - /* Populate the interrupt->IPL map */ - build_int_map(); - - /* Clear registers */ - for (i = 0; i < NUM_REGISTERS; i++) { - R[i] = 0; - } - - /* Allocate ROM if needed */ - if (ROM == NULL) { - ROM = (uint8 *) calloc((size_t)ROM_SIZE, sizeof(uint8)); - } - if (ROM == NULL) { - return SCPE_MEM; - } - - /* Allocate RAM if needed */ - if (RAM == NULL) { - RAM = (uint8 *) calloc((size_t)MEM_SIZE, sizeof(uint8)); - } - if (RAM == NULL) { - return SCPE_MEM; - } - - sim_vm_is_subroutine_call = cpu_is_pc_a_subroutine_call; - - /* Link in our special "boot" command so we can boot with both - * "BO{OT}" and "BO{OT} CPU" */ - sim_vm_cmd = sys_cmd; - - /* Set up the pre-calibration routine */ - sim_clock_precalibrate_commands = att3b2_clock_precalibrate_commands; - - abort_context = C_NONE; - - cpu_in_wait = FALSE; - } - - sim_brk_types = SWMASK('E'); - sim_brk_dflt = SWMASK('E'); - - return SCPE_OK; -} - -static const char *cpu_next_caveats = -"The NEXT command in this 3B2 architecture simulator currently will\n" -"enable stepping across subroutine calls which are initiated by the\n" -"JSB, CALL and CALLPS instructions.\n" -"This stepping works by dynamically establishing breakpoints at the\n" -"memory address immediately following the instruction which initiated\n" -"the subroutine call. These dynamic breakpoints are automatically\n" -"removed once the simulator returns to the sim> prompt for any reason.\n" -"If the called routine returns somewhere other than one of these\n" -"locations due to a trap, stack unwind or any other reason, instruction\n" -"execution will continue until some other reason causes execution to stop.\n"; - -t_bool cpu_is_pc_a_subroutine_call (t_addr **ret_addrs) -{ - static t_addr returns[MAX_SUB_RETURN_SKIP+1] = {0}; - static t_bool caveats_displayed = FALSE; - int i; - - if (!caveats_displayed) { - caveats_displayed = TRUE; - sim_printf ("%s", cpu_next_caveats); - } - - /* get data */ - if (SCPE_OK != get_aval (R[NUM_PC], &cpu_dev, &cpu_unit)) { - return FALSE; - } - - switch (sim_eval[0]) { - case JSB: - case CALL: - case CALLPS: -#if defined(REV3) - case UCALLPS: -#endif - returns[0] = R[NUM_PC] + (unsigned int) (1 - fprint_sym(stdnul, R[NUM_PC], - sim_eval, &cpu_unit, - SWMASK ('M'))); - for (i=1; imnemonic); - - for (i = 0; i < mn->op_count; i++) { - - /* Special cases for non-descriptor opcodes */ - if ((mn->mode == OP_BYTE) || (mn->mode == OP_DESB && i > 0)) { - mode = 6; - reg = 15; - } else if (mn->mode == OP_HALF || (mn->mode == OP_DESH && i > 0)) { - mode = 5; - reg = 15; - } else if (mn->mode == OP_COPR) { - mode = 4; - reg = 15; - } else { - desc = (uint8) val[vp++]; - -#if defined(REV3) /* WE 32200 only */ - switch (desc) { - case 0x5b: - /* Get next byte */ - desc = (uint8) val[vp++]; - /* - * Mode 0x10: Auto pre-decrement -(%rx) - * Mode 0x12: Auto post-decrement (%rx)- - * Mode 0x14: Auto pre-increment +(%rx) - * Mode 0x16: Auto post-increment (%rx)- - */ - mode = ((desc >> 5) & 0x7) | 0x10; - reg = desc & 0x1f; - break; - case 0xab: - case 0xbb: - mode = 0xab; - desc = (uint8) val[vp++]; - reg = (desc >> 4) & 0xf; - reg2 = (desc & 0xf) + 16; - break; - case 0xcb: - /* Get next byte */ - desc = (uint8) val[vp++]; - mode = (desc >> 4) & 0xf; - reg = (desc & 0xf) + 16; - break; - case 0xdb: - mode = 0xdb; - desc = (uint8) val[vp++]; - reg = (desc >> 4) & 0xf; - reg2 = (desc & 0xf) + 16; - break; - default: - mode = (desc >> 4) & 0xf; - reg = desc & 0xf; - break; - } -#else /* WE 32100 only */ - mode = (desc >> 4) & 0xf; - reg = desc & 0xf; -#endif - - /* Find the expanded data type, if any */ - if (mode == 14 && - (reg == 0 || reg == 2 || reg == 3 || - reg == 4 || reg == 6 || reg == 7)) { - etype = reg; - /* The real descriptor byte lies one ahead */ - desc = (uint8) val[vp++]; - mode = (desc >> 4) & 0xf; - reg = desc & 0xf; - } - } - - fputc(i ? ',' : ' ', of); - - switch (etype) { - case 0: - fprintf(of, "{uword}"); - break; - case 2: - fprintf(of, "{uhalf}"); - break; - case 3: - fprintf(of, "{ubyte}"); - break; - case 4: - fprintf(of, "{word}"); - break; - case 6: - fprintf(of, "{half}"); - break; - case 7: - fprintf(of, "{sbyte}"); - break; - default: - /* do nothing */ - break; - } - - switch(mode) { - case 0: /* Positive Literal */ - case 1: /* Positive Literal */ - case 2: /* Positive Literal */ - case 3: /* Positive Literal */ - case 15: /* Negative Literal */ - fprintf(of, "&%d", desc); - break; - case 4: /* Halfword Immediate, Register Mode */ - switch (reg) { - case 15: /* Word Immediate */ - OP_R_W(w, val, vp); - fprintf(of, "&0x%x", w); - break; - default: /* Register Mode */ - cpu_register_name(reg, reg_name, 8); - fprintf(of, "%s", reg_name); - break; - } - break; - case 5: /* Halfword Immediate, Register Deferred */ - switch (reg) { - case 15: - OP_R_H(w, val, vp); - fprintf(of, "&0x%x", w); - break; - default: - cpu_register_name(reg, reg_name, 8); - fprintf(of, "(%s)", reg_name); - break; - } - break; - case 6: /* Byte Immediate, FP Short Offset */ - switch (reg) { - case 15: - OP_R_B(w, val, vp); - fprintf(of, "&0x%x", w); - break; - default: - fprintf(of, "%d(%%fp)", (int8) reg); - break; - } - break; - case 7: /* Absolute, AP Short Offset */ - switch (reg) { - case 15: - OP_R_W(w, val, vp); - fprintf(of, "$0x%x", w); - break; - default: - fprintf(of, "%d(%%ap)", (int8) reg); - break; - } - break; - case 8: /* Word Displacement */ - OP_R_W(w, val, vp); - cpu_register_name(reg, reg_name, 8); - fprintf(of, "0x%x(%s)", w, reg_name); - break; - case 9: /* Word Displacement Deferred */ - OP_R_W(w, val, vp); - cpu_register_name(reg, reg_name, 8); - fprintf(of, "*0x%x(%s)", w, reg_name); - break; - case 10: /* Halfword Displacement */ - OP_R_H(w, val, vp); - cpu_register_name(reg, reg_name, 8); - fprintf(of, "0x%x(%s)", w, reg_name); - break; - case 11: /* Halfword Displacement Deferred */ - OP_R_H(w, val, vp); - cpu_register_name(reg, reg_name, 8); - fprintf(of, "*0x%x(%s)", w, reg_name); - break; - case 12: /* Byte Displacement */ - OP_R_B(w, val, vp); - cpu_register_name(reg, reg_name, 8); - fprintf(of, "%d(%s)", (int8) w, reg_name); - break; - case 13: /* Byte Displacement Deferred */ - OP_R_B(w, val, vp); - cpu_register_name(reg, reg_name, 8); - fprintf(of, "*%d(%s)", (int8) w, reg_name); - break; - case 14: - if (reg == 15) { - OP_R_W(w, val, vp); - fprintf(of, "*$0x%x", w); - } - break; -#if defined(REV3) - case 0x10: /* Auto pre-decrement */ - cpu_register_name(reg, reg_name, 8); - fprintf(of, "-(%s)", reg_name); - break; - case 0x12: /* Auto post-decrement */ - cpu_register_name(reg, reg_name, 8); - fprintf(of, "(%s)-", reg_name); - break; - case 0x14: /* Auto pre-increment */ - cpu_register_name(reg, reg_name, 8); - fprintf(of, "+(%s)", reg_name); - break; - case 0x16: /* Auto post-increment */ - cpu_register_name(reg, reg_name, 8); - fprintf(of, "(%s)+", reg_name); - break; - case 0xab: - OP_R_B(w, val, vp); - cpu_register_name(reg, reg_name, 8); - cpu_register_name(reg2, reg2_name, 8); - fprintf(of, "%d(%s,%s)", (int8) w, reg2_name, reg_name); - break; - case 0xbb: - OP_R_H(w, val, vp); - cpu_register_name(reg, reg_name, 8); - cpu_register_name(reg2, reg2_name, 8); - fprintf(of, "0x%x(%s,%s)", w, reg2_name, reg_name); - break; - case 0xdb: - cpu_register_name(reg, reg_name, 8); - cpu_register_name(reg2, reg2_name, 8); - fprintf(of, "%s[%s]", reg2_name, reg_name); - break; -#endif - default: - fprintf(of, ""); - break; - } - } - - - return -(vp - 1); -} - -void fprint_sym_hist(FILE *st, instr *ip) -{ - int32 i; - - if (ip == NULL || ip->mn == NULL) { - fprintf(st, "???"); - return; - } - - fprintf(st, "%s", ip->mn->mnemonic); - - if (ip->mn->op_count > 0) { - fputc(' ', st); - } - - /* Show the operand mnemonics */ - for (i = 0; i < ip->mn->op_count; i++) { - cpu_show_operand(st, &ip->operands[i]); - if (i < ip->mn->op_count - 1) { - fputc(',', st); - } - } -} - -t_stat cpu_show_virt(FILE *of, UNIT *uptr, int32 val, CONST void *desc) -{ - uint32 va, pa; - t_stat r; - - const char *cptr = (const char *)desc; - if (cptr) { - va = (uint32) get_uint(cptr, 16, 0xffffffff, &r); - if (r == SCPE_OK) { - r = mmu_decode_va(va, 0, FALSE, &pa); - if (r == SCPE_OK) { - fprintf(of, "Virtual %08x = Physical %08x\n", va, pa); - return SCPE_OK; - } else { - fprintf(of, "Translation not possible for virtual address.\n"); - return SCPE_ARG; - } - } else { - fprintf(of, "Illegal address format.\n"); - return SCPE_ARG; - } - } - - fprintf(of, "Address argument required.\n"); - return SCPE_ARG; -} - -t_stat cpu_set_hist(UNIT *uptr, int32 val, CONST char *cptr, void *desc) -{ - uint32 i, size; - t_stat result; - - /* Clear the history buffer if no argument */ - if (cptr == NULL) { - for (i = 0; i < cpu_hist_size; i++) { - INST[i].valid = FALSE; - } - return SCPE_OK; - } - - /* Otherwise, get the new length */ - size = (uint32) get_uint(cptr, 10, MAX_HIST_SIZE, &result); - - /* If no length was provided, give up */ - if (result != SCPE_OK) { - return SCPE_ARG; - } - - /* Legnth 0 is a special flag that means disable the feature. */ - if (size == 0) { - if (INST != NULL) { - for (i = 0; i < cpu_hist_size; i++) { - INST[i].valid = FALSE; - } - } - cpu_hist_size = 0; - cpu_hist_p = 0; - return SCPE_OK; - } - - /* Reinitialize the new history ring bufer */ - cpu_hist_p = 0; - if (size > 0) { - if (INST != NULL) { - free(INST); - } - INST = (instr *)calloc(size, sizeof(instr)); - if (INST == NULL) { - return SCPE_MEM; - } - memset(INST, 0, sizeof(instr) * size); - cpu_hist_size = size; - } - - return SCPE_OK; -} - -t_stat cpu_show_hist(FILE *st, UNIT *uptr, int32 val, CONST void *desc) -{ - uint32 i; - size_t j, count; - char *cptr = (char *) desc; - t_stat result; - instr *ip; - - int32 di; - - if (cpu_hist_size == 0) { - return SCPE_NOFNC; - } - - /* 'count' is the number of history entries the user wants */ - - if (cptr) { - count = (size_t) get_uint(cptr, 10, cpu_hist_size, &result); - if ((result != SCPE_OK) || (count == 0)) { - return SCPE_ARG; - } - } else { - count = cpu_hist_size; - } - - /* Position for reading from ring buffer */ - di = (int32) (cpu_hist_p - count); - - if (di < 0) { - di = di + (int32) cpu_hist_size; - } - - fprintf(st, "PSW SP PC IR\n"); - - for (i = 0; i < count; i++) { - ip = &INST[(di++) % (int32) cpu_hist_size]; - if (ip->valid) { - /* Show the opcode mnemonic */ - fprintf(st, "%08x %08x %08x ", ip->psw, ip->sp, ip->pc); - /* Show the operand data */ - if (ip->mn == NULL || ip->mn->op_count < 0) { - fprintf(st, "???"); - } else { - fprint_sym_hist(st, ip); - if (ip->mn->op_count > 0 && ip->mn->mode == OP_DESC) { - fprintf(st, "\n "); - for (j = 0; j < (uint32) ip->mn->op_count; j++) { - fprintf(st, "%08x", ip->operands[j].data); - if (j < (uint32) ip->mn->op_count - 1) { - fputc(' ', st); - } - } - } - } - fputc('\n', st); - } - } - - - return SCPE_OK; -} - -void cpu_register_name(uint8 reg, char *buf, size_t len) { - switch(reg) { - case 9: - snprintf(buf, len, "%%fp"); - break; - case 10: - snprintf(buf, len, "%%ap"); - break; - case 11: - snprintf(buf, len, "%%psw"); - break; - case 12: - snprintf(buf, len, "%%sp"); - break; - case 13: - snprintf(buf, len, "%%pcbp"); - break; - case 14: - snprintf(buf, len, "%%isp"); - break; - case 15: - snprintf(buf, len, "%%pc"); - break; - default: - snprintf(buf, len, "%%r%d", reg); - break; - } -} - -void cpu_show_operand(FILE *st, operand *op) -{ - char reg_name[8]; -#if defined(REV3) - char reg2_name[8]; -#endif - - if (op->etype != -1) { - switch(op->etype) { - case 0: - fprintf(st, "{uword}"); - break; - case 2: - fprintf(st, "{uhalf}"); - break; - case 3: - fprintf(st, "{ubyte}"); - break; - case 4: - fprintf(st, "{word}"); - break; - case 6: - fprintf(st, "{half}"); - break; - case 7: - fprintf(st, "{sbyte}"); - break; - } - } - - switch(op->mode) { - case 0: - case 1: - case 2: - case 3: - fprintf(st, "&0x%x", op->embedded.b); - break; - case 4: - if (op->reg == 15) { - fprintf(st, "&0x%x", op->embedded.w); - } else { - cpu_register_name(op->reg, reg_name, 8); - fprintf(st, "%s", reg_name); - } - break; - case 5: - if (op->reg == 15) { - fprintf(st, "&0x%x", op->embedded.w); - } else { - cpu_register_name(op->reg, reg_name, 8); - fprintf(st, "(%s)", reg_name); - } - break; - case 6: /* FP Short Offset */ - if (op->reg == 15) { - fprintf(st, "&0x%x", op->embedded.w); - } else { - fprintf(st, "%d(%%fp)", op->reg); - } - break; - case 7: /* AP Short Offset */ - if (op->reg == 15) { - fprintf(st, "$0x%x", op->embedded.w); - } else { - fprintf(st, "%d(%%ap)", op->embedded.w); - } - break; - case 8: - cpu_register_name(op->reg, reg_name, 8); - fprintf(st, "0x%x(%s)", (int32)op->embedded.w, reg_name); - break; - case 9: - cpu_register_name(op->reg, reg_name, 8); - fprintf(st, "*0x%x(%s)", (int32)op->embedded.w, reg_name); - break; - case 10: - cpu_register_name(op->reg, reg_name, 8); - fprintf(st, "0x%x(%s)", (int16)op->embedded.w, reg_name); - break; - case 11: - cpu_register_name(op->reg, reg_name, 8); - fprintf(st, "*0x%x(%s)", (int16)op->embedded.w, reg_name); - break; - case 12: - cpu_register_name(op->reg, reg_name, 8); - fprintf(st, "%d(%s)", (int8)op->embedded.w, reg_name); - break; - case 13: - cpu_register_name(op->reg, reg_name, 8); - fprintf(st, "*%d(%s)", (int8)op->embedded.w, reg_name); - break; - case 14: - if (op->reg == 15) { - fprintf(st, "*$0x%x", op->embedded.w); - } - break; - case 15: - fprintf(st, "&0x%x", (int32)op->embedded.w); - break; -#if defined(REV3) - case 0x10: /* Auto pre-decrement */ - cpu_register_name(op->reg, reg_name, 8); - fprintf(st, "-(%s)", reg_name); - break; - case 0x12: /* Auto post-decrement */ - cpu_register_name(op->reg, reg_name, 8); - fprintf(st, "(%s)-", reg_name); - break; - case 0x14: /* Auto pre-increment */ - cpu_register_name(op->reg, reg_name, 8); - fprintf(st, "+(%s)", reg_name); - break; - case 0x16: /* Auto post-increment */ - cpu_register_name(op->reg, reg_name, 8); - fprintf(st, "(%s)+", reg_name); - break; - case 0xab: - cpu_register_name(op->reg, reg_name, 8); - cpu_register_name(op->reg2, reg2_name, 8); - fprintf(st, "%d(%s,%s)", (int8)op->embedded.b, reg2_name, reg_name); - break; - case 0xbb: - cpu_register_name(op->reg, reg_name, 8); - cpu_register_name(op->reg2, reg2_name, 8); - fprintf(st, "0x%x(%s,%s)", op->embedded.h, reg2_name, reg_name); - break; - case 0xdb: - cpu_register_name(op->reg, reg_name, 8); - cpu_register_name(op->reg2, reg2_name, 8); - fprintf(st, "%s[%s]", reg2_name, reg_name); - break; -#endif - } -} - -t_stat cpu_set_size(UNIT *uptr, int32 val, CONST char *cptr, void *desc) -{ - uint32 uval = (uint32) val; - uint8 *nRAM = NULL; - - if ((val <= 0) || (val > MAXMEMSIZE)) { - return SCPE_ARG; - } - - /* Do (re-)allocation for memory. */ - nRAM = (uint8 *) calloc(uval, sizeof(uint32)); - - if (nRAM == NULL) { - return SCPE_MEM; - } - - free(RAM); - RAM = nRAM; - memset(RAM, 0, (size_t)uval * sizeof(uint32)); - - MEM_SIZE = uval; - - return SCPE_OK; -} - -static SIM_INLINE void clear_instruction(instr *inst) -{ - uint8 i; - - inst->mn = NULL; - inst->psw = 0; - inst->sp = 0; - inst->pc = 0; - - for (i = 0; i < 4; i++) { - inst->operands[i].mode = 0; - inst->operands[i].reg = 0; - inst->operands[i].dtype = -1; - inst->operands[i].etype = -1; - inst->operands[i].embedded.w = 0; - inst->operands[i].data = 0; - } -} - -/* - * Decode a single descriptor-defined operand from the instruction - * stream. Returns the number of bytes consumed during decode. - */ -static uint8 decode_operand(uint32 pa, instr *instr, uint8 op_number, int8 *etype) -{ - uint8 desc; - uint8 offset = 0; - operand *oper = &instr->operands[op_number]; - - /* Read in the descriptor byte */ - desc = read_b(pa + offset++, ACC_IF, BUS_CPU); - -#if defined(REV3) - /* - * Handle addressing modes specific to the WE 32200, not - * implemented in the WE 32100 - */ - switch (desc) { - case 0x5b: /* Auto post / pre-increment */ - desc = read_b(pa + offset++, ACC_IF, BUS_CPU); - /* - * Mode 0x10: Auto pre-decrement -(%rx) - * Mode 0x12: Auto post-decrement (%rx)- - * Mode 0x14: Auto pre-increment +(%rx) - * Mode 0x16: Auto post-increment (%rx)- - */ - oper->mode = ((desc >> 5) & 0x7) | 0x10; - oper->reg = desc & 0x1f; - break; - case 0xab: /* Indexed with byte displacement */ - oper->mode = 0xab; - desc = read_b(pa + offset++, ACC_IF, BUS_CPU); - oper->reg = (desc >> 4) & 0xf; - oper->reg2 = (desc & 0xf) + 16; - break; - case 0xbb: /* Indexed with halfword displacement */ - oper->mode = desc; - desc = read_b(pa + offset++, ACC_IF, BUS_CPU); - oper->reg = (desc >> 4) & 0xf; - oper->reg2 = (desc & 0xf) + 16; - break; - case 0xcb: /* Extended format 1: r16 - r31 */ - desc = read_b(pa + offset++, ACC_IF, BUS_CPU); - oper->mode = (desc >> 4) & 0xf; - oper->reg = (desc & 0xf) + 16; - break; - case 0xdb: /* Indexed with scaling */ - oper->mode = desc; - desc = read_b(pa + offset++, ACC_IF, BUS_CPU); - oper->reg = (desc >> 4) & 0xf; - oper->reg2 = (desc & 0xf) + 16; - break; - default: - oper->mode = (desc >> 4) & 0xf; - oper->reg = desc & 0xf; - break; - } -#else - /* - * WE 32100 only - */ - oper->mode = (desc >> 4) & 0xf; - oper->reg = desc & 0xf; -#endif - - oper->dtype = instr->mn->dtype; - oper->etype = *etype; - - switch (oper->mode) { - case 0: /* Positive Literal */ - case 1: /* Positive Literal */ - case 2: /* Positive Literal */ - case 3: /* Positive Literal */ - case 15: /* Negative literal */ - oper->embedded.b = desc; - oper->data = oper->embedded.b; - break; - case 4: /* Word Immediate, Register Mode */ - switch (oper->reg) { - case 15: /* Word Immediate */ - oper->embedded.w = (uint32) read_b(pa + offset++, ACC_IF, BUS_CPU); - oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8u; - oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 16u; - oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 24u; - oper->data = oper->embedded.w; - break; - default: /* Register mode */ - oper->data = R[oper->reg]; - break; - } - break; - case 5: /* Halfword Immediate, Register Deferred Mode */ - switch (oper->reg) { - case 15: /* Halfword Immediate */ - oper->embedded.h = (uint16) read_b(pa + offset++, ACC_IF, BUS_CPU); - oper->embedded.h |= ((uint16) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8u; - oper->data = oper->embedded.h; - break; - case 11: /* INVALID */ - cpu_abort(NORMAL_EXCEPTION, INVALID_DESCRIPTOR); - return offset; - default: /* Register deferred mode */ - oper->data = R[oper->reg]; - break; - } - break; - case 6: /* Byte Immediate, FP Short Offset */ - switch (oper->reg) { - case 15: /* Byte Immediate */ - oper->embedded.b = read_b(pa + offset++, ACC_IF, BUS_CPU); - oper->data = oper->embedded.b; - break; - default: /* FP Short Offset */ - oper->embedded.b = oper->reg; - oper->data = oper->embedded.b; - break; - } - break; - case 7: /* Absolute, AP Short Offset */ - switch (oper->reg) { - case 15: /* Absolute */ - oper->embedded.w = (uint32) read_b(pa + offset++, ACC_IF, BUS_CPU); - oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8u; - oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 16u; - oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 24u; - oper->data = oper->embedded.w; - break; - default: /* AP Short Offset */ - oper->embedded.b = oper->reg; - oper->data = oper->embedded.b; - break; - } - break; - case 8: /* Word Displacement */ - case 9: /* Word Displacement Deferred */ - oper->embedded.w = (uint32) read_b(pa + offset++, ACC_IF, BUS_CPU); - oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8u; - oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 16u; - oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 24u; - oper->data = oper->embedded.w; - break; - case 10: /* Halfword Displacement */ - case 11: /* Halfword Displacement Deferred */ - oper->embedded.h = read_b(pa + offset++, ACC_IF, BUS_CPU); - oper->embedded.h |= ((uint16) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8u; - oper->data = oper->embedded.h; - break; - case 12: /* Byte Displacement */ - case 13: /* Byte Displacement Deferred */ - oper->embedded.b = read_b(pa + offset++, ACC_IF, BUS_CPU); - oper->data = oper->embedded.b; - break; - case 14: /* Absolute Deferred, Extended-Operand */ - switch (oper->reg) { - case 15: /* Absolute Deferred */ - oper->embedded.w = (uint32) read_b(pa + offset++, ACC_IF, BUS_CPU); - oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8u; - oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 16u; - oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 24u; - break; - case 0: - case 2: - case 3: - case 4: - case 6: - case 7: /* Expanded Datatype */ - /* Recursively decode the remainder of the operand after - storing the expanded datatype */ - *etype = (int8) oper->reg; - oper->etype = *etype; - offset += decode_operand(pa + offset, instr, op_number, etype); - break; - default: - cpu_abort(NORMAL_EXCEPTION, RESERVED_DATATYPE); - break; - } - break; -#if defined(REV3) - case 0x10: /* Auto pre-decrement */ - case 0x12: /* Auto post-decrement */ - case 0x14: /* Auto pre-increment */ - case 0x16: /* Auto post-increment */ - oper->data = R[oper->reg]; - break; - case 0xab: /* Indexed with byte displacement */ - oper->embedded.b = read_b(pa + offset++, ACC_IF, BUS_CPU); - oper->data = oper->embedded.b; - break; - case 0xbb: /* Indexed with halfword displacement */ - oper->embedded.h = read_b(pa + offset++, ACC_IF, BUS_CPU); - oper->embedded.h |= ((uint16) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8u; - oper->data = oper->embedded.h; - break; - case 0xdb: /* Indexed with scaling */ - switch (op_type(oper)) { - case BT: - case SB: - oper->data = R[oper->reg]; - break; - case HW: - case UH: - oper->data = R[oper->reg] * 2; - break; - case WD: - case UW: - oper->data = R[oper->reg] * 4; - break; - } - - oper->data += R[oper->reg2]; - - break; -#endif - default: - cpu_abort(NORMAL_EXCEPTION, INVALID_DESCRIPTOR); - } - - return offset; -} - -/* - * Decode the instruction currently being pointed at by the PC. - * This routine does the following: - * 1. Read the opcode. - * 2. Determine the number of operands to decode based on - * the opcode type. - * 3. Fetch each opcode from main memory. - * - * This routine is guaranteed not to change state. - * - * returns: a Normal Exception if an error occured, or 0 on success. - */ -uint8 decode_instruction(instr *instr) -{ - uint8 offset = 0; - uint8 b1, b2; - uint16 hword_op; - uint32 pa; - mnemonic *mn = NULL; - int i; - int8 etype = -1; /* Expanded datatype (if any) */ - - clear_instruction(instr); - - pa = R[NUM_PC]; - - /* Store off the PC and and PSW for history keeping */ - instr->psw = R[NUM_PSW]; - instr->sp = R[NUM_SP]; - instr->pc = pa; - - if (read_operand(pa + offset++, &b1) != SCPE_OK) { - /* We tried to read out of a page that doesn't exist. We - need to let the operating system handle it.*/ - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); - return offset; - } - - /* It should never, ever happen that operand fetch - would cause a page fault. */ - - if (b1 == 0x30) { - read_operand(pa + offset++, &b2); - hword_op = (uint16) ((uint16)b1 << 8) | (uint16) b2; - for (i = 0; i < HWORD_OP_COUNT; i++) { - if (hword_ops[i].opcode == hword_op) { - mn = &hword_ops[i]; - break; - } - } - } else { - mn = &ops[b1]; - } - - if (mn == NULL) { - cpu_abort(NORMAL_EXCEPTION, ILLEGAL_OPCODE); - return offset; - } - - instr->mn = mn; - - if (mn->op_count < 0) { - cpu_abort(NORMAL_EXCEPTION, ILLEGAL_OPCODE); - return offset; - } - - if (mn->op_count == 0) { - /* Nothing else to do, we're done decoding. */ - return offset; - } - - switch (mn->mode) { - case OP_BYTE: - instr->operands[0].embedded.b = read_b(pa + offset++, ACC_IF, BUS_CPU); - instr->operands[0].mode = 6; - instr->operands[0].reg = 15; - break; - case OP_HALF: - instr->operands[0].embedded.h = read_b(pa + offset++, ACC_IF, BUS_CPU); - instr->operands[0].embedded.h |= (uint16)(read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8; - instr->operands[0].mode = 5; - instr->operands[0].reg = 15; - break; - case OP_COPR: - instr->operands[0].embedded.w = (uint32) read_b(pa + offset++, ACC_IF, BUS_CPU); - instr->operands[0].embedded.w |= (uint32) read_b(pa + offset++, ACC_IF, BUS_CPU) << 8; - instr->operands[0].embedded.w |= (uint32) read_b(pa + offset++, ACC_IF, BUS_CPU) << 16; - instr->operands[0].embedded.w |= (uint32) read_b(pa + offset++, ACC_IF, BUS_CPU) << 24; - instr->operands[0].mode = 4; - instr->operands[0].reg = 15; - - /* Decode subsequent operands */ - for (i = 1; i < mn->op_count; i++) { - offset += decode_operand(pa + offset, instr, (uint8) i, &etype); - } - - break; - case OP_DESC: - for (i = 0; i < mn->op_count; i++) { - offset += decode_operand(pa + offset, instr, (uint8) i, &etype); - } - break; -#if defined(REV3) - case OP_DESB: - /* Operand 0 is a descriptor byte */ - offset += decode_operand(pa + offset, instr, 0, &etype); - /* Operand 1 is a signed byte offset */ - instr->operands[1].embedded.b = read_b(pa + offset++, ACC_IF, BUS_CPU); - break; - case OP_DESH: - /* Operand 0 is a descriptor byte */ - offset += decode_operand(pa + offset, instr, 0, &etype); - /* Operand 1 is a signed byte offset */ - instr->operands[1].embedded.h = read_b(pa + offset++, ACC_IF, BUS_CPU); - instr->operands[1].embedded.h |= (uint16)(read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8; - break; -#endif - default: - break; - } - - return offset; -} - -static SIM_INLINE void cpu_context_switch_3(uint32 new_pcbp) -{ - if (R[NUM_PSW] & PSW_R_MASK) { - - R[0] = R[NUM_PCBP] + 64; - R[2] = read_w(R[0], ACC_AF, BUS_CPU); - R[0] += 4; - - while (R[2] != 0) { - R[1] = read_w(R[0], ACC_AF, BUS_CPU); - R[0] += 4; - - /* Execute MOVBLW instruction inside this loop */ - while (R[2] != 0) { - write_w(R[1], read_w(R[0], ACC_AF, BUS_CPU), BUS_CPU); - R[2]--; - R[0] += 4; - R[1] += 4; - } - - R[2] = read_w(R[0], ACC_AF, BUS_CPU); - R[0] += 4; - } - - R[0] = R[0] + 4; - } -} - -static SIM_INLINE void cpu_context_switch_2(uint32 new_pcbp) -{ - R[NUM_PCBP] = new_pcbp; - - /* Put new PSW, PC and SP values from PCB into registers */ - R[NUM_PSW] = read_w(R[NUM_PCBP], ACC_AF, BUS_CPU); - R[NUM_PSW] &= ~PSW_TM_MASK; /* Clear TM */ - R[NUM_PC] = read_w(R[NUM_PCBP] + 4, ACC_AF, BUS_CPU); - R[NUM_SP] = read_w(R[NUM_PCBP] + 8, ACC_AF, BUS_CPU); - - /* If i-bit is set, increment PCBP past initial context area */ - if (R[NUM_PSW] & PSW_I_MASK) { - R[NUM_PSW] &= ~PSW_I_MASK; - R[NUM_PCBP] += 12; - } -} - -static SIM_INLINE void cpu_context_switch_1(uint32 new_pcbp) -{ - /* Save the current PC in PCB */ - write_w(R[NUM_PCBP] + 4, R[NUM_PC], BUS_CPU); - - /* Copy the 'R' flag from the new PSW to the old PSW */ - R[NUM_PSW] &= ~PSW_R_MASK; - R[NUM_PSW] |= (read_w(new_pcbp, ACC_AF, BUS_CPU) & PSW_R_MASK); - - /* Save current PSW and SP in PCB */ - write_w(R[NUM_PCBP], R[NUM_PSW], BUS_CPU); - write_w(R[NUM_PCBP] + 8, R[NUM_SP], BUS_CPU); - - /* If R is set, save current R0-R8/FP/AP in PCB */ - if (R[NUM_PSW] & PSW_R_MASK) { - write_w(R[NUM_PCBP] + 24, R[NUM_FP], BUS_CPU); - write_w(R[NUM_PCBP] + 28, R[0], BUS_CPU); - write_w(R[NUM_PCBP] + 32, R[1], BUS_CPU); - write_w(R[NUM_PCBP] + 36, R[2], BUS_CPU); - write_w(R[NUM_PCBP] + 40, R[3], BUS_CPU); - write_w(R[NUM_PCBP] + 44, R[4], BUS_CPU); - write_w(R[NUM_PCBP] + 48, R[5], BUS_CPU); - write_w(R[NUM_PCBP] + 52, R[6], BUS_CPU); - write_w(R[NUM_PCBP] + 56, R[7], BUS_CPU); - write_w(R[NUM_PCBP] + 60, R[8], BUS_CPU); - write_w(R[NUM_PCBP] + 20, R[NUM_AP], BUS_CPU); - - R[NUM_FP] = R[NUM_PCBP] + 52; - } -} - -void cpu_on_interrupt(uint16 vec) -{ - uint32 new_psw_ptr, new_psw, new_pc; /* Quick-Interrupt */ - uint32 new_pcbp, new_pcbp_ptr; /* Full-Interrupt */ - t_bool quick; - - cpu_int_ack = vec; - - quick = (R[NUM_PSW] & PSW_QIE_MASK) != 0; - - sim_debug(IRQ_MSG, &cpu_dev, - "[%08x] [cpu_on_interrupt] vec=%02x (%d), quick=%d, sbd_int_req = %x, csr_data = %x\n", - R[NUM_PC], vec, vec, quick, sbd_int_req, csr_data); - - /* - * "If a nonmaskable interrupt request is received, an auto-vector - * interrupt acknowledge cycle is performed (as if an autovector - * interrupt at level 0 was being acknowledged) and no - * Interrupt-ID is fetched. The value 0 is used as the ID." - */ - if (cpu_nmi) { - vec = 0; - cpu_nmi = FALSE; - } - - cpu_km = TRUE; - - if (quick) { - /* - * Quick interrupt microsequence - */ - new_psw_ptr = (uint32)0x48c + (8 * (uint32)vec); - - /* Set ISC, TM, and ET to 2, 0, 0 before saving */ - R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); - R[NUM_PSW] |= (2 << PSW_ISC); - - abort_context = C_RESET_INT_STACK; - - write_w(R[NUM_ISP], R[NUM_PC], BUS_CPU); - write_w(R[NUM_ISP] + 4, R[NUM_PSW], BUS_CPU); - - /* Set ISC, TM, and ET to 1, 0, 0 */ - R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); - R[NUM_PSW] |= (2 << PSW_ISC); - - abort_context = C_RESET_SYSTEM_DATA; - - new_psw = read_w(new_psw_ptr, ACC_AF, BUS_CPU); - - /* Clear out PSW state */ - R[NUM_PSW] &= ~(QIE_PSW_MASK); - - /* Set PM to CM and clear out old CM */ - R[NUM_PSW] |= (R[NUM_PSW] & PSW_CM_MASK) >> 2; - R[NUM_PSW] &= ~(PSW_CM_MASK); - - /* Copy from new PSW */ - R[NUM_PSW] |= (new_psw & QIE_PSW_MASK); - - /* Grab the new PC */ - new_pc = read_w(new_psw_ptr + 4, ACC_AF, BUS_CPU); - - /* Finish ISP stack push */ - R[NUM_ISP] = R[NUM_ISP] + 8; - - /* Set new PSW ISC/TM/ET to 7/0/3 */ - R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); - R[NUM_PSW] |= 7 << PSW_ISC; - R[NUM_PSW] |= 3 << PSW_ET; - - /* Set new PC */ - R[NUM_PC] = new_pc; - - /* Done */ - abort_context = C_NONE; - } else { - /* - * Full interrupt microsequence - */ - new_pcbp_ptr = (uint32)0x8c + (4 * (uint32)vec); - - abort_context = C_RESET_SYSTEM_DATA; - - new_pcbp = read_w(new_pcbp_ptr, ACC_AF, BUS_CPU); - - abort_context = C_RESET_INT_STACK; - - /* Save the old PCBP */ - irq_push_word(R[NUM_PCBP]); - - /* Set ISC, TM, and ET to 0, 0, 1 before saving */ - R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); - R[NUM_PSW] |= (1 << PSW_ET); - - /* Context switch */ - cpu_context_switch_1(new_pcbp); - cpu_context_switch_2(new_pcbp); - - /* Set ISC, TM, and ET to 7, 0, 3 in new PSW */ - R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); - R[NUM_PSW] |= (7 << PSW_ISC); - R[NUM_PSW] |= (3 << PSW_ET); - - cpu_context_switch_3(new_pcbp); - - abort_context = C_NONE; - } - - cpu_km = FALSE; -} - -t_stat sim_instr(void) -{ - uint8 et, isc, trap; - - /* Temporary register used for overflow detection */ - t_uint64 result; - - /* Scratch space */ - uint32 a, b, c, d; - - /* Used for field calculation */ - uint32 width, offset; - uint32 mask; - - /* Generic index */ - uint32 i; - - /* Interrupt request IPL */ - uint8 ipl; - - /* Used by oprocessor instructions */ - uint32 coprocessor_word; - - operand *src1, *src2, *src3, *dst; - - stop_reason = 0; - - abort_reason = (uint32) setjmp(save_env); - - /* Exception handler. - * - * This gets a little messy because of exception contexts. If a - * normal-exception happens while we're handling a - * normal-exception, it needs to be treated as a stack-exception. - */ - if (abort_reason != 0) { - if (cpu_exception_stack_depth++ >= 10) { - return STOP_ESTK; - } - - if (cpu_unit.flags & UNIT_EXBRK) { - return STOP_EX; - } - - et = R[NUM_PSW] & PSW_ET_MASK; - isc = (R[NUM_PSW] & PSW_ISC_MASK) >> PSW_ISC; - - if (abort_reason == ABORT_EXC) { - switch(abort_context) { - case C_NORMAL_GATE_VECTOR: - cpu_on_normal_exception(N_GATE_VECTOR); - break; - case C_PROCESS_GATE_PCB: - cpu_on_process_exception(GATE_PCB_FAULT); - break; - case C_PROCESS_OLD_PCB: - cpu_on_process_exception(OLD_PCB_FAULT); - break; - case C_PROCESS_NEW_PCB: - cpu_on_process_exception(NEW_PCB_FAULT); - break; - case C_STACK_FAULT: - cpu_on_stack_exception(STACK_FAULT); - break; - case C_RESET_GATE_VECTOR: - cpu_on_reset_exception(GATE_VECTOR_FAULT); - break; - case C_RESET_SYSTEM_DATA: - cpu_on_reset_exception(SYSTEM_DATA_FAULT); - break; - case C_RESET_INT_STACK: - cpu_on_reset_exception(INTERRUPT_STACK_FAULT); - break; - default: - switch(et) { - case NORMAL_EXCEPTION: - cpu_on_normal_exception(isc); - break; - case STACK_EXCEPTION: - cpu_on_stack_exception(isc); - break; - case RESET_EXCEPTION: - cpu_on_reset_exception(isc); - break; - default: - stop_reason = STOP_EX; - break; - } - break; - } - } - /* Traps are handled at the end of instruction execution */ - } - - while (stop_reason == 0) { - trap = 0; - abort_context = C_NONE; - - if (sim_brk_summ && sim_brk_test(R[NUM_PC], SWMASK ('E'))) { - stop_reason = STOP_IBKPT; - break; - } - - if (cpu_exception_stack_depth > 0) { - cpu_exception_stack_depth--; - } - - AIO_CHECK_EVENT; - - if (sim_interval-- <= 0) { - if ((stop_reason = sim_process_event())) { - break; - } - } - - /* Process DMA requests */ - dmac_service_drqs(); - - /* - * Post-increment IU mode pointers (if needed). - * - * This is essentially a colossal hack. We never want to - * increment these pointers during an interlocked Read/Write - * operation, so we only increment after a CPU step has - * occured. - */ - if (iu_increment_a) { - increment_modep_a(); - } - if (iu_increment_b) { - increment_modep_b(); - } - - /* Interrupt Handling - * - * - NMI is always serviced first. - * - SBD interrupts are handled next in priority. - * - IO Bus boards are handled last. - */ - if (cpu_nmi) { - cpu_nmi = FALSE; - cpu_in_wait = FALSE; - cpu_on_interrupt(0); - } else if (cio_int_req) { - for (i = 0; i < CIO_SLOTS; i++) { - if ((cio_int_req & (1 << i)) && (PSW_CUR_IPL < cio[i].ipl)) { - cpu_in_wait = FALSE; - CIO_CLR_INT(i); - cpu_on_interrupt(cio[i].ivec); - break; - } - } - } else if (sbd_int_req) { - ipl = int_map[sbd_int_req]; - if (PSW_CUR_IPL < ipl) { - /* For the system board, interrupt vector is always - equal to IPL */ - cpu_in_wait = FALSE; - cpu_on_interrupt(ipl); - } - } - - if (cpu_in_wait) { - sim_idle(TMR_CLK, TRUE); - continue; - } - - /* Reset the TM bits */ - /* TODO: Figure out why we were doing this! */ - /* R[NUM_PSW] |= PSW_TM_MASK; */ - - /* Record the instruction for history */ - if (cpu_hist_size > 0) { - cpu_instr = &INST[cpu_hist_p]; - cpu_hist_p = (cpu_hist_p + 1) % cpu_hist_size; - } else { - cpu_instr = &inst; - } - - /* Decode the instruction */ - pc_incr = decode_instruction(cpu_instr); - - /* Make sure to update the valid bit for history keeping (if - * enabled) */ - cpu_instr->valid = TRUE; - - /* - * Operate on the decoded instruction. - */ - - /* Special case for coprocessor instructions */ - if (cpu_instr->mn->mode == OP_COPR) { - coprocessor_word = cpu_instr->operands[0].embedded.w; - } - - /* Get the operands */ - if (cpu_instr->mn->src_op1 >= 0) { - src1 = &cpu_instr->operands[cpu_instr->mn->src_op1]; - } - - if (cpu_instr->mn->src_op2 >= 0) { - src2 = &cpu_instr->operands[cpu_instr->mn->src_op2]; - } - - if (cpu_instr->mn->src_op3 >= 0) { - src3 = &cpu_instr->operands[cpu_instr->mn->src_op3]; - } - - if (cpu_instr->mn->dst_op >= 0) { - dst = &cpu_instr->operands[cpu_instr->mn->dst_op]; - } - - switch (cpu_instr->mn->opcode) { - case ADDW2: - case ADDH2: - case ADDB2: - a = cpu_read_op(src1); - b = cpu_read_op(dst); - add(a, b, dst); - break; - case ADDW3: - case ADDH3: - case ADDB3: - a = cpu_read_op(src1); - b = cpu_read_op(src2); - add(a, b, dst); - break; - case ALSW3: - a = cpu_read_op(src2); - b = cpu_read_op(src1); - result = (t_uint64)a << (b & 0x1f); - cpu_write_op(dst, result); - cpu_set_nz_flags(result, dst); - cpu_set_c_flag(0); - cpu_set_v_flag_op(result, dst); - break; - case ANDW2: - case ANDH2: - case ANDB2: - a = cpu_read_op(src1); - b = cpu_read_op(dst); - c = a & b; - cpu_write_op(dst, c); - cpu_set_nz_flags(c, dst); - cpu_set_c_flag(0); - cpu_set_v_flag_op(c, dst); - break; - case ANDW3: - case ANDH3: - case ANDB3: - a = cpu_read_op(src1); - b = cpu_read_op(src2); - c = a & b; - cpu_write_op(dst, c); - cpu_set_nz_flags(c, dst); - cpu_set_c_flag(0); - cpu_set_v_flag_op(c, dst); - break; - case BEH: - case BEH_D: - if (cpu_z_flag() == 1) { - pc_incr = sign_extend_h(dst->embedded.h); - } - break; - case BEB: - case BEB_D: - if (cpu_z_flag() == 1) { - pc_incr = sign_extend_b(dst->embedded.b); - } - break; - case BGH: - if ((cpu_n_flag() | cpu_z_flag()) == 0) { - pc_incr = sign_extend_h(dst->embedded.h); - } - break; - case BGB: - if ((cpu_n_flag() | cpu_z_flag()) == 0) { - pc_incr = sign_extend_b(dst->embedded.b); - } - break; - case BGEH: - if ((cpu_n_flag() == 0) | (cpu_z_flag() == 1)) { - pc_incr = sign_extend_h(dst->embedded.h); - } - break; - case BGEB: - if ((cpu_n_flag() == 0) | (cpu_z_flag() == 1)) { - pc_incr = sign_extend_b(dst->embedded.b); - } - break; - case BGEUH: - if (cpu_c_flag() == 0) { - pc_incr = sign_extend_h(dst->embedded.h); - } - break; - case BGEUB: - if (cpu_c_flag() == 0) { - pc_incr = sign_extend_b(dst->embedded.b); - } - break; - case BGUH: - if ((cpu_c_flag() | cpu_z_flag()) == 0) { - pc_incr = sign_extend_h(dst->embedded.h); - } - break; - case BGUB: - if ((cpu_c_flag() | cpu_z_flag()) == 0) { - pc_incr = sign_extend_b(dst->embedded.b); - } - break; - case BITW: - case BITH: - case BITB: - a = cpu_read_op(src1); - b = cpu_read_op(src2); - c = a & b; - cpu_set_nz_flags(c, src1); - cpu_set_c_flag(0); - cpu_set_v_flag(0); - break; - case BLH: - if ((cpu_n_flag() == 1) && (cpu_z_flag() == 0)) { - pc_incr = sign_extend_h(dst->embedded.h); - } - break; - case BLB: - if ((cpu_n_flag() == 1) && (cpu_z_flag() == 0)) { - pc_incr = sign_extend_b(dst->embedded.b); - } - break; - case BLEH: - if ((cpu_n_flag() | cpu_z_flag()) == 1) { - pc_incr = sign_extend_h(dst->embedded.h); - } - break; - case BLEB: - if ((cpu_n_flag() | cpu_z_flag()) == 1) { - pc_incr = sign_extend_b(dst->embedded.b); - } - break; - case BLEUH: - if ((cpu_c_flag() | cpu_z_flag()) == 1) { - pc_incr = sign_extend_h(dst->embedded.h); - } - break; - case BLEUB: - if ((cpu_c_flag() | cpu_z_flag()) == 1) { - pc_incr = sign_extend_b(dst->embedded.b); - } - break; - case BLUH: - if (cpu_c_flag() == 1) { - pc_incr = sign_extend_h(dst->embedded.h); - } - break; - case BLUB: - if (cpu_c_flag() == 1) { - pc_incr = sign_extend_b(dst->embedded.b); - } - break; - case BNEH: - case BNEH_D: - if (cpu_z_flag() == 0) { - pc_incr = sign_extend_h(dst->embedded.h); - } - break; - case BNEB: - case BNEB_D: - if (cpu_z_flag() == 0) { - pc_incr = sign_extend_b(dst->embedded.b); - } - break; - case BPT: - trap = BREAKPOINT_TRAP; - break; - case BRH: - pc_incr = sign_extend_h(dst->embedded.h); - break; - case BRB: - pc_incr = sign_extend_b(dst->embedded.b); - /* BRB is commonly used to halt the processor in a tight - * infinite loop. */ - if (pc_incr == 0) { - stop_reason = STOP_LOOP; - } - break; - case BSBH: - cpu_push_word(R[NUM_PC] + pc_incr); - pc_incr = sign_extend_h(dst->embedded.h); - break; - case BSBB: - cpu_push_word(R[NUM_PC] + pc_incr); - pc_incr = sign_extend_b(dst->embedded.b); - break; - case BVCH: - if (cpu_v_flag() == 0) { - pc_incr = sign_extend_h(dst->embedded.h); - } - break; - case BVCB: - if (cpu_v_flag() == 0) { - pc_incr = sign_extend_b(dst->embedded.b); - } - break; - case BVSH: - if (cpu_v_flag() == 1) { - pc_incr = sign_extend_h(dst->embedded.h); - } - break; - case BVSB: - if (cpu_v_flag() == 1) { - pc_incr = sign_extend_b(dst->embedded.b); - } - break; - case CALL: - a = cpu_effective_address(src1); - b = cpu_effective_address(dst); - write_w(R[NUM_SP] + 4, R[NUM_AP], BUS_CPU); - write_w(R[NUM_SP], R[NUM_PC] + pc_incr, BUS_CPU); - R[NUM_SP] += 8; - R[NUM_PC] = b; - R[NUM_AP] = a; - pc_incr = 0; - break; - case CFLUSH: - break; - case CALLPS: - if (cpu_execution_level() != EX_LVL_KERN) { - cpu_abort(NORMAL_EXCEPTION, PRIVILEGED_OPCODE); - break; - } - - a = R[0]; - - cpu_km = TRUE; - - abort_context = C_RESET_INT_STACK; - - irq_push_word(R[NUM_PCBP]); - - /* Set current PC to start of next instruction (always PC+2) */ - R[NUM_PC] += 2; - - /* Set old PSW ISC, TM, and ET to 0, 0, 1 */ - R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); - R[NUM_PSW] |= (1 << PSW_ET); - - cpu_context_switch_1(a); - abort_context = C_PROCESS_NEW_PCB; - cpu_context_switch_2(a); - - R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); - R[NUM_PSW] |= (7 << PSW_ISC); - R[NUM_PSW] |= (3 << PSW_ET); - - cpu_context_switch_3(a); - - abort_context = C_NONE; - - cpu_km = FALSE; - pc_incr = 0; - break; - case CLRW: - case CLRH: - case CLRB: - cpu_write_op(dst, 0); - cpu_set_n_flag(0); - cpu_set_z_flag(1); - cpu_set_c_flag(0); - cpu_set_v_flag(0); - break; - case CMPW: - case CMPH: - case CMPB: - a = cpu_read_op(src1); - b = cpu_read_op(src2); - - switch(op_type(src2)) { - case WD: - case UW: - cpu_set_n_flag((int32)b < (int32)a); - break; - case HW: - case UH: - cpu_set_n_flag((int16)b < (int16)a); - break; - case BT: - case SB: - cpu_set_n_flag((int8)b < (int8)a); - break; - default: - /* Unreachable */ - break; - } - - cpu_set_z_flag(b == a); - cpu_set_c_flag(b < a); - cpu_set_v_flag(0); - break; - case DECW: - case DECH: - case DECB: - a = cpu_read_op(dst); - sub(a, 1, dst); - break; - case DIVW2: - a = cpu_read_op(src1); - b = cpu_read_op(dst); - - if (a == 0) { - cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); - break; - } - - if (a == WORD_MASK && b == WD_MSB) { - cpu_set_v_flag(1); - } - - DIV(a, b, src1, dst, int32); - - cpu_write_op(dst, result); - cpu_set_nz_flags(result, dst); - cpu_set_c_flag(0); - break; - case DIVH2: - a = cpu_read_op(src1); - b = cpu_read_op(dst); - - if (a == 0) { - cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); - break; - } - - if (a == HALF_MASK && b == HW_MSB) { - cpu_set_v_flag(1); - } - - DIV(a, b, src1, dst, int16); - - cpu_write_op(dst, result); - cpu_set_nz_flags(result, dst); - cpu_set_c_flag(0); - break; - case DIVB2: - a = cpu_read_op(src1); - b = cpu_read_op(dst); - - if (a == 0) { - cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); - break; - } - - if (a == BYTE_MASK && b == BT_MSB) { - cpu_set_v_flag(1); - } - - result = (uint8)b / (uint8)a; - - cpu_write_op(dst, result); - cpu_set_nz_flags(result, dst); - cpu_set_c_flag(0); - break; - case DIVW3: - a = cpu_read_op(src1); - b = cpu_read_op(src2); - - if (a == 0) { - cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); - break; - } - - if (a == WORD_MASK && b == WD_MSB) { - cpu_set_v_flag(1); - } - - DIV(a, b, src1, src2, int32); - - cpu_write_op(dst, result); - cpu_set_nz_flags(result, dst); - cpu_set_c_flag(0); - break; - case DIVH3: - a = cpu_read_op(src1); - b = cpu_read_op(src2); - - if (a == 0) { - cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); - break; - } - - if (a == HALF_MASK && b == HW_MSB) { - cpu_set_v_flag(1); - } - - DIV(a, b, src1, src2, int16); - - cpu_write_op(dst, result); - cpu_set_nz_flags(result, dst); - cpu_set_c_flag(0); - break; - case DIVB3: - a = cpu_read_op(src1); - b = cpu_read_op(src2); - - if (a == 0) { - cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); - break; - } - - if (a == BYTE_MASK && b == BT_MSB) { - cpu_set_v_flag(1); - } - - result = (uint8)b / (uint8)a; - - cpu_write_op(dst, result); - cpu_set_nz_flags(result, dst); - cpu_set_c_flag(0); - break; - case MVERNO: - R[0] = CPU_VERSION; - break; - case ENBVJMP: - if (cpu_execution_level() != EX_LVL_KERN) { - cpu_abort(NORMAL_EXCEPTION, PRIVILEGED_OPCODE); - break; - } - mmu_enable(); - R[NUM_PC] = R[0]; - pc_incr = 0; - break; - case DISVJMP: - if (cpu_execution_level() != EX_LVL_KERN) { - cpu_abort(NORMAL_EXCEPTION, PRIVILEGED_OPCODE); - break; - } - mmu_disable(); - R[NUM_PC] = R[0]; - pc_incr = 0; - break; - case EXTFW: - case EXTFH: - case EXTFB: - width = (cpu_read_op(src1) & 0x1f) + 1; - offset = cpu_read_op(src2) & 0x1f; - if (width >= 32) { - mask = -1; - } else { - mask = (1ul << width) - 1; - } - mask = mask << offset; - - if (width + offset > 32) { - mask |= (1ul << ((width + offset) - 32)) - 1; - } - - a = cpu_read_op(src3); /* src */ - a &= mask; - a = a >> offset; - - cpu_write_op(dst, a); - cpu_set_nz_flags(a, dst); - cpu_set_c_flag(0); - cpu_set_v_flag_op(a, dst); - break; - case INCW: - case INCH: - case INCB: - a = cpu_read_op(dst); - add(a, 1, dst); - break; - case INSFW: - case INSFH: - case INSFB: - width = (cpu_read_op(src1) & 0x1f) + 1; - offset = cpu_read_op(src2) & 0x1f; - if (width >= 32) { - mask = -1; - } else { - mask = (1ul << width) - 1; - } - - a = cpu_read_op(src3) & mask; /* src */ - b = cpu_read_op(dst); /* dst */ - - b &= ~(mask << offset); - b |= (a << offset); - - cpu_write_op(dst, b); - cpu_set_nz_flags(b, dst); - cpu_set_c_flag(0); - cpu_set_v_flag_op(b, dst); - break; - case JMP: - R[NUM_PC] = cpu_effective_address(dst); - pc_incr = 0; - break; - case JSB: - cpu_push_word(R[NUM_PC] + pc_incr); - R[NUM_PC] = cpu_effective_address(dst); - pc_incr = 0; - break; - case LLSW3: - case LLSH3: - case LLSB3: - result = (t_uint64)cpu_read_op(src2) << (cpu_read_op(src1) & 0x1f); - cpu_write_op(dst, result); - cpu_set_nz_flags(result, dst); - cpu_set_c_flag(0); - cpu_set_v_flag_op(result, dst); - break; - case ARSW3: - case ARSH3: - case ARSB3: - a = cpu_read_op(src2); - b = cpu_read_op(src1) & 0x1f; - result = a >> b; - /* Ensure the MSB is copied appropriately */ - switch (op_type(src2)) { - case WD: - if (a & 0x80000000) { - result |= shift_32_table[b + 1]; - } - break; - case HW: - if (a & 0x8000) { - result |= shift_16_table[b + 1]; - } - break; - case BT: - if (a & 0x80) { - result |= shift_8_table[b + 1]; - } - break; - } - cpu_write_op(dst, result); - cpu_set_nz_flags(result, dst); - cpu_set_c_flag(0); - cpu_set_v_flag(0); - break; - case LRSW3: - a = (uint32) cpu_read_op(src2) >> (cpu_read_op(src1) & 0x1f); - cpu_write_op(dst, a); - cpu_set_nz_flags(a, dst); - cpu_set_c_flag(0); - cpu_set_v_flag_op(a, dst); - break; - case GATE: - cpu_km = TRUE; - if (R[NUM_SP] < read_w(R[NUM_PCBP] + 12, ACC_AF, BUS_CPU) || - R[NUM_SP] > read_w(R[NUM_PCBP] + 16, ACC_AF, BUS_CPU)) { - sim_debug(EXECUTE_MSG, &cpu_dev, - "[%08x] STACK OUT OF BOUNDS IN GATE. " - "SP=%08x, R[NUM_PCBP]+12=%08x, " - "R[NUM_PCBP]+16=%08x\n", - R[NUM_PC], - R[NUM_SP], - read_w(R[NUM_PCBP] + 12, ACC_AF, BUS_CPU), - read_w(R[NUM_PCBP] + 16, ACC_AF, BUS_CPU)); - cpu_abort(STACK_EXCEPTION, STACK_BOUND); - } - cpu_km = FALSE; - - abort_context = C_STACK_FAULT; - - /* Push PC+2 onto stack */ - write_w(R[NUM_SP], R[NUM_PC] + 2, BUS_CPU); - - /* Write 1, 0, 2 to ISC, TM, ET */ - R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); - R[NUM_PSW] |= (1 << PSW_ISC); - R[NUM_PSW] |= (2 << PSW_ET); - - /* Push PSW onto stack */ - write_w(R[NUM_SP] + 4, R[NUM_PSW], BUS_CPU); - - abort_context = C_NONE; - - /* Perform gate entry-point 2 */ - cpu_perform_gate(R[0] & 0x7c, - R[1] & 0x7ff8); - - /* Finish push of PC and PSW */ - R[NUM_SP] += 8; - pc_incr = 0; - -#if defined(REV3) - /* - * Both the WE 32100 and the WE 32200 processor manuals - * state that this is not a privileged instruction, and - * that it can be run from any processor level. This is - * true for the WE 32100, but not true for the WE - * 32200. Tests in the Rev 3 off-line processor - * diagnostics check to ensure that there is an exception - * raised when calling GATE in a non-privileged context. - */ - if (cpu_execution_level() != EX_LVL_KERN) { - cpu_abort(NORMAL_EXCEPTION, PRIVILEGED_OPCODE); - } -#endif - break; - case MCOMW: - case MCOMH: - case MCOMB: /* One's complement */ - a = ~(cpu_read_op(src1)); - cpu_write_op(dst, a); - cpu_set_nz_flags(a, dst); - cpu_set_c_flag(0); - cpu_set_v_flag_op(a, dst); - break; - case MNEGW: - case MNEGH: - case MNEGB: /* Two's complement */ - a = ~cpu_read_op(src1) + 1; - cpu_write_op(dst, a); - cpu_set_nz_flags(a, dst); - cpu_set_c_flag(0); - cpu_set_v_flag_op(a, dst); - break; - case MOVBLW: - while (R[2] != 0) { - a = read_w(R[0], ACC_AF, BUS_CPU); - write_w(R[1], a, BUS_CPU); - R[2]--; - R[0] += 4; - R[1] += 4; - } - break; - case STREND: - while (read_b(R[0], ACC_AF, BUS_CPU) != '\0') { - R[0]++; - } - break; - case SWAPWI: - case SWAPHI: - case SWAPBI: - a = cpu_read_op(dst); - cpu_write_op(dst, R[0]); - R[0] = a; - cpu_set_nz_flags(a, dst); - cpu_set_v_flag(0); - cpu_set_c_flag(0); - break; - case ROTW: - a = cpu_read_op(src1) & 0x1f; - b = (uint32) cpu_read_op(src2); - mask = (CHAR_BIT * sizeof(a) - 1); - d = (b >> a) | (b << ((~a + 1) & mask)); - cpu_write_op(dst, d); - cpu_set_nz_flags(d, dst); - cpu_set_v_flag(0); - cpu_set_c_flag(0); - break; - case MOVAW: - a = cpu_effective_address(src1); - cpu_write_op(dst, a); - cpu_set_nz_flags(a, dst); - cpu_set_v_flag(0); - cpu_set_c_flag(0); - break; - case MOVTRW: - a = cpu_effective_address(src1); - result = mmu_xlate_addr(a, ACC_MT); - cpu_write_op(dst, result); - cpu_set_nz_flags(result, dst); - cpu_set_v_flag(0); - cpu_set_c_flag(0); - break; - case MOVW: - case MOVH: - case MOVB: - a = cpu_read_op(src1); - cpu_write_op(dst, a); - - /* Flags are never set if the source or destination is the - PSW */ - if (!(op_is_psw(src1) || op_is_psw(dst))) { - cpu_set_nz_flags(a, dst); - cpu_set_c_flag(0); - cpu_set_v_flag_op(a, dst); - } - - /* However, if a move to PSW set the O bit, we have to - generate an overflow exception trap */ - if (op_is_psw(dst) && (R[NUM_PSW] & PSW_OE_MASK)) { - trap = INTEGER_OVERFLOW; - } - break; - case MODW2: - a = cpu_read_op(src1); - b = cpu_read_op(dst); - if (a == 0) { - cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); - break; - } - MOD(a, b, src1, dst, int32); - cpu_write_op(dst, result); - cpu_set_nz_flags(result, dst); - cpu_set_c_flag(0); - cpu_set_v_flag_op(result, dst); - break; - case MODH2: - a = cpu_read_op(src1); - b = cpu_read_op(dst); - if (a == 0) { - cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); - break; - } - MOD(a, b, src1, dst, int16); - cpu_write_op(dst, result); - cpu_set_nz_flags(result, dst); - cpu_set_c_flag(0); - cpu_set_v_flag_op(result, dst); - break; - case MODB2: - a = cpu_read_op(src1); - b = cpu_read_op(dst); - if (a == 0) { - cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); - break; - } - result = (uint8)b % (uint8)a; - cpu_write_op(dst, result); - cpu_set_nz_flags(result, dst); - cpu_set_c_flag(0); - cpu_set_v_flag_op(result, dst); - break; - break; - case MODW3: - a = cpu_read_op(src1); - b = cpu_read_op(src2); - if (a == 0) { - cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); - break; - } - MOD(a, b, src1, src2, int32); - cpu_write_op(dst, result); - cpu_set_nz_flags(result, dst); - cpu_set_c_flag(0); - cpu_set_v_flag_op(result, dst); - break; - case MODH3: - a = cpu_read_op(src1); - b = cpu_read_op(src2); - if (a == 0) { - cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); - break; - } - MOD(a, b, src1, src2, int16); - cpu_write_op(dst, result); - cpu_set_nz_flags(result, dst); - cpu_set_c_flag(0); - cpu_set_v_flag_op(result, dst); - break; - case MODB3: - a = cpu_read_op(src1); - b = cpu_read_op(src2); - if (a == 0) { - cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); - break; - } - result = (uint8)b % (uint8)a; - cpu_write_op(dst, result); - cpu_set_nz_flags(result, dst); - cpu_set_c_flag(0); - cpu_set_v_flag_op(result, dst); - break; - case MULW2: - result = (t_uint64)cpu_read_op(src1) * (t_uint64)cpu_read_op(dst); - cpu_write_op(dst, (uint32)(result & WORD_MASK)); - cpu_set_nz_flags((uint32)(result & WORD_MASK), dst); - cpu_set_c_flag(0); - cpu_set_v_flag_op(result, dst); - break; - case MULH2: - a = cpu_read_op(src1) * cpu_read_op(dst); - cpu_write_op(dst, a); - cpu_set_nz_flags(a, dst); - cpu_set_c_flag(0); - cpu_set_v_flag_op(result, dst); - break; - case MULB2: - a = cpu_read_op(src1) * cpu_read_op(dst); - cpu_write_op(dst, a); - cpu_set_nz_flags(a, dst); - cpu_set_c_flag(0); - cpu_set_v_flag_op(result, src1); - break; - case MULW3: - result = (t_uint64)cpu_read_op(src1) * (t_uint64)cpu_read_op(src2); - cpu_write_op(dst, (uint32)(result & WORD_MASK)); - cpu_set_nz_flags((uint32)(result & WORD_MASK), dst); - cpu_set_c_flag(0); - cpu_set_v_flag_op(result, dst); - break; - case MULH3: - a = cpu_read_op(src1) * cpu_read_op(src2); - cpu_write_op(dst, a); - cpu_set_nz_flags(a, dst); - cpu_set_c_flag(0); - cpu_set_v_flag_op(result, dst); - break; - case MULB3: - a = cpu_read_op(src1) * cpu_read_op(src2); - cpu_write_op(dst, a); - cpu_set_nz_flags(a, dst); - cpu_set_c_flag(0); - cpu_set_v_flag_op(result, dst); - break; - case NOP: - break; - case NOP2: - pc_incr += 1; - break; - case NOP3: - pc_incr += 2; - break; - case ORW2: - case ORH2: - case ORB2: - a = (cpu_read_op(src1) | cpu_read_op(dst)); - cpu_write_op(dst, a); - cpu_set_nz_flags(a, dst); - cpu_set_c_flag(0); - cpu_set_v_flag_op(a, dst); - break; - case ORW3: - case ORH3: - case ORB3: - a = (cpu_read_op(src1) | cpu_read_op(src2)); - cpu_write_op(dst, a); - cpu_set_nz_flags(a, dst); - cpu_set_c_flag(0); - cpu_set_v_flag_op(a, dst); - break; - case POPW: - /* N.B. "If dst is the stack pointer (%sp), the results - are indeterminate". The ordering here is important. If - we decrement SP before writing the results, we end up - in a weird, bad state. */ - a = read_w(R[NUM_SP] - 4, ACC_AF, BUS_CPU); - cpu_write_op(dst, a); - R[NUM_SP] -= 4; - cpu_set_nz_flags(a, dst); - cpu_set_c_flag(0); - cpu_set_v_flag(0); - break; - case PUSHAW: - a = cpu_effective_address(src1); - cpu_push_word(a); - cpu_set_nz_flags(a, src1); - cpu_set_c_flag(0); - cpu_set_v_flag(0); - break; - case PUSHW: - a = cpu_read_op(src1); - cpu_push_word(a); - cpu_set_nz_flags(a, src1); - cpu_set_c_flag(0); - cpu_set_v_flag(0); - break; - case RGEQ: - if (cpu_n_flag() == 0 || cpu_z_flag() == 1) { - R[NUM_PC] = cpu_pop_word(); - pc_incr = 0; - } - break; - case RGEQU: - if (cpu_c_flag() == 0) { - R[NUM_PC] = cpu_pop_word(); - pc_incr = 0; - } - break; - case RGTR: - if ((cpu_n_flag() | cpu_z_flag()) == 0) { - R[NUM_PC] = cpu_pop_word(); - pc_incr = 0; - } - break; - case RNEQ: - case RNEQU: - if (cpu_z_flag() == 0) { - R[NUM_PC] = cpu_pop_word(); - pc_incr = 0; - } - break; - case RET: - a = R[NUM_AP]; - b = read_w(R[NUM_SP] - 4, ACC_AF, BUS_CPU); - c = read_w(R[NUM_SP] - 8, ACC_AF, BUS_CPU); - R[NUM_AP] = b; - R[NUM_PC] = c; - R[NUM_SP] = a; - pc_incr = 0; - break; - case RETG: - abort_context = C_STACK_FAULT; - a = read_w(R[NUM_SP] - 4, ACC_AF, BUS_CPU); /* PSW */ - b = read_w(R[NUM_SP] - 8, ACC_AF, BUS_CPU); /* PC */ - abort_context = C_NONE; - if ((a & PSW_CM_MASK) < (R[NUM_PSW] & PSW_CM_MASK)) { - sim_debug(EXECUTE_MSG, &cpu_dev, - "[%08x] Illegal level change. New level=%d, Cur level=%d\n", - R[NUM_PC], - (a & PSW_CM_MASK) >> PSW_CM, - (R[NUM_PSW] & PSW_CM_MASK) >> PSW_CM); - cpu_abort(NORMAL_EXCEPTION, ILLEGAL_LEVEL_CHANGE); - break; - } - /* Clear some state and move it from the current PSW */ - a &= ~PSW_IPL_MASK; - a &= ~PSW_CFD_MASK; - a &= ~PSW_QIE_MASK; - a &= ~PSW_CD_MASK; - a &= ~PSW_R_MASK; - a &= ~PSW_ISC_MASK; - a &= ~PSW_TM_MASK; - a &= ~PSW_ET_MASK; - - a |= (R[NUM_PSW] & PSW_IPL_MASK); - a |= (R[NUM_PSW] & PSW_CFD_MASK); - a |= (R[NUM_PSW] & PSW_QIE_MASK); - a |= (R[NUM_PSW] & PSW_CD_MASK); - a |= (R[NUM_PSW] & PSW_R_MASK); - a |= (7 << PSW_ISC); - a |= (3 << PSW_ET); - - R[NUM_PSW] = a; - R[NUM_PC] = b; - - R[NUM_SP] -= 8; - pc_incr = 0; - break; - case RETPS: - if (cpu_execution_level() != EX_LVL_KERN) { - cpu_abort(NORMAL_EXCEPTION, PRIVILEGED_OPCODE); - break; - } - - /* Force kernel memory access */ - cpu_km = TRUE; - - abort_context = C_RESET_INT_STACK; - /* Restore process state */ - a = irq_pop_word(); /* New process PCBP */ - - abort_context = C_PROCESS_OLD_PCB; - b = read_w(a, ACC_AF, BUS_CPU); /* New PSW */ - - abort_context = C_PROCESS_NEW_PCB; - /* Copy the 'R' flag from the new PSW to the old PSW */ - R[NUM_PSW] &= ~PSW_R_MASK; - R[NUM_PSW] |= (b & PSW_R_MASK); - - /* a now holds the new PCBP */ - cpu_context_switch_2(a); - - /* Perform block moves, if any */ - cpu_context_switch_3(a); - - /* Restore registers if R bit is set */ - if (R[NUM_PSW] & PSW_R_MASK) { - R[NUM_FP] = read_w(a + 24, ACC_AF, BUS_CPU); - R[0] = read_w(a + 28, ACC_AF, BUS_CPU); - R[1] = read_w(a + 32, ACC_AF, BUS_CPU); - R[2] = read_w(a + 36, ACC_AF, BUS_CPU); - R[3] = read_w(a + 40, ACC_AF, BUS_CPU); - R[4] = read_w(a + 44, ACC_AF, BUS_CPU); - R[5] = read_w(a + 48, ACC_AF, BUS_CPU); - R[6] = read_w(a + 52, ACC_AF, BUS_CPU); - R[7] = read_w(a + 56, ACC_AF, BUS_CPU); - R[8] = read_w(a + 60, ACC_AF, BUS_CPU); - R[NUM_AP] = read_w(a + 20, ACC_AF, BUS_CPU); - } - - abort_context = C_NONE; - - /* Un-force kernel memory access */ - cpu_km = FALSE; - pc_incr = 0; - break; - case INTACK: - R[0] = cpu_int_ack << 2; - break; - case EXTOP: - sim_debug(EXECUTE_MSG, &cpu_dev, - "[%08x] EXTOP instruction.\n", - R[NUM_PC]); - cpu_abort(NORMAL_EXCEPTION, RESERVED_OPCODE); - break; - case SPOP: - /* Memory fault is signaled when no support processor is - active */ - if (mau_broadcast(coprocessor_word, 0, 0) != SCPE_OK) { - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); - } - break; - case SPOPD2: - case SPOPS2: - case SPOPT2: - a = cpu_effective_address(src1); - b = cpu_effective_address(dst); - if (mau_broadcast(coprocessor_word, a, b) != SCPE_OK) { - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); - } - break; - case SPOPRD: - case SPOPRS: - case SPOPRT: - a = cpu_effective_address(src1); - if (mau_broadcast(coprocessor_word, a, 0) != SCPE_OK) { - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); - } - break; - case SPOPWD: - case SPOPWS: - case SPOPWT: - a = cpu_effective_address(dst); - if (mau_broadcast(coprocessor_word, 0, a) != SCPE_OK) { - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); - } - break; - case SUBW2: - case SUBH2: - case SUBB2: - a = cpu_read_op(dst); - b = cpu_read_op(src1); - sub(a, b, dst); - break; - case SUBW3: - case SUBH3: - case SUBB3: - a = cpu_read_op(src2); - b = cpu_read_op(src1); - sub(a, b, dst); - break; - case RESTORE: - a = R[NUM_FP] - 28; /* Old FP */ - b = read_w(a, ACC_AF, BUS_CPU); /* Old FP */ - c = R[NUM_FP] - 24; /* Old save point */ - - for (d = src1->reg; d < NUM_FP; d++) { - R[d] = read_w(c, ACC_AF, BUS_CPU); - c += 4; - } - - R[NUM_FP] = b; /* Restore FP */ - R[NUM_SP] = a; /* Restore SP */ - break; - case RGTRU: - if ((cpu_c_flag() & cpu_z_flag()) == 0) { - R[NUM_PC] = cpu_pop_word(); - pc_incr = 0; - } - break; - case RLEQ: - if ((cpu_n_flag() | cpu_z_flag()) == 1) { - R[NUM_PC] = cpu_pop_word(); - pc_incr = 0; - } - break; - case RLEQU: - if ((cpu_c_flag() | cpu_z_flag()) == 1) { - R[NUM_PC] = cpu_pop_word(); - pc_incr = 0; - } - break; - case RLSS: - if ((cpu_n_flag() == 1) & (cpu_z_flag() == 0)) { - R[NUM_PC] = cpu_pop_word(); - pc_incr = 0; - } - break; - case RLSSU: - if (cpu_c_flag() == 1) { - R[NUM_PC] = cpu_pop_word(); - pc_incr = 0; - } - break; - case REQL: - if (cpu_z_flag() == 1) { - R[NUM_PC] = cpu_pop_word(); - pc_incr = 0; - } - break; - case REQLU: - if (cpu_z_flag() == 1) { - R[NUM_PC] = cpu_pop_word(); - pc_incr = 0; - } - break; - case RSB: - R[NUM_PC] = cpu_pop_word(); - pc_incr = 0; - break; - case RVC: - if (cpu_v_flag() == 0) { - R[NUM_PC] = cpu_pop_word(); - pc_incr = 0; - } - break; - case RVS: - if (cpu_v_flag() == 1) { - R[NUM_PC] = cpu_pop_word(); - pc_incr = 0; - } - break; - case SAVE: - /* Save the FP register */ - write_w(R[NUM_SP], R[NUM_FP], BUS_CPU); - - /* Save all the registers from the one identified by the - src operand up to FP (exclusive) */ - for (a = src1->reg, b = 4; a < NUM_FP; a++, b += 4) { - write_w(R[NUM_SP] + b, R[a], BUS_CPU); - } - - R[NUM_SP] = R[NUM_SP] + 28; - R[NUM_FP] = R[NUM_SP]; - break; - case STRCPY: - /* The STRCPY instruction will always copy the NULL - * terminator of a string. However, copying the NULL - * terminator never increments the source or destination - * pointer! */ - while (1) { - a = read_b(R[0], ACC_AF, BUS_CPU); - write_b(R[1], (uint8) a, BUS_CPU); - if (a == '\0') { - break; - } - R[0]++; - R[1]++; - } - break; - case TSTW: - a = cpu_read_op(src1); - cpu_set_n_flag((int32)a < 0); - cpu_set_z_flag(a == 0); - cpu_set_c_flag(0); - cpu_set_v_flag(0); - break; - case TSTH: - a = cpu_read_op(src1); - cpu_set_n_flag((int16)a < 0); - cpu_set_z_flag(a == 0); - cpu_set_c_flag(0); - cpu_set_v_flag(0); - break; - case TSTB: - a = cpu_read_op(src1); - cpu_set_n_flag((int8)a < 0); - cpu_set_z_flag(a == 0); - cpu_set_c_flag(0); - cpu_set_v_flag(0); - break; - case WAIT: - if (cpu_execution_level() != EX_LVL_KERN) { - cpu_abort(NORMAL_EXCEPTION, PRIVILEGED_OPCODE); - break; - } - cpu_in_wait = TRUE; - break; - case XORW2: - case XORH2: - case XORB2: - a = (cpu_read_op(src1) ^ cpu_read_op(dst)); - cpu_write_op(dst, a); - cpu_set_nz_flags(a, dst); - cpu_set_c_flag(0); - cpu_set_v_flag_op(a, dst); - break; - case XORW3: - case XORH3: - case XORB3: - a = (cpu_read_op(src1) ^ cpu_read_op(src2)); - cpu_write_op(dst, a); - cpu_set_nz_flags(a, dst); - cpu_set_c_flag(0); - cpu_set_v_flag_op(a, dst); - break; -#if defined(REV3) - case ADDPB2: - a = cpu_read_op(src1); - b = cpu_read_op(dst); - result = add_bcd(a, b); /* sets flags */ - cpu_write_op(dst, result); - break; - case ADDPB3: - a = cpu_read_op(src1); - b = cpu_read_op(src2); - result = add_bcd(a, b); /* sets flags */ - cpu_write_op(dst, result); - break; - case DTB: - a = cpu_read_op(dst); - result = a - 1; - cpu_write_op(dst, (uint32)(result & WORD_MASK)); - if ((int32)result > -1) { - pc_incr = sign_extend_b(src1->embedded.b); - } - sim_debug(EXECUTE_MSG, &cpu_dev, - "[%08x] DTB: dst=%08x r=%08x emb=%04x\n", - R[NUM_PC], a, (uint32)(result & WORD_MASK), src1->embedded.h); - break; - case DTH: - a = cpu_read_op(dst); - result = a - 1; - cpu_write_op(dst, (uint32)(result & WORD_MASK)); - if ((int32)result > -1) { - pc_incr = sign_extend_h(src1->embedded.h); - } - sim_debug(EXECUTE_MSG, &cpu_dev, - "[%08x] DTH: dst=%08x r=%08x emb=%04x\n", - R[NUM_PC], a, (uint32)(result & WORD_MASK), src1->embedded.h); - break; - case TEDTB: - if (cpu_z_flag() == 0) { - a = cpu_read_op(dst); - result = a - 1; - cpu_write_op(dst, (uint32)(result & WORD_MASK)); - if ((int32)result > -1) { - pc_incr = sign_extend_b(src1->embedded.b); - } - } - break; - case TEDTH: - if (cpu_z_flag() == 0) { - a = cpu_read_op(dst); - result = a - 1; - cpu_write_op(dst, (uint32)(result & WORD_MASK)); - if ((int32)result > -1) { - pc_incr = sign_extend_h(src1->embedded.h); - } - } - break; - case TGDTB: - if ((cpu_n_flag() | cpu_z_flag()) == 1) { - a = cpu_read_op(dst); - result = a - 1; - cpu_write_op(dst, (uint32)(result & WORD_MASK)); - if ((int32)result > -1) { - pc_incr = sign_extend_b(src1->embedded.b); - } - } - break; - case TGDTH: - if ((cpu_n_flag() | cpu_z_flag()) == 1) { - a = cpu_read_op(dst); - result = a - 1; - cpu_write_op(dst, (uint32)(result & WORD_MASK)); - if ((int32)result > -1) { - pc_incr = sign_extend_h(src1->embedded.h); - } - } - break; - case TGEDTB: - if ((cpu_n_flag() == 1) & (cpu_z_flag() == 0)) { - a = cpu_read_op(dst); - result = a - 1; - cpu_write_op(dst, (uint32)(result & WORD_MASK)); - if ((int32)result > -1) { - pc_incr = sign_extend_b(src1->embedded.b); - } - } - break; - case TGEDTH: - if ((cpu_n_flag() == 1) & (cpu_z_flag() == 0)) { - a = cpu_read_op(dst); - result = a - 1; - cpu_write_op(dst, (uint32)(result & WORD_MASK)); - if ((int32)result > -1) { - pc_incr = sign_extend_h(src1->embedded.h); - } - } - break; - case TNEDTB: - if (cpu_z_flag() == 1) { - a = cpu_read_op(dst); - result = a - 1; - cpu_write_op(dst, (uint32)(result & WORD_MASK)); - if ((int32)result > -1) { - pc_incr = sign_extend_b(src1->embedded.b); - } - } - break; - case TNEDTH: - if (cpu_z_flag() == 1) { - a = cpu_read_op(dst); - result = a - 1; - cpu_write_op(dst, (uint32)(result & WORD_MASK)); - if ((int32)result > -1) { - pc_incr = sign_extend_h(src1->embedded.h); - } - } - break; - case SUBPB2: - a = cpu_read_op(src1); - b = cpu_read_op(dst); - result = sub_bcd(b, a); /* sets flags */ - cpu_write_op(dst, result); - break; - case SUBPB3: - a = cpu_read_op(src1); - b = cpu_read_op(src2); - result = sub_bcd(b, a); /* sets flags */ - cpu_write_op(dst, result); - break; - case PACKB: - a = cpu_read_op(src1); - b = ((a & 0x0f00) >> 4) | (a & 0xf); - cpu_write_op(dst, b); - break; - case UNPACKB: - a = cpu_read_op(src1); /* d1, d0 */ - b = cpu_read_op(src2); /* d3, d2 */ - c = ((b & 0xf0) << 8) | ((a & 0xf0) << 4) | - ((b & 0xf) << 4) | ((a & 0xf)); /* d3, d1, d2, d0 */ - cpu_write_op(dst, c); - break; - case CASWI: - a = cpu_read_op(src1); - b = cpu_read_op(src2); - c = cpu_read_op(dst); - result = c - b; - - if (result == 0) { - cpu_write_op(dst, a); - } else { - cpu_write_op(src2, c); - } - - cpu_set_n_flag((int32)result < 0); - cpu_set_z_flag(result == 0); - cpu_set_c_flag((uint32)b > (uint32)c); - cpu_set_v_flag_op(result, dst); - break; - case SETX: - R[NUM_PSW] |= (1 << PSW_X); - break; - case CLRX: - R[NUM_PSW] &= ~(1 << PSW_X); - break; - case RETQINT: - abort_context = C_RESET_INT_STACK; - - /* Get old PSW value from interrupt stack */ - a = read_w(R[NUM_ISP] - 4, ACC_AF, BUS_CPU); - - /* Update PSW */ - R[NUM_PSW] &= ~(QIE_PSW_MASK); - R[NUM_PSW] |= (a & QIE_PSW_MASK); - - /* Get old PC value from interrupt stack */ - b = read_w(R[NUM_ISP] - 8, ACC_AF, BUS_CPU); - - /* Set new PC */ - R[NUM_PC] = b; - - /* Finish interrupt stack pop */ - R[NUM_ISP] = R[NUM_ISP] - 8; - - /* Set new PSW ISC/TM/ET to 7/0/3 */ - R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); - R[NUM_PSW] |= 7 << PSW_ISC; - R[NUM_PSW] |= 3 << PSW_ET; - - /* Done */ - abort_context = C_NONE; - break; - case UCALLPS: - if ((R[NUM_PSW] & PSW_EXUC_MASK) == 0) { - cpu_abort(NORMAL_EXCEPTION, ILLEGAL_OPCODE); - } - - cpu_km = TRUE; - - /* Get the new PCBP */ - abort_context = C_RESET_SYSTEM_DATA; - a = read_w(0x488, ACC_AF, BUS_CPU); - - /* Save the existing PCBP */ - abort_context = C_RESET_INT_STACK; - irq_push_word(R[NUM_PCBP]); - - /* Prepare the new PC to be pushed on the stack */ - R[NUM_PC] = R[NUM_PC] + 2; - - /* Set ISC/TM/ET to 0/0/1 */ - R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); - R[NUM_PSW] |= 1 << PSW_ET; - - cpu_context_switch_1(a); - abort_context = C_PROCESS_NEW_PCB; - cpu_context_switch_2(a); - - /* Set ISC/TM/ET to 7/0/3 */ - R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); - R[NUM_PSW] |= 7 << PSW_ISC; - R[NUM_PSW] |= 3 << PSW_ET; - - cpu_context_switch_3(a); - - abort_context = C_NONE; - cpu_km = FALSE; - - break; -#endif - default: - sim_debug(EXECUTE_MSG, &cpu_dev, - "[%08x] Illegal Opcode 0x%x\n", - R[NUM_PC], cpu_instr->mn->opcode); - cpu_abort(NORMAL_EXCEPTION, ILLEGAL_OPCODE); - }; - - /* Increment the PC appropriately */ - R[NUM_PC] += pc_incr; - - /* If TE and TM are both set, generate a trace trap */ - if ((R[NUM_PSW] & PSW_TE_MASK) && (R[NUM_PSW] & PSW_TM_MASK)) { - trap = TRACE_TRAP; - } - - /* Handle traps */ - if (trap) { - R[NUM_PSW] &= ~(PSW_ET_MASK); - R[NUM_PSW] &= ~(PSW_ISC_MASK); - R[NUM_PSW] |= NORMAL_EXCEPTION; - R[NUM_PSW] |= (uint32) (trap << PSW_ISC); - cpu_on_normal_exception(trap); - } - } - - return stop_reason; -} - -static SIM_INLINE void cpu_on_process_exception(uint8 isc) -{ - uint32 new_pcbp; - - sim_debug(EXECUTE_MSG, &cpu_dev, - "[cpu_on_process_exception %d] SP=%08x PCBP=%08x ISP=%08x\n", - isc, R[NUM_SP], R[NUM_PCBP], R[NUM_ISP]); - - cpu_km = TRUE; - - abort_context = C_RESET_SYSTEM_DATA; - new_pcbp = read_w(0x84, ACC_AF, BUS_CPU); - - abort_context = C_RESET_INT_STACK; - irq_push_word(R[NUM_PCBP]); - - cpu_context_switch_2(new_pcbp); - - /* Set TM and ET to 0 and 3 in new PSW */ - R[NUM_PSW] &= ~(PSW_TM_MASK|PSW_ET_MASK); - R[NUM_PSW] |= (3 << PSW_ET); - - cpu_km = FALSE; - abort_context = C_NONE; - return; -} - -static SIM_INLINE void cpu_on_reset_exception(uint8 isc) -{ - uint32 new_pcbp; - - sim_debug(EXECUTE_MSG, &cpu_dev, - "[cpu_on_reset_exception %d] SP=%08x PCBP=%08x ISP=%08x\n", - isc, R[NUM_SP], R[NUM_PCBP], R[NUM_ISP]); - - if (isc == EXTERNAL_RESET) { - R[NUM_PSW] &= ~(PSW_R_MASK); - } - - cpu_km = TRUE; - - mmu_disable(); - - abort_context = C_RESET_SYSTEM_DATA; - new_pcbp = read_w(0x80, ACC_AF, BUS_CPU); - - abort_context = C_RESET_NEW_PCB; - cpu_context_switch_2(new_pcbp); - - cpu_km = FALSE; - abort_context = C_NONE; -} - -static SIM_INLINE void cpu_on_stack_exception(uint8 isc) -{ - uint32 new_pcbp; - - sim_debug(EXECUTE_MSG, &cpu_dev, - "[cpu_on_stack_exception %d] SP=%08x PCBP=%08x ISP=%08x\n", - isc, R[NUM_SP], R[NUM_PCBP], R[NUM_ISP]); - - abort_context = C_RESET_SYSTEM_DATA; - cpu_km = TRUE; - new_pcbp = read_w(0x88, ACC_AF, BUS_CPU); - - abort_context = C_RESET_INT_STACK; - irq_push_word(R[NUM_PCBP]); - - abort_context = C_PROCESS_OLD_PCB; - R[NUM_PSW] &= ~(PSW_ET_MASK|PSW_ISC_MASK); - R[NUM_PSW] |= (2 << PSW_ET); - R[NUM_PSW] |= (uint32) (isc << PSW_ISC); - - cpu_context_switch_1(new_pcbp); - cpu_context_switch_2(new_pcbp); - - /* Set ISC, TM, and ET to 7, 0, 3 in new PSW */ - R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); - R[NUM_PSW] |= (7 << PSW_ISC); - R[NUM_PSW] |= (3 << PSW_ET); - - cpu_km = FALSE; - abort_context = C_NONE; -} - -static SIM_INLINE void cpu_on_normal_exception(uint8 isc) -{ - sim_debug(EXECUTE_MSG, &cpu_dev, - "[cpu_on_normal_exception %d] %%sp=%08x abort_context=%d\n", - isc, R[NUM_SP], abort_context); - - cpu_km = TRUE; - if (R[NUM_SP] < read_w(R[NUM_PCBP] + 12, ACC_AF, BUS_CPU) || - R[NUM_SP] > read_w(R[NUM_PCBP] + 16, ACC_AF, BUS_CPU)) { - sim_debug(EXECUTE_MSG, &cpu_dev, - "STACK OUT OF BOUNDS IN EXCEPTION HANDLER. " - "SP=%08x, R[NUM_PCBP]+12=%08x, " - "R[NUM_PCBP]+16=%08x\n", - R[NUM_SP], - read_w(R[NUM_PCBP] + 12, ACC_AF, BUS_CPU), - read_w(R[NUM_PCBP] + 16, ACC_AF, BUS_CPU)); - cpu_abort(STACK_EXCEPTION, STACK_BOUND); - } - cpu_km = FALSE; - - /* Set context for STACK (FAULT) */ - abort_context = C_STACK_FAULT; - /* Save address of next instruction to stack */ - write_w(R[NUM_SP], R[NUM_PC], BUS_CPU); - - /* Write 0, 3 to TM, ET fields of PSW */ - R[NUM_PSW] &= ~(PSW_TM_MASK|PSW_ET_MASK); - R[NUM_PSW] |= (3 << PSW_ET); - - /* Save PSW to stack */ - write_w(R[NUM_SP] + 4, R[NUM_PSW], BUS_CPU); - - /* Set context for RESET (GATE VECTOR) */ - abort_context = C_RESET_GATE_VECTOR; - cpu_perform_gate(0, ((uint32) isc) << 3); - - /* Finish push of old PC and PSW */ - R[NUM_SP] += 8; - abort_context = C_NONE; -} - -static SIM_INLINE void cpu_perform_gate(uint32 index1, uint32 index2) -{ - uint32 gate_l2, new_psw; - - abort_context = C_NORMAL_GATE_VECTOR; - cpu_km = TRUE; - - gate_l2 = read_w(index1, ACC_AF, BUS_CPU) + index2; - - /* Get new PSW from second-level table */ - new_psw = read_w(gate_l2, ACC_AF, BUS_CPU); - - /* Clear state in PSW */ - new_psw &= ~(PSW_PM_MASK|PSW_IPL_MASK|PSW_R_MASK| - PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); - - /* Set PM in new PSW */ - new_psw |= (R[NUM_PSW] & PSW_CM_MASK) >> 2; /* PM */ - new_psw |= (R[NUM_PSW] & PSW_IPL_MASK); /* IPL */ - new_psw |= (R[NUM_PSW] & PSW_R_MASK); /* R */ - - /* Set new PSW ISC, TM, and ET to 7, 1, 3 */ - new_psw |= (7 << PSW_ISC); /* ISC */ - new_psw |= (1 << PSW_TM); /* TM */ - new_psw |= (3 << PSW_ET); /* ET */ - - R[NUM_PC] = read_w(gate_l2 + 4, ACC_AF, BUS_CPU); - R[NUM_PSW] = new_psw; - - cpu_km = FALSE; - abort_context = C_NONE; -} - -/* - * TODO: Setting 'data' to the effective address is bogus. We're only - * doing it because we want to get the address when we trace the - * instructions using "SHOW CPU HISTORY". We should just put - * effective_address as a field in the operand struct and make - * cpu_show_hist smarter. - */ -static uint32 cpu_effective_address(operand *op) -{ -#if defined(REV3) - uint32 tmp; -#endif - - /* Register Deferred */ - if (op->mode == 5 && op->reg != 11) { - return R[op->reg]; - } - - /* Absolute */ - if (op->mode == 7 && op->reg == 15) { - return op->embedded.w; - } - - /* Absolute Deferred */ - if (op->mode == 14 && op->reg == 15) { - /* May cause exception */ - return read_w(op->embedded.w, ACC_AF, BUS_CPU); - } - - /* FP Short Offset */ - if (op->mode == 6 && op->reg != 15) { - return R[NUM_FP] + sign_extend_b(op->embedded.b); - } - - /* AP Short Offset */ - if (op->mode == 7 && op->reg != 15) { - return R[NUM_AP] + sign_extend_b(op->embedded.b); - } - - /* Word Displacement */ - if (op->mode == 8) { - return R[op->reg] + op->embedded.w; - } - - /* Word Displacement Deferred */ - if (op->mode == 9) { - return read_w(R[op->reg] + op->embedded.w, ACC_AF, BUS_CPU); - } - - /* Halfword Displacement */ - if (op->mode == 10) { - return R[op->reg] + sign_extend_h(op->embedded.h); - } - - /* Halfword Displacement Deferred */ - if (op->mode == 11) { - return read_w(R[op->reg] + sign_extend_h(op->embedded.h), ACC_AF, BUS_CPU); - } - - /* Byte Displacement */ - if (op->mode == 12) { - return R[op->reg] + sign_extend_b(op->embedded.b); - } - - /* Byte Displacement Deferred */ - if (op->mode == 13) { - return read_w(R[op->reg] + sign_extend_b(op->embedded.b), ACC_AF, BUS_CPU); - } - -#if defined(REV3) - /* Auto pre-decrement */ - if (op->mode == 0x10) { - switch(op_type(op)) { - case BT: - case SB: - R[op->reg] -= 1; - break; - case HW: - case UH: - R[op->reg] -= 2; - break; - case WD: - case UW: - R[op->reg] -= 4; - break; - } - return R[op->reg]; - } - - /* Auto post-decrement */ - if (op->mode == 0x12) { - tmp = R[op->reg]; - switch(op_type(op)) { - case BT: - case SB: - R[op->reg] -= 1; - break; - case HW: - case UH: - R[op->reg] -= 2; - break; - case WD: - case UW: - R[op->reg] -= 4; - break; - } - return tmp; - } - - /* Auto pre-increment */ - if (op->mode == 0x14) { - switch(op_type(op)) { - case BT: - case SB: - R[op->reg] += 1; - break; - case HW: - case UH: - R[op->reg] += 2; - break; - case WD: - case UW: - R[op->reg] += 4; - break; - } - return R[op->reg]; - } - - /* Auto post-increment */ - if (op->mode == 0x16) { - tmp = R[op->reg]; - switch(op_type(op)) { - case BT: - case SB: - R[op->reg] += 1; - break; - case HW: - case UH: - R[op->reg] += 2; - break; - case WD: - case UW: - R[op->reg] += 4; - break; - } - return tmp; - } - - /* Indexed with byte displacement */ - if (op->mode == 0xab) { - tmp = sign_extend_b(op->embedded.b); - tmp += R[op->reg]; - tmp += R[op->reg2]; - return tmp; - } - - /* Indexed with halfword displacement */ - if (op->mode == 0xbb) { - tmp = sign_extend_h(op->embedded.h); - tmp += R[op->reg]; - tmp += R[op->reg2]; - return tmp; - } - - /* Indexed with scaling */ - if (op->mode == 0xdb) { - switch(op_type(op)) { - case BT: - case SB: - tmp = R[op->reg]; - break; - case HW: - case UH: - tmp = R[op->reg] * 2; - break; - case WD: - case UW: - tmp = R[op->reg] * 4; - break; - default: - /* We should never reach this */ - tmp = 0; - break; - } - - return tmp + R[op->reg2]; - } -#endif - - if (cpu_unit.flags & UNIT_OPBRK) { - stop_reason = STOP_OPCODE; - } - - return 0; -} - -/* - * Read and Write routines for operands. - * - * The rules for dealing with the type (signed/unsigned, - * byte/halfword/word) of operands are fairly complex. - * - * 1. The expanded operand mode does not affect the treatment of - * Literal Mode operands. All literals are signed. - * - * 2. The expanded operand mode does not affect the length of - * Immediate Mode operands, but does affect whether they are signed - * or unsigned. - * - * 3. When using expanded-mode operands, the new type remains in - * effect for the operands that folow in the instruction unless - * another expanded operand mode overrides it. (This rule in - * particular is managed by decode_instruction()) - * - * 4. The expanded operand mode is illegal with coprocessor instructions - * and CALL, SAVE, RESTORE, SWAP INTERLOCKED, PUSAHW, PUSHAW, POPW, - * and JSB. (Illegal Operand Fault) - * - * 5. When writing a byte, the Negative (N) flag is set based on the - * high bit of the data type being written, regardless of the SIGN - * of the extended datatype. e.g.: {ubyte} and {sbyte} both check - * for bit 7, {uhalf} and {shalf} both check for bit 15, and - * {uword} and {sword} both check for bit 31. - * - * 6. For instructions with a signed destination, V is set if the sign - * bit of the output value is different from any truncated bit of - * the result. For instructions with an unsigned destination, V is - * set if any truncated bit is 1. - */ - - -/* - * Read the data referenced by an operand. Performs sign or zero - * extension as required by the read width and operand type, then - * returns the read value. - * - * "All operations are performed only on 32-bit quantities even though - * an instruction may specify a byte or halfword operand. The WE - * 32100 Microprocessor reads in the correct number of bits for the - * operand and extends the data automatically to 32 bits. It uses - * sign extension when reading signed data or halfwords and zero - * extension when reading unsigned data or bytes (or bit fields that - * contain less than 32 bits). The data type of the source operand - * determines how many bits are fetched and what type of extension is - * applied. Bytes are treated as unsigned, while halfwords and words - * are considered signed. The type of extension applied can be - * changed using the expanded-operand type mode as described in 3.4.5 - * Expanded-Operand Type Mode. For sign extension, the value of the - * MSB or sign bit of the data fills the high-order bits to form a - * 32-bit value. In zero extension, zeros fill the high order bits. - * The microprocessor automatically extends a byte or halfword to 32 - * bits before performing an operation. Figure 3-3 illustrates sign - * and zero extension. An arithmetic, logical, data transfer, or bit - * field operation always yields an intermediate result that is 32 - * bits in length. If the result is to be stored in a register, the - * processor writes all 32 bits to that register. The processor - * automatically strips any surplus high-order bits from a result - * when writing bytes or halfwords to memory." -- "WE 32100 - * Microprocessor Information Manual", Section 3.1.1 - * - */ -static uint32 cpu_read_op(operand * op) -{ - uint32 eff; - uint32 data; - - /* Register */ - if (op->mode == 4 && op->reg != 15) { - switch (op_type(op)) { - case WD: - case UW: - data = R[op->reg]; - break; - case HW: - data = sign_extend_h(R[op->reg] & HALF_MASK); - break; - case UH: - data = R[op->reg] & HALF_MASK; - break; - case BT: - data = R[op->reg] & BYTE_MASK; - break; - case SB: - data = sign_extend_b(R[op->reg] & BYTE_MASK); - break; - default: - sim_debug(EXECUTE_MSG, &cpu_dev, - "[%08x] cpu_read_op: unknown op type (1): %d\n", - R[NUM_PC], op_type(op)); - stop_reason = STOP_ERR; - data = 0; - break; - } - - op->data = data; - return data; - } - - /* Literal */ - if (op->mode < 4 || op->mode == 15) { - /* Both positive and negative literals are _always_ treated as - signed bytes, and they are _always_ sign extended. They - simply ignore expanded datatypes. */ - data = sign_extend_b(op->embedded.b); - op->data = data; - return data; - } - - /* Immediate */ - if (op->reg == 15 && - (op->mode == 4 || op->mode == 5 || op->mode == 6)) { - switch (op->mode) { - case 4: /* Word Immediate */ - data = op->embedded.w; - op->data = data; - return data; - case 5: /* Halfword Immediate */ - data = sign_extend_h(op->embedded.h); - op->data = data; - return data; - case 6: /* Byte Immedaite */ - data = sign_extend_b(op->embedded.b); - op->data = data; - return data; - } - } - - /* At this point, we'll need to find an effective address */ - eff = cpu_effective_address(op); - - switch (op_type(op)) { - case WD: /* Signed Word */ - case UW: /* Unsigned Word */ - data = read_w(eff, ACC_OF, BUS_CPU); - op->data = data; - return data; - case HW: /* Signed Halfword */ - data = sign_extend_h(read_h(eff, ACC_OF, BUS_CPU)); - op->data = data; - return data; - case UH: /* Unsigned Halfword */ - data = read_h(eff, ACC_OF, BUS_CPU); - op->data = data; - return data; - case SB: /* Signed Byte */ - data = sign_extend_b(read_b(eff, ACC_OF, BUS_CPU)); - op->data = data; - return data; - case BT: /* Unsigned Byte */ - data = read_b(eff, ACC_OF, BUS_CPU); - op->data = data; - return data; - default: - sim_debug(EXECUTE_MSG, &cpu_dev, - "[%08x] cpu_read_op: unknown op type (2): %d\n", - R[NUM_PC], op_type(op)); - stop_reason = STOP_ERR; - return 0; - } -} - - -static void cpu_write_op(operand * op, t_uint64 val) -{ - uint32 eff; - op->data = (uint32) val; - - /* Writing to a register. */ - if (op->mode == 4 && op->reg != 15) { - if (PRIVREG(op->reg) && cpu_execution_level() != EX_LVL_KERN) { - cpu_abort(NORMAL_EXCEPTION, PRIVILEGED_REGISTER); - return; - } - - /* Registers always get the full 32-bits written, EXCEPT for - * the PSW, which has some Read-Only fields. */ - if (op->reg == NUM_PSW) { - WRITE_PSW((uint32) val); - } else { - R[op->reg] = (uint32) val; - } - - return; - } - - /* Literal mode is not legal. */ - if (op->mode < 4 || op->mode == 15) { - cpu_abort(NORMAL_EXCEPTION, INVALID_DESCRIPTOR); - return; - } - - /* Immediate mode is not legal. */ - if (op->reg == 15 && - (op->mode == 4 || op->mode == 5 || op->mode == 6)) { - cpu_abort(NORMAL_EXCEPTION, INVALID_DESCRIPTOR); - return; - } - - eff = cpu_effective_address(op); - - switch (op_type(op)) { - case UW: - case WD: - write_w(eff, (uint32) val, BUS_CPU); - break; - case HW: - case UH: - write_h(eff, val & HALF_MASK, BUS_CPU); - break; - case SB: - case BT: - write_b(eff, val & BYTE_MASK, BUS_CPU); - break; - default: - sim_debug(EXECUTE_MSG, &cpu_dev, - "[%08x] cpu_read_op: unknown op type (3): %d\n", - R[NUM_PC], op_type(op)); - stop_reason = STOP_ERR; - break; - } -} - -/* - * Returns the correct datatype for an operand -- either extended type - * or default type. - */ -static SIM_INLINE int8 op_type(operand *op) { - if (op->etype > -1) { - return op->etype; - } else { - return op->dtype; - } -} - -static SIM_INLINE t_bool op_signed(operand *op) { - return (op_type(op) == WD || op_type(op) == HW || op_type(op) == SB); -} - -static SIM_INLINE uint32 sign_extend_b(uint8 val) -{ - if (val & 0x80) - return ((uint32) val) | 0xffffff00; - return (uint32) val; -} - -static SIM_INLINE uint32 sign_extend_h(uint16 val) -{ - if (val & 0x8000) - return ((uint32) val) | 0xffff0000; - return (uint32) val; -} - -/* - * Returns the current CPU execution level. - */ -static SIM_INLINE uint8 cpu_execution_level() -{ - return (R[NUM_PSW] & PSW_CM_MASK) >> PSW_CM; -} - -static SIM_INLINE t_bool cpu_z_flag() -{ - return (R[NUM_PSW] & PSW_Z_MASK) != 0; -} - -static SIM_INLINE t_bool cpu_n_flag() -{ - return (R[NUM_PSW] & PSW_N_MASK) != 0; -} - -static SIM_INLINE t_bool cpu_c_flag() -{ - return (R[NUM_PSW] & PSW_C_MASK) != 0; -} - -static SIM_INLINE t_bool cpu_v_flag() -{ - return (R[NUM_PSW] & PSW_V_MASK) != 0; -} - -#if defined(REV3) -static SIM_INLINE t_bool cpu_x_flag() -{ - return (R[NUM_PSW] & PSW_X_MASK) != 0; -} -#endif - -static SIM_INLINE void cpu_set_z_flag(t_bool val) -{ - if (val) { - R[NUM_PSW] |= PSW_Z_MASK; - } else { - R[NUM_PSW] &= ~PSW_Z_MASK; - } -} - -static SIM_INLINE void cpu_set_n_flag(t_bool val) -{ - if (val) { - R[NUM_PSW] |= PSW_N_MASK; - } else { - R[NUM_PSW] &= ~PSW_N_MASK; - } -} - -static SIM_INLINE void cpu_set_c_flag(t_bool val) -{ - if (val) { - R[NUM_PSW] |= PSW_C_MASK; - } else { - R[NUM_PSW] &= ~PSW_C_MASK; - } -} - -#if defined(REV3) -static SIM_INLINE void cpu_set_x_flag(t_bool val) -{ - if (val) { - R[NUM_PSW] |= PSW_X_MASK; - } else { - R[NUM_PSW] &= ~PSW_X_MASK; - } -} -#endif - -static SIM_INLINE void cpu_set_v_flag_op(t_uint64 val, operand *op) -{ - switch(op_type(op)) { - case WD: - case UW: - cpu_set_v_flag(0); - break; - case HW: - case UH: - cpu_set_v_flag(val > HALF_MASK); - break; - case BT: - case SB: - default: - cpu_set_v_flag(val > BYTE_MASK); - break; - } -} - -static SIM_INLINE void cpu_set_v_flag(t_bool val) -{ - if (val) { - R[NUM_PSW] |= PSW_V_MASK; - if (R[NUM_PSW] & PSW_OE_MASK) { - cpu_abort(NORMAL_EXCEPTION, INTEGER_OVERFLOW); - } - } else { - R[NUM_PSW] &= ~PSW_V_MASK; - } -} - -static void cpu_set_nz_flags(t_uint64 data, operand *dst) -{ - int8 type = op_type(dst); - - switch (type) { - case WD: - case UW: - cpu_set_n_flag(!!(WD_MSB & data)); - cpu_set_z_flag((data & WORD_MASK) == 0); - break; - case HW: - case UH: - cpu_set_n_flag(HW_MSB & data); - cpu_set_z_flag((data & HALF_MASK) == 0); - break; - case BT: - case SB: - cpu_set_n_flag(BT_MSB & data); - cpu_set_z_flag((data & BYTE_MASK) == 0); - break; - } -} - -static SIM_INLINE void cpu_push_word(uint32 val) -{ - write_w(R[NUM_SP], val, BUS_CPU); - R[NUM_SP] += 4; -} - -static SIM_INLINE uint32 cpu_pop_word() -{ - uint32 result; - /* We always read fromthe stack first BEFORE decrementing, - in case this causes a fault. */ - result = read_w(R[NUM_SP] - 4, ACC_AF, BUS_CPU); - R[NUM_SP] -= 4; - return result; -} - -static SIM_INLINE void irq_push_word(uint32 val) -{ - write_w(R[NUM_ISP], val, BUS_CPU); - R[NUM_ISP] += 4; -} - -static SIM_INLINE uint32 irq_pop_word() -{ - R[NUM_ISP] -= 4; - return read_w(R[NUM_ISP], ACC_AF, BUS_CPU); -} - -static SIM_INLINE t_bool op_is_psw(operand *op) -{ - return (op->mode == 4 && op->reg == NUM_PSW); -} - -static SIM_INLINE void sub(t_uint64 a, t_uint64 b, operand *dst) -{ - t_uint64 result; - - result = a - b; - - cpu_write_op(dst, result); - - cpu_set_nz_flags(result, dst); - cpu_set_c_flag((uint32)b > (uint32)a); - cpu_set_v_flag_op(result, dst); -} - -static SIM_INLINE void add(t_uint64 a, t_uint64 b, operand *dst) -{ - t_uint64 result; - - result = a + b; - - cpu_write_op(dst, result); - - cpu_set_nz_flags(result, dst); - - switch(op_type(dst)) { - case WD: - cpu_set_c_flag(result > WORD_MASK); - cpu_set_v_flag(((a ^ ~b) & (a ^ result)) & WD_MSB); - break; - case UW: - cpu_set_c_flag(result > WORD_MASK); - cpu_set_v_flag(result > WORD_MASK); - break; - case HW: - cpu_set_c_flag(result > HALF_MASK); - cpu_set_v_flag(((a ^ ~b) & (a ^ result)) & HW_MSB); - break; - case UH: - cpu_set_c_flag(result > HALF_MASK); - cpu_set_v_flag(result > HALF_MASK); - break; - case BT: - cpu_set_c_flag(result > BYTE_MASK); - cpu_set_v_flag(result > BYTE_MASK); - break; - case SB: - cpu_set_c_flag(result > BYTE_MASK); - cpu_set_v_flag(((a ^ ~b) & (a ^ result)) & BT_MSB); - break; - } -} - -#if defined(REV3) -/* - * Return the packed BCD byte result of adding two packed BCD input - * bytes. This will set the C and X carry flags appropraitely if there - * is a carry. - */ -static SIM_INLINE uint8 add_bcd(uint8 packed_a, uint8 packed_b) -{ - uint16 l, h, result; - - l = (packed_a & 0x0f) + (packed_b & 0x0f) + (cpu_x_flag() ? 1 : 0); - if ((l & 0xff) > 9) { - l += 6; - } - - h = ((packed_a >> 4) & 0x0f) + ((packed_b >> 4) & 0x0f) + (l > 15 ? 1 : 0); - if ((h & 0xff) > 9) { - h += 6; - } - - result = ((l & 0x0f) | (h << 4)) & 0xff; - - cpu_set_c_flag(h > 15); - cpu_set_x_flag(h > 15); - cpu_set_z_flag(result == 0); - cpu_set_n_flag(0); - cpu_set_v_flag(0); - - return (uint8)result; -} - -/* - * Return the packed BCD byte result of subtracting two packed BCD - * input bytes. This will set the C and X carry flags appropraitely if - * there is a carry. - */ -static SIM_INLINE uint8 sub_bcd(uint8 packed_a, uint8 packed_b) -{ - uint16 l, h, result; - - l = (packed_a & 0x0f) - (packed_b & 0x0f) - (cpu_x_flag() ? 1 : 0); - if ((l & 0x10) != 0) { - l -= 6; - } - - h = ((packed_a >> 4) & 0x0f) - ((packed_b >> 4) & 0x0f) - ((l & 0x10) != 0 ? 1 : 0); - if ((h & 0x10) != 0) { - h -= 6; - } - - result = ((l & 0x0f)|(h << 4)) & 0xff; - - cpu_set_c_flag(h > 15); - cpu_set_x_flag(h > 15); - cpu_set_z_flag(result == 0); - cpu_set_n_flag(0); - cpu_set_v_flag(0); - - return (uint8)result; -} -#endif - -/* - * Set PSW's ET and ISC fields, and store global exception or fault - * state appropriately. - */ -void cpu_abort(uint8 et, uint8 isc) -{ - /* We don't trap Integer Overflow if the OE bit is not set */ - if ((R[NUM_PSW] & PSW_OE_MASK) == 0 && isc == INTEGER_OVERFLOW) { - return; - } - - R[NUM_PSW] &= ~(PSW_ET_MASK); /* Clear ET */ - R[NUM_PSW] &= ~(PSW_ISC_MASK); /* Clear ISC */ - R[NUM_PSW] |= et; /* Set ET */ - R[NUM_PSW] |= (uint32) (isc << PSW_ISC); /* Set ISC */ - - longjmp(save_env, ABORT_EXC); -} - -CONST char *cpu_description(DEVICE *dptr) -{ -#if defined(REV3) - return "3B2/700 CPU (WE 32200)"; -#else - return "3B2/400 CPU (WE 32100)"; -#endif -} - -t_stat cpu_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) -{ -#if defined(REV3) - fprintf(st, "3B2/700 CPU Help\n\n"); - fprintf(st, "The 3B2/700 CPU simulates a WE 32200 at 22 MHz.\n\n"); -#else - fprintf(st, "3B2/400 CPU Help\n\n"); - fprintf(st, "The 3B2/400 CPU simulates a WE 32100 at 10 MHz.\n\n"); -#endif - - fprint_set_help(st, dptr); - fprint_show_help(st, dptr); - fprint_reg_help(st, dptr); - -#if defined(REV3) - fprintf(st, "\nAdditional documentation for the 3B2/700 Simulator is available on the web:\n\n"); -#else - fprintf(st, "\nAdditional documentation for the 3B2/400 Simulator is available on the web:\n\n"); -#endif - fprintf(st, " https://loomcom.com/3b2/emulator.html\n\n"); - - return SCPE_OK; -} +/* 3b2_cpu.c: WE32100 and WE32200 CPU + + Copyright (c) 2017-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +/* + * This is an implementation of the WE 32100 and WE 32200 CPUs, used + * in the Rev 2 (e.g. 3B2/400) and Rev 3 (e.g. 3B2/700) AT&T 3B2 + * computers, respectively. + * + * The WE 32K series of microprocessors were fully 32-bit, general + * purpose CISC architecture microprocessors with features designed to + * support UNIX as a primary operating system. In addition to the CPU, + * other members of the WE 32K chipset included: + * + * - WE 32101 and WE 32201 Memory Management Units + * - WE 32106 and WE 32206 Math Accelerator Units + * + * The architecture is fully described in the following books: + * + * - "WE 32100 Microprocessor Information Manual" (AT&T, 1985) + * - "WE 32200 Microprocessor Information Manual" (AT&T, 1988) + * + */ + +#include "3b2_cpu.h" + +#if defined(REV3) +#include "3b2_if.h" +#else +#include "3b2_id.h" +#endif /* defined(REV3) */ + +#include "3b2_csr.h" +#include "3b2_dmac.h" +#include "3b2_io.h" +#include "3b2_iu.h" +#include "3b2_mau.h" +#include "3b2_mem.h" +#include "3b2_mmu.h" +#include "3b2_stddev.h" +#include "3b2_timer.h" + +/* Up to 128KB ROM allowed */ +#define MAX_SUB_RETURN_SKIP 9 + +/* Kernel-privileged registers on write */ +#if defined(REV3) +#define PRIVREG(V) ((V) == NUM_PSW || (V) == NUM_PCBP || \ + (V) == NUM_ISP || (V) == NUM_PC || (V) >= 24) +#else +#define PRIVREG(V) ((V) == NUM_PSW || (V) == NUM_PCBP || \ + (V) == NUM_ISP || (V) == NUM_PC) +#endif + +/* Static function declarations */ +static uint32 cpu_effective_address(operand * op); +static uint32 cpu_read_op(operand * op); +static void cpu_write_op(operand * op, t_uint64 val); +static void cpu_set_nz_flags(t_uint64 data, operand * op); +static SIM_INLINE void cpu_on_normal_exception(uint8 isc); +static SIM_INLINE void cpu_on_stack_exception(uint8 isc); +static SIM_INLINE void cpu_on_process_exception(uint8 isc); +static SIM_INLINE void cpu_on_reset_exception(uint8 isc); +static SIM_INLINE void cpu_perform_gate(uint32 index1, uint32 index2); +static SIM_INLINE void clear_instruction(instr *inst); +static SIM_INLINE int8 op_type(operand *op); +static SIM_INLINE t_bool op_signed(operand *op); +static SIM_INLINE uint32 sign_extend_b(uint8 val); +static SIM_INLINE uint32 sign_extend_h(uint16 val); +static SIM_INLINE t_bool cpu_z_flag(); +static SIM_INLINE t_bool cpu_n_flag(); +static SIM_INLINE t_bool cpu_c_flag(); +static SIM_INLINE t_bool cpu_v_flag(); +static SIM_INLINE void cpu_set_z_flag(t_bool val); +static SIM_INLINE void cpu_set_n_flag(t_bool val); +static SIM_INLINE void cpu_set_c_flag(t_bool val); +static SIM_INLINE void cpu_set_v_flag(t_bool val); +static SIM_INLINE void cpu_set_v_flag_op(t_uint64 val, operand *op); +static SIM_INLINE uint8 cpu_execution_level(); +static SIM_INLINE void cpu_push_word(uint32 val); +static SIM_INLINE uint32 cpu_pop_word(); +static SIM_INLINE void irq_push_word(uint32 val); +static SIM_INLINE uint32 irq_pop_word(); +static SIM_INLINE void cpu_context_switch_1(uint32 pcbp); +static SIM_INLINE void cpu_context_switch_2(uint32 pcbp); +static SIM_INLINE void cpu_context_switch_3(uint32 pcbp); +static SIM_INLINE t_bool op_is_psw(operand *op); +static SIM_INLINE void add(t_uint64 a, t_uint64 b, operand *dst); +static SIM_INLINE void sub(t_uint64 a, t_uint64 b, operand *dst); +#if defined(REV3) +static SIM_INLINE uint8 add_bcd(uint8 a, uint8 b); +static SIM_INLINE uint8 sub_bcd(uint8 a, uint8 b); +#endif + +/* RO memory. */ +uint8 *ROM = NULL; + +/* Main memory. */ +uint8 *RAM = NULL; + +/* Save environment for setjmp/longjmp */ +jmp_buf save_env; +volatile uint32 abort_context; + +/* Pointer to the last decoded instruction */ +instr *cpu_instr; + +/* The instruction to use if there is no history storage */ +instr inst; + +/* Circular buffer of instructions */ +instr *INST = NULL; +uint32 cpu_hist_size = 0; +uint32 cpu_hist_p = 0; + +t_bool cpu_in_wait = FALSE; + +volatile size_t cpu_exception_stack_depth = 0; +volatile int32 stop_reason; +volatile uint32 abort_reason; + +/* Register data */ +uint32 R[NUM_REGISTERS]; + +/* Other global CPU state */ + +t_bool rom_loaded = FALSE; /* True if ROM has been loaded, false otherwise */ + +/* Interrupt request bitfield */ +/* Note: Only the lowest 8 bits are used by Rev 2, and only the lowest + 12 bits are used by Rev 3 */ +uint16 sbd_int_req = 0; /* Currently set interrupt sources */ +uint8 int_map[INT_MAP_LEN]; /* Map of interrupt sources to highest + priority IPL */ +t_bool cpu_nmi = FALSE; /* If set, there has been an NMI */ +int32 pc_incr = 0; /* Length (in bytes) of instruction + currently being executed */ +t_bool cpu_ex_halt = FALSE; /* Flag to halt on exceptions / traps */ +t_bool cpu_km = FALSE; /* If true, kernel mode has been forced + for memory access */ +uint16 cpu_int_ack; /* The most recently acknowledged + interrupt */ + +CTAB sys_cmd[] = { + { "BOOT", &sys_boot, RU_BOOT, + "bo{ot} boot simulator\n", NULL, &run_cmd_message }, + { NULL } +}; + +BITFIELD psw_bits[] = { + BITFFMT(ET,2,%d), /* Exception Type */ + BIT(TM), /* Trace Mask */ + BITFFMT(ISC,4,%d), /* Internal State Code */ + BIT(I), /* Register Initial Context (I) */ + BIT(R), /* Register Initial Context (R) */ + BITFFMT(PM,2,%d), /* Previous Execution Level */ + BITFFMT(CM,2,%d), /* Current Execution Level */ + BITFFMT(IPL,4,%d), /* Interrupt Priority Level */ + BIT(TE), /* Trace Enable */ + BIT(C), /* Carry */ + BIT(V), /* Overflow */ + BIT(Z), /* Zero */ + BIT(N), /* Negative */ + BIT(OE), /* Enable Overflow Trap */ + BIT(CD), /* Cache Disable */ + BIT(QIE), /* Quick-Interrupt Enable */ + BIT(CFD), /* Cache Flush Disable */ +#if defined(REV3) + BIT(X), /* Extend Carry / Borrow */ + BIT(AR), /* Additional Register Save */ + BIT(EXUC), /* Exception/User Call Option */ + BIT(EA), /* Enable Arbitrary Alignment */ + BITNCF(2), /* Unused */ +#else + BITNCF(6), /* Unused */ +#endif + ENDBITS +}; + +BITFIELD sbd_int_req_bits[] = { +#if defined(REV3) + BIT(CLOK), /* UNIX Interval Timer */ + BIT(PWRD), /* Power Down Request */ + BIT(BUSO), /* UBUS or BUB Operational Interrupt */ + BIT(SBER), /* Single Bit Memory Error */ + BIT(MBER), /* Multiple Bit Memory Error */ + BIT(BRXF), /* UBUS, BUB, EIO Bus Received Fail */ + BIT(BTMO), /* UBUS Timer Timeout */ + BIT(UDMA), /* UART DMA Complete */ + BIT(UART), /* UART Interrupt */ + BIT(FDMA), /* Floppy DMA Complete */ + BIT(FLOP), /* Floppy Interrupt */ + BIT(PIR9), /* PIR 9 */ + BIT(PIR8), /* PIR 8 */ + BITNCF(3), /* Unused */ + ENDBITS +#else + BIT(SERR), /* System Error */ + BIT(CLOK), /* UNIX Interval Timer */ + BIT(DMAC), /* DMA Complete */ + BIT(UART), /* UART */ + BIT(DISK), /* Integrated Disk Drive (Winchester) */ + BIT(FLOP), /* Integrated Floppy Drive */ + BIT(PIR9), /* PIR 9 */ + BIT(PIR8), /* PIR 8 */ + BITNCF(8), /* Unused */ + ENDBITS +#endif +}; + +/* Registers. */ +REG cpu_reg[] = { + { HRDATAD (R0, R[0], 32, "General purpose register 0") }, + { HRDATAD (R1, R[1], 32, "General purpose register 1") }, + { HRDATAD (R2, R[2], 32, "General purpose register 2") }, + { HRDATAD (R3, R[3], 32, "General purpose register 3") }, + { HRDATAD (R4, R[4], 32, "General purpose register 4") }, + { HRDATAD (R5, R[5], 32, "General purpose register 5") }, + { HRDATAD (R6, R[6], 32, "General purpose register 6") }, + { HRDATAD (R7, R[7], 32, "General purpose register 7") }, + { HRDATAD (R8, R[8], 32, "General purpose register 8") }, + { HRDATAD (FP, R[NUM_FP], 32, "Frame Pointer") }, + { HRDATAD (AP, R[NUM_AP], 32, "Argument Pointer") }, + { HRDATADF (PSW, R[NUM_PSW], 32, "Processor Status Word", psw_bits) }, + { HRDATAD (SP, R[NUM_SP], 32, "Stack Pointer") }, + { HRDATAD (PCBP, R[NUM_PCBP], 32, "Process Control Block Pointer") }, + { HRDATAD (ISP, R[NUM_ISP], 32, "Interrupt Stack Pointer") }, + { HRDATAD (PC, R[NUM_PC], 32, "Program Counter") }, +#if defined(REV3) + { HRDATAD (R16, R[16], 32, "General purpose register 16")}, + { HRDATAD (R17, R[17], 32, "General purpose register 17")}, + { HRDATAD (R18, R[18], 32, "General purpose register 18")}, + { HRDATAD (R19, R[19], 32, "General purpose register 19")}, + { HRDATAD (R20, R[20], 32, "General purpose register 20")}, + { HRDATAD (R21, R[21], 32, "General purpose register 21")}, + { HRDATAD (R22, R[22], 32, "General purpose register 22")}, + { HRDATAD (R23, R[23], 32, "General purpose register 23")}, + { HRDATAD (R24, R[24], 32, "Privileged register 24")}, + { HRDATAD (R25, R[25], 32, "Privileged register 25")}, + { HRDATAD (R26, R[26], 32, "Privileged register 26")}, + { HRDATAD (R27, R[27], 32, "Privileged register 27")}, + { HRDATAD (R28, R[28], 32, "Privileged register 28")}, + { HRDATAD (R29, R[29], 32, "Privileged register 29")}, + { HRDATAD (R30, R[30], 32, "Privileged register 30")}, + { HRDATAD (R31, R[31], 32, "Privileged register 31")}, +#endif + { HRDATADF (SBD_INT, sbd_int_req, 16, "Interrupt Requests", sbd_int_req_bits) }, + { NULL } +}; + +static DEBTAB cpu_deb_tab[] = { + { "READ", READ_MSG, "Memory read activity" }, + { "WRITE", WRITE_MSG, "Memory write activity" }, + { "DECODE", DECODE_MSG, "Instruction decode" }, + { "EXECUTE", EXECUTE_MSG, "Instruction execute" }, + { "INIT", INIT_MSG, "Initialization" }, + { "IRQ", IRQ_MSG, "Interrupt Handling" }, + { "IO", IO_DBG, "I/O Dispatch" }, + { "CIO", CIO_DBG, "Common I/O Interface" }, + { "TRACE", TRACE_DBG, "Call Trace" }, + { "ERROR", ERR_MSG, "Error" }, + { NULL, 0, NULL } +}; + +UNIT cpu_unit = { + UDATA (NULL, UNIT_FIX|UNIT_BINK|UNIT_IDLE, DEFMEMSIZE) +}; + +/* + * The following commands deposit a small calibration program into + * mainstore at 0x2000000 and then set the program counter to the + * start address. Simulator calibration will execute this program to + * establish a baseline execution rate. + * + * Program: + * 84 01 46 MOVW &0x1,%r6 + * 84 46 47 MOVW %r6,%r7 + * 84 47 48 MOVW %r7,%r8 + * 90 48 INCW %r8 + * 28 48 TSTW %r8 + * 4f 0b BLEB 0xb + * e4 07 48 40 MODW3 &0x7,%r8,%r0 + * 84 40 47 MOVW %r0,%r7 + * 7b 0b BRB 0xb + * 8c 48 40 MNEGW %r8,%r0 + * a4 07 40 MODW2 &0x7,%r0 + * 84 40 47 MOVW %r0,%r7 + * e8 47 48 40 MULW3 %r7,%r8,%r0 + * 9c 07 40 ADDW2 &0x7,%r0 + * 84 40 46 MOVW %r0,%r6 + * 28 48 TSTW %r8 + * 4f 05 BLEB 0x5 + * a8 03 47 MULW2 &0x3,%r7 + * d0 01 46 46 LLSW3 &0x1,%r6,%r6 + * 28 46 TSTW %r6 + * 4f 09 BLEB 0x9 + * ec 46 47 40 DIVW3 %r6,%r7,%r0 + * 84 40 48 MOVW %r0,%r8 + * d4 01 47 47 LRSW3 &0x1,%r7,%r7 + * 3c 48 47 CMPW %r8,%r7 + * 4f 05 BLEB 0x5 + * bc 48 47 SUBW2 %r8,%r7 + * 7b bc BRB -0x44 + */ +static const char *att3b2_clock_precalibrate_commands[] = { + "-v 2000000 84014684", + "-v 2000004 46478447", + "-v 2000008 48904828", + "-v 200000c 484f0be4", + "-v 2000010 07484084", + "-v 2000014 40477b0b", + "-v 2000018 8c4840a4", + "-v 200001c 07408440", + "-v 2000020 47e84748", + "-v 2000024 409c0740", + "-v 2000028 84404628", + "-v 200002c 484f05a8", + "-v 2000030 0347d001", + "-v 2000034 46462846", + "-v 2000038 4f09ec46", + "-v 200003c 47408440", + "-v 2000040 48d40147", + "-v 2000044 473c4847", + "-v 2000048 4f05bc48", + "-v 200004c 477bbc00", + "PC 2000000", + NULL +}; + +MTAB cpu_mod[] = { +#if defined(REV2) + { UNIT_MSIZE, (1u << 20), NULL, "1M", + &cpu_set_size, NULL, NULL, "Set Memory to 1M bytes" }, + { UNIT_MSIZE, (1u << 21), NULL, "2M", + &cpu_set_size, NULL, NULL, "Set Memory to 2M bytes" }, + { UNIT_MSIZE, (1u << 22), NULL, "4M", + &cpu_set_size, NULL, NULL, "Set Memory to 4M bytes" }, +#endif +#if defined(REV3) + { UNIT_MSIZE, (1u << 23), NULL, "8M", + &cpu_set_size, NULL, NULL, "Set Memory to 8M bytes" }, + { UNIT_MSIZE, (1u << 24), NULL, "16M", + &cpu_set_size, NULL, NULL, "Set Memory to 16M bytes" }, + { UNIT_MSIZE, (1u << 25), NULL, "32M", + &cpu_set_size, NULL, NULL, "Set Memory to 32M bytes" }, + { UNIT_MSIZE, (1u << 26), NULL, "64M", + &cpu_set_size, NULL, NULL, "Set Memory to 64M bytes" }, +#endif + { MTAB_XTD|MTAB_VDV|MTAB_NMO|MTAB_SHP, 0, "HISTORY", "HISTORY", + &cpu_set_hist, &cpu_show_hist, NULL, "Displays instruction history" }, + { MTAB_XTD|MTAB_VDV|MTAB_NMO|MTAB_SHP, 0, "VIRTUAL", NULL, + NULL, &cpu_show_virt, NULL, "Show translation for virtual address" }, + { MTAB_XTD|MTAB_VDV|MTAB_NMO|MTAB_SHP, 0, "STACK", NULL, + NULL, &cpu_show_stack, NULL, "Display the current stack with optional depth" }, + { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "CIO", NULL, + NULL, &cpu_show_cio, NULL, "Display backplane configuration" }, + { MTAB_XTD|MTAB_VDV, 0, "IDLE", "IDLE", &sim_set_idle, &sim_show_idle }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "NOIDLE", &sim_clr_idle, NULL }, + { UNIT_EXBRK, UNIT_EXBRK, "Break on exceptions", "EXBRK", + NULL, NULL, NULL, "Enable break on exceptions and traps" }, + { UNIT_EXBRK, 0, "No break on exceptions", "NOEXBRK", + NULL, NULL, NULL, "Disable break on exceptions and traps" }, + { UNIT_OPBRK, UNIT_OPBRK, "Break on invalid opcodes", "OPBRK", + NULL, NULL, NULL, "Enable break on invalid opcodes" }, + { UNIT_OPBRK, 0, "No break on invalid opcodes", "NOOPBRK", + NULL, NULL, NULL, "Disable break on invalid opcodes" }, + { 0 } +}; + +DEVICE cpu_dev = { + "CPU", /* Name */ + &cpu_unit, /* Units */ + cpu_reg, /* Registers */ + cpu_mod, /* Modifiers */ + 1, /* Number of Units */ + 16, /* Address radix */ + 32, /* Address width */ + 1, /* Addr increment */ + 16, /* Data radix */ + 8, /* Data width */ + &cpu_ex, /* Examine routine */ + &cpu_dep, /* Deposit routine */ + &cpu_reset, /* Reset routine */ + &cpu_boot, /* Boot routine */ + NULL, /* Attach routine */ + NULL, /* Detach routine */ + NULL, /* Context */ + DEV_DYNM|DEV_DEBUG, /* Flags */ + 0, /* Debug control flags */ + cpu_deb_tab, /* Debug flag names */ + &cpu_set_size, /* Memory size change */ + NULL, /* Logical names */ + &cpu_help, /* Help routine */ + NULL, /* Attach Help Routine */ + NULL, /* Help Context */ + &cpu_description /* Device Description */ +}; + +mnemonic hword_ops[HWORD_OP_COUNT] = { + {0x3009, 0, OP_NONE, NA, "MVERNO", -1, -1, -1, -1}, + {0x300d, 0, OP_NONE, NA, "ENBVJMP", -1, -1, -1, -1}, + {0x3013, 0, OP_NONE, NA, "DISVJMP", -1, -1, -1, -1}, + {0x3019, 0, OP_NONE, NA, "MOVBLW", -1, -1, -1, -1}, + {0x301f, 0, OP_NONE, NA, "STREND", -1, -1, -1, -1}, + {0x302f, 1, OP_DESC, WD, "INTACK", -1, -1, -1, -1}, + {0x3035, 0, OP_NONE, NA, "STRCPY", -1, -1, -1, -1}, + {0x3045, 0, OP_NONE, NA, "RETG", -1, -1, -1, -1}, + {0x3061, 0, OP_NONE, NA, "GATE", -1, -1, -1, -1}, + {0x30ac, 0, OP_NONE, NA, "CALLPS", -1, -1, -1, -1}, +#if defined(REV3) + {0x30c0, 0, OP_NONE, NA, "UCALLPS", -1, -1, -1, -1}, +#endif + {0x30c8, 0, OP_NONE, NA, "RETPS", -1, -1, -1, -1} +}; + +/* Lookup table of operand types. */ +mnemonic ops[256] = { + {0x00, 0, OP_NONE, NA, "halt", -1, -1, -1, -1}, + {0x01, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x02, 2, OP_COPR, WD, "SPOPRD", 1, -1, -1, -1}, + {0x03, 3, OP_COPR, WD, "SPOPD2", 1, -1, -1, 2}, + {0x04, 2, OP_DESC, WD, "MOVAW", 0, -1, -1, 1}, + {0x05, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x06, 2, OP_COPR, WD, "SPOPRT", 1, -1, -1, -1}, + {0x07, 3, OP_COPR, WD, "SPOPT2", 1, -1, -1, 2}, + {0x08, 0, OP_NONE, NA, "RET", -1, -1, -1, -1}, +#if defined(REV3) + {0x09, 3, OP_DESC, WD, "CASWI", 0, 1, -1, 2}, + {0x0a, 0, OP_NONE, NA, "SETX", -1, -1, -1, -1}, + {0x0b, 0, OP_NONE, NA, "CLRX", -1, -1, -1, -1}, +#else + {0x09, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x0a, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x0b, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif + {0x0c, 2, OP_DESC, WD, "MOVTRW", 0, -1, -1, 1}, +#if defined(REV3) + {0x0d, 2, OP_DESH, HW, "TEDTH", 1, -1, -1, 0}, + {0x0e, 2, OP_DESC, HW, "PACKB", 0, -1, -1, 1}, + {0x0f, 3, OP_DESC, HW, "UNPACKB", 0, 1, -1, 2}, +#else + {0x0d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x0e, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x0f, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif + {0x10, 1, OP_DESC, WD, "SAVE", 0, -1, -1, -1}, + {0x11, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x12, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x13, 2, OP_COPR, WD, "SPOPWD", -1, -1, -1, 1}, + {0x14, 1, OP_BYTE, NA, "EXTOP", -1, -1, -1, -1}, + {0x15, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x16, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x17, 2, OP_COPR, WD, "SPOPWT", -1, -1, -1, 1}, + {0x18, 1, OP_DESC, WD, "RESTORE", 0, -1, -1, -1}, +#if defined(REV3) + {0x19, 2, OP_DESH, HW, "DTH", 1, -1, -1, 0}, +#else + {0x19, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif + {0x1a, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x1b, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x1c, 1, OP_DESC, WD, "SWAPWI", -1, -1, -1, 0}, +#if defined(REV3) + {0x1d, 2, OP_DESH, HW, "TGEDTH", 1, -1, -1, 0}, +#else + {0x1d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif + {0x1e, 1, OP_DESC, HW, "SWAPHI", -1, -1, -1, 0}, + {0x1f, 1, OP_DESC, BT, "SWAPBI", -1, -1, -1, 0}, + {0x20, 1, OP_DESC, WD, "POPW", -1, -1, -1, 0}, + {0x21, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x22, 2, OP_COPR, WD, "SPOPRS", 1, -1, -1, -1}, + {0x23, 3, OP_COPR, WD, "SPOPS2", 1, -1, -1, 2}, + {0x24, 1, OP_DESC, NA, "JMP", -1, -1, -1, 0}, + {0x25, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x26, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x27, 0, OP_NONE, NA, "CFLUSH", -1, -1, -1, -1}, + {0x28, 1, OP_DESC, WD, "TSTW", 0, -1, -1, -1}, +#if defined(REV3) + {0x29, 2, OP_DESB, BT, "DTB", 1, -1, -1, 0}, +#else + {0x29, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif + {0x2a, 1, OP_DESC, HW, "TSTH", 0, -1, -1, -1}, + {0x2b, 1, OP_DESC, BT, "TSTB", 0, -1, -1, -1}, + {0x2c, 2, OP_DESC, WD, "CALL", 0, -1, -1, 1}, +#if defined(REV3) + {0x2d, 2, OP_DESH, HW, "TGDTH", 1, -1, -1, 0}, +#else + {0x2d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif + {0x2e, 0, OP_NONE, NA, "BPT", -1, -1, -1, -1}, /* TODO: Verify */ + {0x2f, 0, OP_NONE, NA, "WAIT", -1, -1, -1, -1}, + {0x30, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, /* Two-byte instructions */ + {0x31, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x32, 1, OP_COPR, WD, "SPOP", -1, -1, -1, -1}, + {0x33, 2, OP_COPR, WD, "SPOPWS", -1, -1, -1, 1}, + {0x34, 1, OP_DESC, WD, "JSB", -1, -1, -1, 0}, + {0x35, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x36, 1, OP_HALF, NA, "BSBH", -1, -1, -1, 0}, + {0x37, 1, OP_BYTE, NA, "BSBB", -1, -1, -1, 0}, + {0x38, 2, OP_DESC, WD, "BITW", 0, 1, -1, -1}, + {0x39, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x3a, 2, OP_DESC, HW, "BITH", 0, 1, -1, -1}, + {0x3b, 2, OP_DESC, BT, "BITB", 0, 1, -1, -1}, + {0x3c, 2, OP_DESC, WD, "CMPW", 0, 1, -1, -1}, +#if defined(REV3) + {0x3d, 2, OP_DESH, HW, "TNEDTH", 1, -1, -1, 0}, +#else + {0x3d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif + {0x3e, 2, OP_DESC, HW, "CMPH", 0, 1, -1, -1}, + {0x3f, 2, OP_DESC, BT, "CMPB", 0, 1, -1, -1}, + {0x40, 0, OP_NONE, NA, "RGEQ", -1, -1, -1, -1}, + {0x41, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x42, 1, OP_HALF, NA, "BGEH", -1, -1, -1, 0}, + {0x43, 1, OP_BYTE, NA, "BGEB", -1, -1, -1, 0}, + {0x44, 0, OP_NONE, NA, "RGTR", -1, -1, -1, -1}, + {0x45, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x46, 1, OP_HALF, NA, "BGH", -1, -1, -1, 0}, + {0x47, 1, OP_BYTE, NA, "BGB", -1, -1, -1, 0}, + {0x48, 0, OP_NONE, NA, "RLSS", -1, -1, -1, 0}, + {0x49, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x4a, 1, OP_HALF, NA, "BLH", -1, -1, -1, 0}, + {0x4b, 1, OP_BYTE, NA, "BLB", -1, -1, -1, 0}, + {0x4c, 0, OP_NONE, NA, "RLEQ", -1, -1, -1, -1}, +#if defined(REV3) + {0x4d, 2, OP_DESB, BT, "TEDTB", 1, -1, -1, 0}, +#else + {0x4d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif + {0x4e, 1, OP_HALF, NA, "BLEH", -1, -1, -1, 0}, + {0x4f, 1, OP_BYTE, NA, "BLEB", -1, -1, -1, 0}, + {0x50, 0, OP_NONE, NA, "RGEQU", -1, -1, -1, 0}, /* aka RCC */ + {0x51, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x52, 1, OP_HALF, NA, "BGEUH", -1, -1, -1, 0}, /* aka BCCH */ + {0x53, 1, OP_BYTE, NA, "BGEUB", -1, -1, -1, 0}, /* aka BCCB */ + {0x54, 0, OP_NONE, NA, "RGTRU", -1, -1, -1, -1}, /* TODO: Implement */ + {0x55, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x56, 1, OP_HALF, NA, "BGUH", -1, -1, -1, 0}, + {0x57, 1, OP_BYTE, NA, "BGUB", -1, -1, -1, 0}, + {0x58, 0, OP_NONE, NA, "RLSSU", -1, -1, -1, 0}, /* aka RCS */ /* TODO: Implement */ + {0x59, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x5a, 1, OP_HALF, NA, "BLUH", -1, -1, -1, 0}, /* aka BCSH */ + {0x5b, 1, OP_BYTE, NA, "BLUB", -1, -1, -1, 0}, /* aka BCSB */ + {0x5c, 0, OP_NONE, NA, "RLEQU", -1, -1, -1, -1}, +#if defined(REV3) + {0x5d, 2, OP_DESB, HW, "TGEDTB", 1, -1, -1, 0}, +#else + {0x5d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif + {0x5e, 1, OP_HALF, NA, "BLEUH", -1, -1, -1, 0}, + {0x5f, 1, OP_BYTE, NA, "BLEUB", -1, -1, -1, 0}, + {0x60, 0, OP_NONE, NA, "RVC", -1, -1, -1, -1}, /* TODO: Implement */ + {0x61, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x62, 1, OP_HALF, NA, "BVCH", -1, -1, -1, 0}, + {0x63, 1, OP_BYTE, NA, "BVCB", -1, -1, -1, 0}, + {0x64, 0, OP_NONE, NA, "RNEQU", -1, -1, -1, -1}, + {0x65, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x66, 1, OP_HALF, NA, "BNEH", -1, -1, -1, 0}, /* duplicate of 76 */ + {0x67, 1, OP_BYTE, NA, "BNEB", -1, -1, -1, 0}, /* duplicate of 77 */ + {0x68, 0, OP_NONE, NA, "RVS", -1, -1, -1, -1}, /* TODO: Implement */ + {0x69, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x6a, 1, OP_HALF, NA, "BVSH", -1, -1, -1, 0}, + {0x6b, 1, OP_BYTE, NA, "BVSB", -1, -1, -1, 0}, + {0x6c, 0, OP_NONE, NA, "REQLU", -1, -1, -1, -1}, +#if defined(REV3) + {0x6d, 2, OP_DESB, BT, "TGDTB", 1, -1, -1, 0}, +#else + {0x6d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif + {0x6e, 1, OP_HALF, NA, "BEH", -1, -1, -1, 0}, /* duplicate of 7e */ + {0x6f, 1, OP_BYTE, NA, "BEB", -1, -1, -1, 0}, /* duplicate of 7f */ + {0x70, 0, OP_NONE, NA, "NOP", -1, -1, -1, -1}, + {0x71, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x72, 0, OP_NONE, NA, "NOP3", -1, -1, -1, -1}, + {0x73, 0, OP_NONE, NA, "NOP2", -1, -1, -1, -1}, + {0x74, 0, OP_NONE, NA, "RNEQ", -1, -1, -1, -1}, + {0x75, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x76, 1, OP_HALF, NA, "BNEH", -1, -1, -1, 0}, /* duplicate of 66 */ + {0x77, 1, OP_BYTE, NA, "BNEB", -1, -1, -1, 0}, /* duplicate of 67 */ + {0x78, 0, OP_NONE, NA, "RSB", -1, -1, -1, -1}, + {0x79, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x7a, 1, OP_HALF, NA, "BRH", -1, -1, -1, 0}, + {0x7b, 1, OP_BYTE, NA, "BRB", -1, -1, -1, 0}, + {0x7c, 0, OP_NONE, NA, "REQL", -1, -1, -1, -1}, +#if defined(REV3) + {0x7d, 2, OP_DESB, BT, "TNEDTB", 1, -1, -1, 0}, +#else + {0x7d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif + {0x7e, 1, OP_HALF, NA, "BEH", -1, -1, -1, 0}, /* duplicate of 6e */ + {0x7f, 1, OP_BYTE, NA, "BEB", -1, -1, -1, 0}, /* duplicate of 6f */ + {0x80, 1, OP_DESC, WD, "CLRW", -1, -1, -1, 0}, + {0x81, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x82, 1, OP_DESC, HW, "CLRH", -1, -1, -1, 0}, + {0x83, 1, OP_DESC, BT, "CLRB", -1, -1, -1, 0}, + {0x84, 2, OP_DESC, WD, "MOVW", 0, -1, -1, 1}, + {0x85, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x86, 2, OP_DESC, HW, "MOVH", 0, -1, -1, 1}, + {0x87, 2, OP_DESC, BT, "MOVB", 0, -1, -1, 1}, + {0x88, 2, OP_DESC, WD, "MCOMW", 0, -1, -1, 1}, + {0x89, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x8a, 2, OP_DESC, HW, "MCOMH", 0, -1, -1, 1}, + {0x8b, 2, OP_DESC, BT, "MCOMB", 0, -1, -1, 1}, + {0x8c, 2, OP_DESC, WD, "MNEGW", 0, -1, -1, 1}, + {0x8d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x8e, 2, OP_DESC, HW, "MNEGH", 0, -1, -1, 1}, + {0x8f, 2, OP_DESC, BT, "MNEGB", 0, -1, -1, 1}, + {0x90, 1, OP_DESC, WD, "INCW", -1, -1, -1, 0}, + {0x91, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x92, 1, OP_DESC, HW, "INCH", -1, -1, -1, 0}, + {0x93, 1, OP_DESC, BT, "INCB", -1, -1, -1, 0}, + {0x94, 1, OP_DESC, WD, "DECW", -1, -1, -1, 0}, + {0x95, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x96, 1, OP_DESC, HW, "DECH", -1, -1, -1, 0}, + {0x97, 1, OP_DESC, BT, "DECB", -1, -1, -1, 0}, +#if defined(REV3) + {0x98, 0, OP_NONE, NA, "RETQINT",-1, -1, -1, -1}, /* TODO: Implement */ +#else + {0x98, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif + {0x99, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x9a, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#if defined(REV3) + {0x9b, 2, OP_DESC, BT, "SUBPB2", 0, -1, -1, 1}, +#else + {0x9b, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif + {0x9c, 2, OP_DESC, WD, "ADDW2", 0, -1, -1, 1}, + {0x9d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0x9e, 2, OP_DESC, HW, "ADDH2", 0, -1, -1, 1}, + {0x9f, 2, OP_DESC, BT, "ADDB2", 0, -1, -1, 1}, + {0xa0, 1, OP_DESC, WD, "PUSHW", 0, -1, -1, -1}, + {0xa1, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xa2, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#if defined(REV3) + {0xa3, 2, OP_DESC, BT, "ADDPB2", 0, -1, -1, 1}, +#else + {0xa3, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif + {0xa4, 2, OP_DESC, WD, "MODW2", 0, -1, -1, 1}, + {0xa5, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xa6, 2, OP_DESC, HW, "MODH2", 0, -1, -1, 1}, + {0xa7, 2, OP_DESC, BT, "MODB2", 0, -1, -1, 1}, + {0xa8, 2, OP_DESC, WD, "MULW2", 0, -1, -1, 1}, + {0xa9, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xaa, 2, OP_DESC, HW, "MULH2", 0, -1, -1, 1}, + {0xab, 2, OP_DESC, BT, "MULB2", 0, -1, -1, 1}, + {0xac, 2, OP_DESC, WD, "DIVW2", 0, -1, -1, 1}, + {0xad, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xae, 2, OP_DESC, HW, "DIVH2", 0, -1, -1, 1}, + {0xaf, 2, OP_DESC, BT, "DIVB2", 0, -1, -1, 1}, + {0xb0, 2, OP_DESC, WD, "ORW2", 0, -1, -1, 1}, + {0xb1, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xb2, 2, OP_DESC, HW, "ORH2", 0, -1, -1, 1}, + {0xb3, 2, OP_DESC, BT, "ORB2", 0, -1, -1, 1}, + {0xb4, 2, OP_DESC, WD, "XORW2", 0, -1, -1, 1}, + {0xb5, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xb6, 2, OP_DESC, HW, "XORH2", 0, -1, -1, 1}, + {0xb7, 2, OP_DESC, BT, "XORB2", 0, -1, -1, 1}, + {0xb8, 2, OP_DESC, WD, "ANDW2", 0, -1, -1, 1}, + {0xb9, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xba, 2, OP_DESC, HW, "ANDH2", 0, -1, -1, 1}, + {0xbb, 2, OP_DESC, BT, "ANDB2", 0, -1, -1, 1}, + {0xbc, 2, OP_DESC, WD, "SUBW2", 0, -1, -1, 1}, + {0xbd, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xbe, 2, OP_DESC, HW, "SUBH2", 0, -1, -1, 1}, + {0xbf, 2, OP_DESC, BT, "SUBB2", 0, -1, -1, 1}, + {0xc0, 3, OP_DESC, WD, "ALSW3", 0, 1, -1, 2}, + {0xc1, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xc2, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xc3, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xc4, 3, OP_DESC, WD, "ARSW3", 0, 1, -1, 2}, + {0xc5, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xc6, 3, OP_DESC, HW, "ARSH3", 0, 1, -1, 2}, + {0xc7, 3, OP_DESC, BT, "ARSB3", 0, 1, -1, 2}, + {0xc8, 4, OP_DESC, WD, "INSFW", 0, 1, 2, 3}, + {0xc9, -1, OP_DESC, NA, "???", -1, -1, -1, -1}, + {0xca, 4, OP_DESC, HW, "INSFH", 0, 1, 2, 3}, + {0xcb, 4, OP_DESC, BT, "INSFB", 0, 1, 2, 3}, + {0xcc, 4, OP_DESC, WD, "EXTFW", 0, 1, 2, 3}, + {0xcd, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xce, 4, OP_DESC, HW, "EXTFH", 0, 1, 2, 3}, + {0xcf, 4, OP_DESC, BT, "EXTFB", 0, 1, 2, 3}, + {0xd0, 3, OP_DESC, WD, "LLSW3", 0, 1, -1, 2}, + {0xd1, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xd2, 3, OP_DESC, HW, "LLSH3", 0, 1, -1, 2}, + {0xd3, 3, OP_DESC, BT, "LLSB3", 0, 1, -1, 2}, + {0xd4, 3, OP_DESC, WD, "LRSW3", 0, 1, -1, 2}, + {0xd5, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xd6, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xd7, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xd8, 3, OP_DESC, WD, "ROTW", 0, 1, -1, 2}, + {0xd9, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xda, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#if defined(REV3) + {0xdb, 3, OP_DESC, BT, "SUBPB3", 0, 1, -1, 2}, +#else + {0xdb, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif + {0xdc, 3, OP_DESC, WD, "ADDW3", 0, 1, -1, 2}, + {0xdd, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xde, 3, OP_DESC, HW, "ADDH3", 0, 1, -1, 2}, + {0xdf, 3, OP_DESC, BT, "ADDB3", 0, 1, -1, 2}, + {0xe0, 1, OP_DESC, WD, "PUSHAW", 0, -1, -1, -1}, + {0xe1, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xe2, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#if defined(REV3) + {0xe3, 3, OP_DESC, BT, "ADDPB3", 0, 1, -1, 2}, +#else + {0xe3, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif + {0xe4, 3, OP_DESC, WD, "MODW3", 0, 1, -1, 2}, + {0xe5, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xe6, 3, OP_DESC, HW, "MODH3", 0, 1, -1, 2}, + {0xe7, 3, OP_DESC, BT, "MODB3", 0, 1, -1, 2}, + {0xe8, 3, OP_DESC, WD, "MULW3", 0, 1, -1, 2}, + {0xe9, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xea, 3, OP_DESC, HW, "MULH3", 0, 1, -1, 2}, + {0xeb, 3, OP_DESC, BT, "MULB3", 0, 1, -1, 2}, + {0xec, 3, OP_DESC, WD, "DIVW3", 0, 1, -1, 2}, + {0xed, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xee, 3, OP_DESC, HW, "DIVH3", 0, 1, -1, 2}, + {0xef, 3, OP_DESC, BT, "DIVB3", 0, 1, -1, 2}, + {0xf0, 3, OP_DESC, WD, "ORW3", 0, 1, -1, 2}, + {0xf1, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xf2, 3, OP_DESC, HW, "ORH3", 0, 1, -1, 2}, + {0xf3, 3, OP_DESC, BT, "ORB3", 0, 1, -1, 2}, + {0xf4, 3, OP_DESC, WD, "XORW3", 0, 1, -1, 2}, + {0xf5, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xf6, 3, OP_DESC, HW, "XORH3", 0, 1, -1, 2}, + {0xf7, 3, OP_DESC, BT, "XORB3", 0, 1, -1, 2}, + {0xf8, 3, OP_DESC, WD, "ANDW3", 0, 1, -1, 2}, + {0xf9, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xfa, 3, OP_DESC, HW, "ANDH3", 0, 1, -1, 2}, + {0xfb, 3, OP_DESC, BT, "ANDB3", 0, 1, -1, 2}, + {0xfc, 3, OP_DESC, WD, "SUBW3", 0, 1, -1, 2}, + {0xfd, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, + {0xfe, 3, OP_DESC, HW, "SUBH3", 0, 1, -1, 2}, + {0xff, 3, OP_DESC, BT, "SUBB3", 0, 1, -1, 2} +}; + +/* from MAME (src/devices/cpu/m68000/m68kcpu.c) */ +const uint8 shift_8_table[65] = +{ + 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 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, 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 +}; +const uint16 shift_16_table[65] = +{ + 0x0000, 0x8000, 0xc000, 0xe000, 0xf000, 0xf800, 0xfc00, 0xfe00, 0xff00, + 0xff80, 0xffc0, 0xffe0, 0xfff0, 0xfff8, 0xfffc, 0xfffe, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff +}; +const uint32 shift_32_table[65] = +{ + 0x00000000, 0x80000000, 0xc0000000, 0xe0000000, 0xf0000000, 0xf8000000, + 0xfc000000, 0xfe000000, 0xff000000, 0xff800000, 0xffc00000, 0xffe00000, + 0xfff00000, 0xfff80000, 0xfffc0000, 0xfffe0000, 0xffff0000, 0xffff8000, + 0xffffc000, 0xffffe000, 0xfffff000, 0xfffff800, 0xfffffc00, 0xfffffe00, + 0xffffff00, 0xffffff80, 0xffffffc0, 0xffffffe0, 0xfffffff0, 0xfffffff8, + 0xfffffffc, 0xfffffffe, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff +}; + +t_stat cpu_show_stack(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + uint32 i, j; + uint32 addr, v, count; + uint8 tmp; + char *cptr = (char *) desc; + t_stat result; + + if (cptr) { + count = (size_t) get_uint(cptr, 10, 128, &result); + if ((result != SCPE_OK) || (count == 0)) { + return SCPE_ARG; + } + } else { + count = 8; + } + + for (i = 0; i < (count * 4); i += 4) { + v = 0; + addr = R[NUM_SP] - i; + + for (j = 0; j < 4; j++) { + result = examine(addr + j, &tmp); + if (result != SCPE_OK) { + return result; + } + v |= (uint32) tmp << ((3 - j) * 8); + } + + fprintf(st, " %08x: %08x\n", addr, v); + } + + return SCPE_OK; +} + +t_stat cpu_show_cio(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + uint32 slot; + + fprintf(st, " SLOT DEVICE\n"); + fprintf(st, "---------------------\n"); + for (slot = 0; slot < CIO_SLOTS; slot++) { + if (cio[slot].populated) { + fprintf(st, " %2d %s\n", slot, cio[slot].name); + } else { + fprintf(st, " %2d -\n", slot); + } + } + + return SCPE_OK; +} + +#if defined(REV3) +t_stat sys_boot(int32 flag, CONST char *ptr) +{ + char gbuf[CBUFSIZE]; + + if ((ptr = get_sim_sw(ptr)) == NULL) { + return SCPE_INVSW; + } + + get_glyph(ptr, gbuf, 0); + if (gbuf[0] && strcmp(gbuf, "CPU")) { + return SCPE_ARG; + } + + return run_cmd(flag, "CPU"); +} +#else +t_stat sys_boot(int32 flag, CONST char *ptr) +{ + char gbuf[CBUFSIZE]; + size_t len = ROM_SIZE; + t_stat r; + + if ((ptr = get_sim_sw(ptr)) == NULL) { + return SCPE_INVSW; + } + + do { + ptr = get_glyph(ptr, gbuf, 0); + + if (gbuf[0] && (strcmp(gbuf, "CPU"))) { + return SCPE_ARG; + } + } while (gbuf[0]); + + return run_cmd(flag, "CPU"); +} +#endif /* Rev 2 boot */ + +t_stat cpu_boot(int32 unit_num, DEVICE *dptr) +{ + /* + * page 2-52 (pdf page 85) + * + * 1. Change to physical address mode + * 2. Fetch the word at physical address 0x80 and store it in + * the PCBP register. + * 3. Fetch the word at the PCB address and store it in the + * PSW. + * 4. Fetch the word at PCB address + 4 bytes and store it + * in the PC. + * 5. Fetch the word at PCB address + 8 bytes and store it + * in the SP. + * 6. Fetch the word at PCB address + 12 bytes and store it + * in the PCB, if bit I in PSW is set. + */ + + if (!rom_loaded) { + sim_messagef(SCPE_NXM, "Cannot boot, ROM not loaded.\n"); + return SCPE_STOP; + } + + sim_debug(EXECUTE_MSG, &cpu_dev, + "CPU Boot/Reset Initiated. PC=%08x SP=%08x\n", + R[NUM_PC], R[NUM_SP]); + + mmu_disable(); + + R[NUM_PCBP] = pread_w(0x80, BUS_CPU); + R[NUM_PSW] = pread_w(R[NUM_PCBP], BUS_CPU); + R[NUM_PC] = pread_w(R[NUM_PCBP] + 4, BUS_CPU); + R[NUM_SP] = pread_w(R[NUM_PCBP] + 8, BUS_CPU); + + if (R[NUM_PSW] & PSW_I_MASK) { + R[NUM_PSW] &= ~PSW_I_MASK; + R[NUM_PCBP] += 12; + } + + /* set ISC to External Reset */ + R[NUM_PSW] &= ~PSW_ISC_MASK; + R[NUM_PSW] |= 3 << PSW_ISC ; + + return SCPE_OK; +} + +t_stat cpu_ex(t_value *vptr, t_addr addr, UNIT *uptr, int32 sw) +{ + uint32 uaddr = (uint32) addr; + uint8 value; + t_stat succ; + + if (vptr == NULL) { + return SCPE_ARG; + } + + if (sw & EX_V_FLAG) { + succ = examine(uaddr, &value); + *vptr = value; + return succ; + } else { + if (IS_ROM(uaddr) || IS_RAM(uaddr)) { + *vptr = (uint32) pread_b(uaddr, BUS_CPU); + return SCPE_OK; + } else { + *vptr = 0; + return SCPE_NXM; + } + } +} + +t_stat cpu_dep(t_value val, t_addr addr, UNIT *uptr, int32 sw) +{ + uint32 uaddr = (uint32) addr; + + if (sw & EX_V_FLAG) { + return deposit(uaddr, (uint8) val); + } else { + if (IS_RAM(uaddr)) { + pwrite_b(uaddr, (uint8) val, BUS_CPU); + return SCPE_OK; + } else { + return SCPE_NXM; + } + } +} + +/* + * Pre-populate the interrupt->IPL map "int_map" + */ +static void build_int_map() +{ + int i; + uint8 ipl; + + for (i = 0; i < INT_MAP_LEN; i++) { +#if defined(REV3) + if (i & (INT_PWRDWN|INT_BUS_OP|INT_SBERR| + INT_MBERR|INT_BUS_RXF|INT_BUS_TMO| + INT_CLOCK)) { + ipl = CPU_IPL_15; + } else if (i & (INT_UART|INT_UART_DMA)) { + ipl = CPU_IPL_13; + } else if (i & (INT_FLOPPY|INT_FLOPPY_DMA)) { + ipl = CPU_IPL_11; + } else if (i & INT_PIR9) { + ipl = CPU_IPL_9; + } else if (i & INT_PIR8) { + ipl = CPU_IPL_8; + } else { + ipl = 0; + } +#else + if (i & (INT_CLOCK|INT_SERR)) { + ipl = CPU_IPL_15; + } else if (i & (INT_UART|INT_DMA)) { + ipl = CPU_IPL_13; + } else if (i & (INT_DISK|INT_FLOPPY)) { + ipl = CPU_IPL_11; + } else if (i & INT_PIR9) { + ipl = CPU_IPL_9; + } else if (i & INT_PIR8) { + ipl = CPU_IPL_8; + } else { + ipl = 0; + } +#endif + + int_map[i] = ipl; + } + + sim_debug(EXECUTE_MSG, &cpu_dev, + "Built interrupt->IPL map of length %d\n", INT_MAP_LEN); +} + +t_stat cpu_reset(DEVICE *dptr) +{ + int i; + t_stat r; + + sim_debug(EXECUTE_MSG, &cpu_dev, "CPU Reset.\n"); + + if (!sim_is_running) { + /* Populate the interrupt->IPL map */ + build_int_map(); + + /* Clear registers */ + for (i = 0; i < NUM_REGISTERS; i++) { + R[i] = 0; + } + + /* Allocate ROM if needed */ + if (ROM == NULL) { + ROM = (uint8 *) calloc((size_t)ROM_SIZE, sizeof(uint8)); + } + if (ROM == NULL) { + return SCPE_MEM; + } + + /* Allocate RAM if needed */ + if (RAM == NULL) { + RAM = (uint8 *) calloc((size_t)MEM_SIZE, sizeof(uint8)); + } + if (RAM == NULL) { + return SCPE_MEM; + } + + sim_vm_is_subroutine_call = cpu_is_pc_a_subroutine_call; + + /* Link in our special "boot" command so we can boot with both + * "BO{OT}" and "BO{OT} CPU" */ + sim_vm_cmd = sys_cmd; + + /* Set up the pre-calibration routine */ + sim_clock_precalibrate_commands = att3b2_clock_precalibrate_commands; + + abort_context = C_NONE; + + cpu_in_wait = FALSE; + } + + sim_brk_types = SWMASK('E'); + sim_brk_dflt = SWMASK('E'); + + return SCPE_OK; +} + +static const char *cpu_next_caveats = +"The NEXT command in this 3B2 architecture simulator currently will\n" +"enable stepping across subroutine calls which are initiated by the\n" +"JSB, CALL and CALLPS instructions.\n" +"This stepping works by dynamically establishing breakpoints at the\n" +"memory address immediately following the instruction which initiated\n" +"the subroutine call. These dynamic breakpoints are automatically\n" +"removed once the simulator returns to the sim> prompt for any reason.\n" +"If the called routine returns somewhere other than one of these\n" +"locations due to a trap, stack unwind or any other reason, instruction\n" +"execution will continue until some other reason causes execution to stop.\n"; + +t_bool cpu_is_pc_a_subroutine_call (t_addr **ret_addrs) +{ + static t_addr returns[MAX_SUB_RETURN_SKIP+1] = {0}; + static t_bool caveats_displayed = FALSE; + int i; + + if (!caveats_displayed) { + caveats_displayed = TRUE; + sim_printf ("%s", cpu_next_caveats); + } + + /* get data */ + if (SCPE_OK != get_aval (R[NUM_PC], &cpu_dev, &cpu_unit)) { + return FALSE; + } + + switch (sim_eval[0]) { + case JSB: + case CALL: + case CALLPS: +#if defined(REV3) + case UCALLPS: +#endif + returns[0] = R[NUM_PC] + (unsigned int) (1 - fprint_sym(stdnul, R[NUM_PC], + sim_eval, &cpu_unit, + SWMASK ('M'))); + for (i=1; imnemonic); + + for (i = 0; i < mn->op_count; i++) { + + /* Special cases for non-descriptor opcodes */ + if ((mn->mode == OP_BYTE) || (mn->mode == OP_DESB && i > 0)) { + mode = 6; + reg = 15; + } else if (mn->mode == OP_HALF || (mn->mode == OP_DESH && i > 0)) { + mode = 5; + reg = 15; + } else if (mn->mode == OP_COPR) { + mode = 4; + reg = 15; + } else { + desc = (uint8) val[vp++]; + +#if defined(REV3) /* WE 32200 only */ + switch (desc) { + case 0x5b: + /* Get next byte */ + desc = (uint8) val[vp++]; + /* + * Mode 0x10: Auto pre-decrement -(%rx) + * Mode 0x12: Auto post-decrement (%rx)- + * Mode 0x14: Auto pre-increment +(%rx) + * Mode 0x16: Auto post-increment (%rx)- + */ + mode = ((desc >> 5) & 0x7) | 0x10; + reg = desc & 0x1f; + break; + case 0xab: + case 0xbb: + mode = 0xab; + desc = (uint8) val[vp++]; + reg = (desc >> 4) & 0xf; + reg2 = (desc & 0xf) + 16; + break; + case 0xcb: + /* Get next byte */ + desc = (uint8) val[vp++]; + mode = (desc >> 4) & 0xf; + reg = (desc & 0xf) + 16; + break; + case 0xdb: + mode = 0xdb; + desc = (uint8) val[vp++]; + reg = (desc >> 4) & 0xf; + reg2 = (desc & 0xf) + 16; + break; + default: + mode = (desc >> 4) & 0xf; + reg = desc & 0xf; + break; + } +#else /* WE 32100 only */ + mode = (desc >> 4) & 0xf; + reg = desc & 0xf; +#endif + + /* Find the expanded data type, if any */ + if (mode == 14 && + (reg == 0 || reg == 2 || reg == 3 || + reg == 4 || reg == 6 || reg == 7)) { + etype = reg; + /* The real descriptor byte lies one ahead */ + desc = (uint8) val[vp++]; + mode = (desc >> 4) & 0xf; + reg = desc & 0xf; + } + } + + fputc(i ? ',' : ' ', of); + + switch (etype) { + case 0: + fprintf(of, "{uword}"); + break; + case 2: + fprintf(of, "{uhalf}"); + break; + case 3: + fprintf(of, "{ubyte}"); + break; + case 4: + fprintf(of, "{word}"); + break; + case 6: + fprintf(of, "{half}"); + break; + case 7: + fprintf(of, "{sbyte}"); + break; + default: + /* do nothing */ + break; + } + + switch(mode) { + case 0: /* Positive Literal */ + case 1: /* Positive Literal */ + case 2: /* Positive Literal */ + case 3: /* Positive Literal */ + case 15: /* Negative Literal */ + fprintf(of, "&%d", desc); + break; + case 4: /* Halfword Immediate, Register Mode */ + switch (reg) { + case 15: /* Word Immediate */ + OP_R_W(w, val, vp); + fprintf(of, "&0x%x", w); + break; + default: /* Register Mode */ + cpu_register_name(reg, reg_name, 8); + fprintf(of, "%s", reg_name); + break; + } + break; + case 5: /* Halfword Immediate, Register Deferred */ + switch (reg) { + case 15: + OP_R_H(w, val, vp); + fprintf(of, "&0x%x", w); + break; + default: + cpu_register_name(reg, reg_name, 8); + fprintf(of, "(%s)", reg_name); + break; + } + break; + case 6: /* Byte Immediate, FP Short Offset */ + switch (reg) { + case 15: + OP_R_B(w, val, vp); + fprintf(of, "&0x%x", w); + break; + default: + fprintf(of, "%d(%%fp)", (int8) reg); + break; + } + break; + case 7: /* Absolute, AP Short Offset */ + switch (reg) { + case 15: + OP_R_W(w, val, vp); + fprintf(of, "$0x%x", w); + break; + default: + fprintf(of, "%d(%%ap)", (int8) reg); + break; + } + break; + case 8: /* Word Displacement */ + OP_R_W(w, val, vp); + cpu_register_name(reg, reg_name, 8); + fprintf(of, "0x%x(%s)", w, reg_name); + break; + case 9: /* Word Displacement Deferred */ + OP_R_W(w, val, vp); + cpu_register_name(reg, reg_name, 8); + fprintf(of, "*0x%x(%s)", w, reg_name); + break; + case 10: /* Halfword Displacement */ + OP_R_H(w, val, vp); + cpu_register_name(reg, reg_name, 8); + fprintf(of, "0x%x(%s)", w, reg_name); + break; + case 11: /* Halfword Displacement Deferred */ + OP_R_H(w, val, vp); + cpu_register_name(reg, reg_name, 8); + fprintf(of, "*0x%x(%s)", w, reg_name); + break; + case 12: /* Byte Displacement */ + OP_R_B(w, val, vp); + cpu_register_name(reg, reg_name, 8); + fprintf(of, "%d(%s)", (int8) w, reg_name); + break; + case 13: /* Byte Displacement Deferred */ + OP_R_B(w, val, vp); + cpu_register_name(reg, reg_name, 8); + fprintf(of, "*%d(%s)", (int8) w, reg_name); + break; + case 14: + if (reg == 15) { + OP_R_W(w, val, vp); + fprintf(of, "*$0x%x", w); + } + break; +#if defined(REV3) + case 0x10: /* Auto pre-decrement */ + cpu_register_name(reg, reg_name, 8); + fprintf(of, "-(%s)", reg_name); + break; + case 0x12: /* Auto post-decrement */ + cpu_register_name(reg, reg_name, 8); + fprintf(of, "(%s)-", reg_name); + break; + case 0x14: /* Auto pre-increment */ + cpu_register_name(reg, reg_name, 8); + fprintf(of, "+(%s)", reg_name); + break; + case 0x16: /* Auto post-increment */ + cpu_register_name(reg, reg_name, 8); + fprintf(of, "(%s)+", reg_name); + break; + case 0xab: + OP_R_B(w, val, vp); + cpu_register_name(reg, reg_name, 8); + cpu_register_name(reg2, reg2_name, 8); + fprintf(of, "%d(%s,%s)", (int8) w, reg2_name, reg_name); + break; + case 0xbb: + OP_R_H(w, val, vp); + cpu_register_name(reg, reg_name, 8); + cpu_register_name(reg2, reg2_name, 8); + fprintf(of, "0x%x(%s,%s)", w, reg2_name, reg_name); + break; + case 0xdb: + cpu_register_name(reg, reg_name, 8); + cpu_register_name(reg2, reg2_name, 8); + fprintf(of, "%s[%s]", reg2_name, reg_name); + break; +#endif + default: + fprintf(of, ""); + break; + } + } + + + return -(vp - 1); +} + +void fprint_sym_hist(FILE *st, instr *ip) +{ + int32 i; + + if (ip == NULL || ip->mn == NULL) { + fprintf(st, "???"); + return; + } + + fprintf(st, "%s", ip->mn->mnemonic); + + if (ip->mn->op_count > 0) { + fputc(' ', st); + } + + /* Show the operand mnemonics */ + for (i = 0; i < ip->mn->op_count; i++) { + cpu_show_operand(st, &ip->operands[i]); + if (i < ip->mn->op_count - 1) { + fputc(',', st); + } + } +} + +t_stat cpu_show_virt(FILE *of, UNIT *uptr, int32 val, CONST void *desc) +{ + uint32 va, pa; + t_stat r; + + const char *cptr = (const char *)desc; + if (cptr) { + va = (uint32) get_uint(cptr, 16, 0xffffffff, &r); + if (r == SCPE_OK) { + r = mmu_decode_va(va, 0, FALSE, &pa); + if (r == SCPE_OK) { + fprintf(of, "Virtual %08x = Physical %08x\n", va, pa); + return SCPE_OK; + } else { + fprintf(of, "Translation not possible for virtual address.\n"); + return SCPE_ARG; + } + } else { + fprintf(of, "Illegal address format.\n"); + return SCPE_ARG; + } + } + + fprintf(of, "Address argument required.\n"); + return SCPE_ARG; +} + +t_stat cpu_set_hist(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + uint32 i, size; + t_stat result; + + /* Clear the history buffer if no argument */ + if (cptr == NULL) { + for (i = 0; i < cpu_hist_size; i++) { + INST[i].valid = FALSE; + } + return SCPE_OK; + } + + /* Otherwise, get the new length */ + size = (uint32) get_uint(cptr, 10, MAX_HIST_SIZE, &result); + + /* If no length was provided, give up */ + if (result != SCPE_OK) { + return SCPE_ARG; + } + + /* Legnth 0 is a special flag that means disable the feature. */ + if (size == 0) { + if (INST != NULL) { + for (i = 0; i < cpu_hist_size; i++) { + INST[i].valid = FALSE; + } + } + cpu_hist_size = 0; + cpu_hist_p = 0; + return SCPE_OK; + } + + /* Reinitialize the new history ring bufer */ + cpu_hist_p = 0; + if (size > 0) { + if (INST != NULL) { + free(INST); + } + INST = (instr *)calloc(size, sizeof(instr)); + if (INST == NULL) { + return SCPE_MEM; + } + memset(INST, 0, sizeof(instr) * size); + cpu_hist_size = size; + } + + return SCPE_OK; +} + +t_stat cpu_show_hist(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + uint32 i; + size_t j, count; + char *cptr = (char *) desc; + t_stat result; + instr *ip; + + int32 di; + + if (cpu_hist_size == 0) { + return SCPE_NOFNC; + } + + /* 'count' is the number of history entries the user wants */ + + if (cptr) { + count = (size_t) get_uint(cptr, 10, cpu_hist_size, &result); + if ((result != SCPE_OK) || (count == 0)) { + return SCPE_ARG; + } + } else { + count = cpu_hist_size; + } + + /* Position for reading from ring buffer */ + di = (int32) (cpu_hist_p - count); + + if (di < 0) { + di = di + (int32) cpu_hist_size; + } + + fprintf(st, "PSW SP PC IR\n"); + + for (i = 0; i < count; i++) { + ip = &INST[(di++) % (int32) cpu_hist_size]; + if (ip->valid) { + /* Show the opcode mnemonic */ + fprintf(st, "%08x %08x %08x ", ip->psw, ip->sp, ip->pc); + /* Show the operand data */ + if (ip->mn == NULL || ip->mn->op_count < 0) { + fprintf(st, "???"); + } else { + fprint_sym_hist(st, ip); + if (ip->mn->op_count > 0 && ip->mn->mode == OP_DESC) { + fprintf(st, "\n "); + for (j = 0; j < (uint32) ip->mn->op_count; j++) { + fprintf(st, "%08x", ip->operands[j].data); + if (j < (uint32) ip->mn->op_count - 1) { + fputc(' ', st); + } + } + } + } + fputc('\n', st); + } + } + + + return SCPE_OK; +} + +void cpu_register_name(uint8 reg, char *buf, size_t len) { + switch(reg) { + case 9: + snprintf(buf, len, "%%fp"); + break; + case 10: + snprintf(buf, len, "%%ap"); + break; + case 11: + snprintf(buf, len, "%%psw"); + break; + case 12: + snprintf(buf, len, "%%sp"); + break; + case 13: + snprintf(buf, len, "%%pcbp"); + break; + case 14: + snprintf(buf, len, "%%isp"); + break; + case 15: + snprintf(buf, len, "%%pc"); + break; + default: + snprintf(buf, len, "%%r%d", reg); + break; + } +} + +void cpu_show_operand(FILE *st, operand *op) +{ + char reg_name[8]; +#if defined(REV3) + char reg2_name[8]; +#endif + + if (op->etype != -1) { + switch(op->etype) { + case 0: + fprintf(st, "{uword}"); + break; + case 2: + fprintf(st, "{uhalf}"); + break; + case 3: + fprintf(st, "{ubyte}"); + break; + case 4: + fprintf(st, "{word}"); + break; + case 6: + fprintf(st, "{half}"); + break; + case 7: + fprintf(st, "{sbyte}"); + break; + } + } + + switch(op->mode) { + case 0: + case 1: + case 2: + case 3: + fprintf(st, "&0x%x", op->embedded.b); + break; + case 4: + if (op->reg == 15) { + fprintf(st, "&0x%x", op->embedded.w); + } else { + cpu_register_name(op->reg, reg_name, 8); + fprintf(st, "%s", reg_name); + } + break; + case 5: + if (op->reg == 15) { + fprintf(st, "&0x%x", op->embedded.w); + } else { + cpu_register_name(op->reg, reg_name, 8); + fprintf(st, "(%s)", reg_name); + } + break; + case 6: /* FP Short Offset */ + if (op->reg == 15) { + fprintf(st, "&0x%x", op->embedded.w); + } else { + fprintf(st, "%d(%%fp)", op->reg); + } + break; + case 7: /* AP Short Offset */ + if (op->reg == 15) { + fprintf(st, "$0x%x", op->embedded.w); + } else { + fprintf(st, "%d(%%ap)", op->embedded.w); + } + break; + case 8: + cpu_register_name(op->reg, reg_name, 8); + fprintf(st, "0x%x(%s)", (int32)op->embedded.w, reg_name); + break; + case 9: + cpu_register_name(op->reg, reg_name, 8); + fprintf(st, "*0x%x(%s)", (int32)op->embedded.w, reg_name); + break; + case 10: + cpu_register_name(op->reg, reg_name, 8); + fprintf(st, "0x%x(%s)", (int16)op->embedded.w, reg_name); + break; + case 11: + cpu_register_name(op->reg, reg_name, 8); + fprintf(st, "*0x%x(%s)", (int16)op->embedded.w, reg_name); + break; + case 12: + cpu_register_name(op->reg, reg_name, 8); + fprintf(st, "%d(%s)", (int8)op->embedded.w, reg_name); + break; + case 13: + cpu_register_name(op->reg, reg_name, 8); + fprintf(st, "*%d(%s)", (int8)op->embedded.w, reg_name); + break; + case 14: + if (op->reg == 15) { + fprintf(st, "*$0x%x", op->embedded.w); + } + break; + case 15: + fprintf(st, "&0x%x", (int32)op->embedded.w); + break; +#if defined(REV3) + case 0x10: /* Auto pre-decrement */ + cpu_register_name(op->reg, reg_name, 8); + fprintf(st, "-(%s)", reg_name); + break; + case 0x12: /* Auto post-decrement */ + cpu_register_name(op->reg, reg_name, 8); + fprintf(st, "(%s)-", reg_name); + break; + case 0x14: /* Auto pre-increment */ + cpu_register_name(op->reg, reg_name, 8); + fprintf(st, "+(%s)", reg_name); + break; + case 0x16: /* Auto post-increment */ + cpu_register_name(op->reg, reg_name, 8); + fprintf(st, "(%s)+", reg_name); + break; + case 0xab: + cpu_register_name(op->reg, reg_name, 8); + cpu_register_name(op->reg2, reg2_name, 8); + fprintf(st, "%d(%s,%s)", (int8)op->embedded.b, reg2_name, reg_name); + break; + case 0xbb: + cpu_register_name(op->reg, reg_name, 8); + cpu_register_name(op->reg2, reg2_name, 8); + fprintf(st, "0x%x(%s,%s)", op->embedded.h, reg2_name, reg_name); + break; + case 0xdb: + cpu_register_name(op->reg, reg_name, 8); + cpu_register_name(op->reg2, reg2_name, 8); + fprintf(st, "%s[%s]", reg2_name, reg_name); + break; +#endif + } +} + +t_stat cpu_set_size(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + uint32 uval = (uint32) val; + uint8 *nRAM = NULL; + + if ((val <= 0) || (val > MAXMEMSIZE)) { + return SCPE_ARG; + } + + /* Do (re-)allocation for memory. */ + nRAM = (uint8 *) calloc(uval, sizeof(uint32)); + + if (nRAM == NULL) { + return SCPE_MEM; + } + + free(RAM); + RAM = nRAM; + memset(RAM, 0, (size_t)uval * sizeof(uint32)); + + MEM_SIZE = uval; + + return SCPE_OK; +} + +static SIM_INLINE void clear_instruction(instr *inst) +{ + uint8 i; + + inst->mn = NULL; + inst->psw = 0; + inst->sp = 0; + inst->pc = 0; + + for (i = 0; i < 4; i++) { + inst->operands[i].mode = 0; + inst->operands[i].reg = 0; + inst->operands[i].dtype = -1; + inst->operands[i].etype = -1; + inst->operands[i].embedded.w = 0; + inst->operands[i].data = 0; + } +} + +/* + * Decode a single descriptor-defined operand from the instruction + * stream. Returns the number of bytes consumed during decode. + */ +static uint8 decode_operand(uint32 pa, instr *instr, uint8 op_number, int8 *etype) +{ + uint8 desc; + uint8 offset = 0; + operand *oper = &instr->operands[op_number]; + + /* Read in the descriptor byte */ + desc = read_b(pa + offset++, ACC_IF, BUS_CPU); + +#if defined(REV3) + /* + * Handle addressing modes specific to the WE 32200, not + * implemented in the WE 32100 + */ + switch (desc) { + case 0x5b: /* Auto post / pre-increment */ + desc = read_b(pa + offset++, ACC_IF, BUS_CPU); + /* + * Mode 0x10: Auto pre-decrement -(%rx) + * Mode 0x12: Auto post-decrement (%rx)- + * Mode 0x14: Auto pre-increment +(%rx) + * Mode 0x16: Auto post-increment (%rx)- + */ + oper->mode = ((desc >> 5) & 0x7) | 0x10; + oper->reg = desc & 0x1f; + break; + case 0xab: /* Indexed with byte displacement */ + oper->mode = 0xab; + desc = read_b(pa + offset++, ACC_IF, BUS_CPU); + oper->reg = (desc >> 4) & 0xf; + oper->reg2 = (desc & 0xf) + 16; + break; + case 0xbb: /* Indexed with halfword displacement */ + oper->mode = desc; + desc = read_b(pa + offset++, ACC_IF, BUS_CPU); + oper->reg = (desc >> 4) & 0xf; + oper->reg2 = (desc & 0xf) + 16; + break; + case 0xcb: /* Extended format 1: r16 - r31 */ + desc = read_b(pa + offset++, ACC_IF, BUS_CPU); + oper->mode = (desc >> 4) & 0xf; + oper->reg = (desc & 0xf) + 16; + break; + case 0xdb: /* Indexed with scaling */ + oper->mode = desc; + desc = read_b(pa + offset++, ACC_IF, BUS_CPU); + oper->reg = (desc >> 4) & 0xf; + oper->reg2 = (desc & 0xf) + 16; + break; + default: + oper->mode = (desc >> 4) & 0xf; + oper->reg = desc & 0xf; + break; + } +#else + /* + * WE 32100 only + */ + oper->mode = (desc >> 4) & 0xf; + oper->reg = desc & 0xf; +#endif + + oper->dtype = instr->mn->dtype; + oper->etype = *etype; + + switch (oper->mode) { + case 0: /* Positive Literal */ + case 1: /* Positive Literal */ + case 2: /* Positive Literal */ + case 3: /* Positive Literal */ + case 15: /* Negative literal */ + oper->embedded.b = desc; + oper->data = oper->embedded.b; + break; + case 4: /* Word Immediate, Register Mode */ + switch (oper->reg) { + case 15: /* Word Immediate */ + oper->embedded.w = (uint32) read_b(pa + offset++, ACC_IF, BUS_CPU); + oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8u; + oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 16u; + oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 24u; + oper->data = oper->embedded.w; + break; + default: /* Register mode */ + oper->data = R[oper->reg]; + break; + } + break; + case 5: /* Halfword Immediate, Register Deferred Mode */ + switch (oper->reg) { + case 15: /* Halfword Immediate */ + oper->embedded.h = (uint16) read_b(pa + offset++, ACC_IF, BUS_CPU); + oper->embedded.h |= ((uint16) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8u; + oper->data = oper->embedded.h; + break; + case 11: /* INVALID */ + cpu_abort(NORMAL_EXCEPTION, INVALID_DESCRIPTOR); + return offset; + default: /* Register deferred mode */ + oper->data = R[oper->reg]; + break; + } + break; + case 6: /* Byte Immediate, FP Short Offset */ + switch (oper->reg) { + case 15: /* Byte Immediate */ + oper->embedded.b = read_b(pa + offset++, ACC_IF, BUS_CPU); + oper->data = oper->embedded.b; + break; + default: /* FP Short Offset */ + oper->embedded.b = oper->reg; + oper->data = oper->embedded.b; + break; + } + break; + case 7: /* Absolute, AP Short Offset */ + switch (oper->reg) { + case 15: /* Absolute */ + oper->embedded.w = (uint32) read_b(pa + offset++, ACC_IF, BUS_CPU); + oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8u; + oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 16u; + oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 24u; + oper->data = oper->embedded.w; + break; + default: /* AP Short Offset */ + oper->embedded.b = oper->reg; + oper->data = oper->embedded.b; + break; + } + break; + case 8: /* Word Displacement */ + case 9: /* Word Displacement Deferred */ + oper->embedded.w = (uint32) read_b(pa + offset++, ACC_IF, BUS_CPU); + oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8u; + oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 16u; + oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 24u; + oper->data = oper->embedded.w; + break; + case 10: /* Halfword Displacement */ + case 11: /* Halfword Displacement Deferred */ + oper->embedded.h = read_b(pa + offset++, ACC_IF, BUS_CPU); + oper->embedded.h |= ((uint16) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8u; + oper->data = oper->embedded.h; + break; + case 12: /* Byte Displacement */ + case 13: /* Byte Displacement Deferred */ + oper->embedded.b = read_b(pa + offset++, ACC_IF, BUS_CPU); + oper->data = oper->embedded.b; + break; + case 14: /* Absolute Deferred, Extended-Operand */ + switch (oper->reg) { + case 15: /* Absolute Deferred */ + oper->embedded.w = (uint32) read_b(pa + offset++, ACC_IF, BUS_CPU); + oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8u; + oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 16u; + oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 24u; + break; + case 0: + case 2: + case 3: + case 4: + case 6: + case 7: /* Expanded Datatype */ + /* Recursively decode the remainder of the operand after + storing the expanded datatype */ + *etype = (int8) oper->reg; + oper->etype = *etype; + offset += decode_operand(pa + offset, instr, op_number, etype); + break; + default: + cpu_abort(NORMAL_EXCEPTION, RESERVED_DATATYPE); + break; + } + break; +#if defined(REV3) + case 0x10: /* Auto pre-decrement */ + case 0x12: /* Auto post-decrement */ + case 0x14: /* Auto pre-increment */ + case 0x16: /* Auto post-increment */ + oper->data = R[oper->reg]; + break; + case 0xab: /* Indexed with byte displacement */ + oper->embedded.b = read_b(pa + offset++, ACC_IF, BUS_CPU); + oper->data = oper->embedded.b; + break; + case 0xbb: /* Indexed with halfword displacement */ + oper->embedded.h = read_b(pa + offset++, ACC_IF, BUS_CPU); + oper->embedded.h |= ((uint16) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8u; + oper->data = oper->embedded.h; + break; + case 0xdb: /* Indexed with scaling */ + switch (op_type(oper)) { + case BT: + case SB: + oper->data = R[oper->reg]; + break; + case HW: + case UH: + oper->data = R[oper->reg] * 2; + break; + case WD: + case UW: + oper->data = R[oper->reg] * 4; + break; + } + + oper->data += R[oper->reg2]; + + break; +#endif + default: + cpu_abort(NORMAL_EXCEPTION, INVALID_DESCRIPTOR); + } + + return offset; +} + +/* + * Decode the instruction currently being pointed at by the PC. + * This routine does the following: + * 1. Read the opcode. + * 2. Determine the number of operands to decode based on + * the opcode type. + * 3. Fetch each opcode from main memory. + * + * This routine is guaranteed not to change state. + * + * returns: a Normal Exception if an error occured, or 0 on success. + */ +uint8 decode_instruction(instr *instr) +{ + uint8 offset = 0; + uint8 b1, b2; + uint16 hword_op; + uint32 pa; + mnemonic *mn = NULL; + int i; + int8 etype = -1; /* Expanded datatype (if any) */ + + clear_instruction(instr); + + pa = R[NUM_PC]; + + /* Store off the PC and and PSW for history keeping */ + instr->psw = R[NUM_PSW]; + instr->sp = R[NUM_SP]; + instr->pc = pa; + + if (read_operand(pa + offset++, &b1) != SCPE_OK) { + /* We tried to read out of a page that doesn't exist. We + need to let the operating system handle it.*/ + cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); + return offset; + } + + /* It should never, ever happen that operand fetch + would cause a page fault. */ + + if (b1 == 0x30) { + read_operand(pa + offset++, &b2); + hword_op = (uint16) ((uint16)b1 << 8) | (uint16) b2; + for (i = 0; i < HWORD_OP_COUNT; i++) { + if (hword_ops[i].opcode == hword_op) { + mn = &hword_ops[i]; + break; + } + } + } else { + mn = &ops[b1]; + } + + if (mn == NULL) { + cpu_abort(NORMAL_EXCEPTION, ILLEGAL_OPCODE); + return offset; + } + + instr->mn = mn; + + if (mn->op_count < 0) { + cpu_abort(NORMAL_EXCEPTION, ILLEGAL_OPCODE); + return offset; + } + + if (mn->op_count == 0) { + /* Nothing else to do, we're done decoding. */ + return offset; + } + + switch (mn->mode) { + case OP_BYTE: + instr->operands[0].embedded.b = read_b(pa + offset++, ACC_IF, BUS_CPU); + instr->operands[0].mode = 6; + instr->operands[0].reg = 15; + break; + case OP_HALF: + instr->operands[0].embedded.h = read_b(pa + offset++, ACC_IF, BUS_CPU); + instr->operands[0].embedded.h |= (uint16)(read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8; + instr->operands[0].mode = 5; + instr->operands[0].reg = 15; + break; + case OP_COPR: + instr->operands[0].embedded.w = (uint32) read_b(pa + offset++, ACC_IF, BUS_CPU); + instr->operands[0].embedded.w |= (uint32) read_b(pa + offset++, ACC_IF, BUS_CPU) << 8; + instr->operands[0].embedded.w |= (uint32) read_b(pa + offset++, ACC_IF, BUS_CPU) << 16; + instr->operands[0].embedded.w |= (uint32) read_b(pa + offset++, ACC_IF, BUS_CPU) << 24; + instr->operands[0].mode = 4; + instr->operands[0].reg = 15; + + /* Decode subsequent operands */ + for (i = 1; i < mn->op_count; i++) { + offset += decode_operand(pa + offset, instr, (uint8) i, &etype); + } + + break; + case OP_DESC: + for (i = 0; i < mn->op_count; i++) { + offset += decode_operand(pa + offset, instr, (uint8) i, &etype); + } + break; +#if defined(REV3) + case OP_DESB: + /* Operand 0 is a descriptor byte */ + offset += decode_operand(pa + offset, instr, 0, &etype); + /* Operand 1 is a signed byte offset */ + instr->operands[1].embedded.b = read_b(pa + offset++, ACC_IF, BUS_CPU); + break; + case OP_DESH: + /* Operand 0 is a descriptor byte */ + offset += decode_operand(pa + offset, instr, 0, &etype); + /* Operand 1 is a signed byte offset */ + instr->operands[1].embedded.h = read_b(pa + offset++, ACC_IF, BUS_CPU); + instr->operands[1].embedded.h |= (uint16)(read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8; + break; +#endif + default: + break; + } + + return offset; +} + +static SIM_INLINE void cpu_context_switch_3(uint32 new_pcbp) +{ + if (R[NUM_PSW] & PSW_R_MASK) { + + R[0] = R[NUM_PCBP] + 64; + R[2] = read_w(R[0], ACC_AF, BUS_CPU); + R[0] += 4; + + while (R[2] != 0) { + R[1] = read_w(R[0], ACC_AF, BUS_CPU); + R[0] += 4; + + /* Execute MOVBLW instruction inside this loop */ + while (R[2] != 0) { + write_w(R[1], read_w(R[0], ACC_AF, BUS_CPU), BUS_CPU); + R[2]--; + R[0] += 4; + R[1] += 4; + } + + R[2] = read_w(R[0], ACC_AF, BUS_CPU); + R[0] += 4; + } + + R[0] = R[0] + 4; + } +} + +static SIM_INLINE void cpu_context_switch_2(uint32 new_pcbp) +{ + R[NUM_PCBP] = new_pcbp; + + /* Put new PSW, PC and SP values from PCB into registers */ + R[NUM_PSW] = read_w(R[NUM_PCBP], ACC_AF, BUS_CPU); + R[NUM_PSW] &= ~PSW_TM_MASK; /* Clear TM */ + R[NUM_PC] = read_w(R[NUM_PCBP] + 4, ACC_AF, BUS_CPU); + R[NUM_SP] = read_w(R[NUM_PCBP] + 8, ACC_AF, BUS_CPU); + + /* If i-bit is set, increment PCBP past initial context area */ + if (R[NUM_PSW] & PSW_I_MASK) { + R[NUM_PSW] &= ~PSW_I_MASK; + R[NUM_PCBP] += 12; + } +} + +static SIM_INLINE void cpu_context_switch_1(uint32 new_pcbp) +{ + /* Save the current PC in PCB */ + write_w(R[NUM_PCBP] + 4, R[NUM_PC], BUS_CPU); + + /* Copy the 'R' flag from the new PSW to the old PSW */ + R[NUM_PSW] &= ~PSW_R_MASK; + R[NUM_PSW] |= (read_w(new_pcbp, ACC_AF, BUS_CPU) & PSW_R_MASK); + + /* Save current PSW and SP in PCB */ + write_w(R[NUM_PCBP], R[NUM_PSW], BUS_CPU); + write_w(R[NUM_PCBP] + 8, R[NUM_SP], BUS_CPU); + + /* If R is set, save current R0-R8/FP/AP in PCB */ + if (R[NUM_PSW] & PSW_R_MASK) { + write_w(R[NUM_PCBP] + 24, R[NUM_FP], BUS_CPU); + write_w(R[NUM_PCBP] + 28, R[0], BUS_CPU); + write_w(R[NUM_PCBP] + 32, R[1], BUS_CPU); + write_w(R[NUM_PCBP] + 36, R[2], BUS_CPU); + write_w(R[NUM_PCBP] + 40, R[3], BUS_CPU); + write_w(R[NUM_PCBP] + 44, R[4], BUS_CPU); + write_w(R[NUM_PCBP] + 48, R[5], BUS_CPU); + write_w(R[NUM_PCBP] + 52, R[6], BUS_CPU); + write_w(R[NUM_PCBP] + 56, R[7], BUS_CPU); + write_w(R[NUM_PCBP] + 60, R[8], BUS_CPU); + write_w(R[NUM_PCBP] + 20, R[NUM_AP], BUS_CPU); + + R[NUM_FP] = R[NUM_PCBP] + 52; + } +} + +void cpu_on_interrupt(uint16 vec) +{ + uint32 new_psw_ptr, new_psw, new_pc; /* Quick-Interrupt */ + uint32 new_pcbp, new_pcbp_ptr; /* Full-Interrupt */ + t_bool quick; + + cpu_int_ack = vec; + + quick = (R[NUM_PSW] & PSW_QIE_MASK) != 0; + + sim_debug(IRQ_MSG, &cpu_dev, + "[%08x] [cpu_on_interrupt] vec=%02x (%d), quick=%d, sbd_int_req = %x, csr_data = %x\n", + R[NUM_PC], vec, vec, quick, sbd_int_req, csr_data); + + /* + * "If a nonmaskable interrupt request is received, an auto-vector + * interrupt acknowledge cycle is performed (as if an autovector + * interrupt at level 0 was being acknowledged) and no + * Interrupt-ID is fetched. The value 0 is used as the ID." + */ + if (cpu_nmi) { + vec = 0; + cpu_nmi = FALSE; + } + + cpu_km = TRUE; + + if (quick) { + /* + * Quick interrupt microsequence + */ + new_psw_ptr = (uint32)0x48c + (8 * (uint32)vec); + + /* Set ISC, TM, and ET to 2, 0, 0 before saving */ + R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); + R[NUM_PSW] |= (2 << PSW_ISC); + + abort_context = C_RESET_INT_STACK; + + write_w(R[NUM_ISP], R[NUM_PC], BUS_CPU); + write_w(R[NUM_ISP] + 4, R[NUM_PSW], BUS_CPU); + + /* Set ISC, TM, and ET to 1, 0, 0 */ + R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); + R[NUM_PSW] |= (2 << PSW_ISC); + + abort_context = C_RESET_SYSTEM_DATA; + + new_psw = read_w(new_psw_ptr, ACC_AF, BUS_CPU); + + /* Clear out PSW state */ + R[NUM_PSW] &= ~(QIE_PSW_MASK); + + /* Set PM to CM and clear out old CM */ + R[NUM_PSW] |= (R[NUM_PSW] & PSW_CM_MASK) >> 2; + R[NUM_PSW] &= ~(PSW_CM_MASK); + + /* Copy from new PSW */ + R[NUM_PSW] |= (new_psw & QIE_PSW_MASK); + + /* Grab the new PC */ + new_pc = read_w(new_psw_ptr + 4, ACC_AF, BUS_CPU); + + /* Finish ISP stack push */ + R[NUM_ISP] = R[NUM_ISP] + 8; + + /* Set new PSW ISC/TM/ET to 7/0/3 */ + R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); + R[NUM_PSW] |= 7 << PSW_ISC; + R[NUM_PSW] |= 3 << PSW_ET; + + /* Set new PC */ + R[NUM_PC] = new_pc; + + /* Done */ + abort_context = C_NONE; + } else { + /* + * Full interrupt microsequence + */ + new_pcbp_ptr = (uint32)0x8c + (4 * (uint32)vec); + + abort_context = C_RESET_SYSTEM_DATA; + + new_pcbp = read_w(new_pcbp_ptr, ACC_AF, BUS_CPU); + + abort_context = C_RESET_INT_STACK; + + /* Save the old PCBP */ + irq_push_word(R[NUM_PCBP]); + + /* Set ISC, TM, and ET to 0, 0, 1 before saving */ + R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); + R[NUM_PSW] |= (1 << PSW_ET); + + /* Context switch */ + cpu_context_switch_1(new_pcbp); + cpu_context_switch_2(new_pcbp); + + /* Set ISC, TM, and ET to 7, 0, 3 in new PSW */ + R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); + R[NUM_PSW] |= (7 << PSW_ISC); + R[NUM_PSW] |= (3 << PSW_ET); + + cpu_context_switch_3(new_pcbp); + + abort_context = C_NONE; + } + + cpu_km = FALSE; +} + +t_stat sim_instr(void) +{ + uint8 et, isc, trap; + + /* Temporary register used for overflow detection */ + t_uint64 result; + + /* Scratch space */ + uint32 a, b, c, d; + + /* Used for field calculation */ + uint32 width, offset; + uint32 mask; + + /* Generic index */ + uint32 i; + + /* Interrupt request IPL */ + uint8 ipl; + + /* Used by oprocessor instructions */ + uint32 coprocessor_word; + + operand *src1, *src2, *src3, *dst; + + stop_reason = 0; + + abort_reason = (uint32) setjmp(save_env); + + /* Exception handler. + * + * This gets a little messy because of exception contexts. If a + * normal-exception happens while we're handling a + * normal-exception, it needs to be treated as a stack-exception. + */ + if (abort_reason != 0) { + if (cpu_exception_stack_depth++ >= 10) { + return STOP_ESTK; + } + + if (cpu_unit.flags & UNIT_EXBRK) { + return STOP_EX; + } + + et = R[NUM_PSW] & PSW_ET_MASK; + isc = (R[NUM_PSW] & PSW_ISC_MASK) >> PSW_ISC; + + if (abort_reason == ABORT_EXC) { + switch(abort_context) { + case C_NORMAL_GATE_VECTOR: + cpu_on_normal_exception(N_GATE_VECTOR); + break; + case C_PROCESS_GATE_PCB: + cpu_on_process_exception(GATE_PCB_FAULT); + break; + case C_PROCESS_OLD_PCB: + cpu_on_process_exception(OLD_PCB_FAULT); + break; + case C_PROCESS_NEW_PCB: + cpu_on_process_exception(NEW_PCB_FAULT); + break; + case C_STACK_FAULT: + cpu_on_stack_exception(STACK_FAULT); + break; + case C_RESET_GATE_VECTOR: + cpu_on_reset_exception(GATE_VECTOR_FAULT); + break; + case C_RESET_SYSTEM_DATA: + cpu_on_reset_exception(SYSTEM_DATA_FAULT); + break; + case C_RESET_INT_STACK: + cpu_on_reset_exception(INTERRUPT_STACK_FAULT); + break; + default: + switch(et) { + case NORMAL_EXCEPTION: + cpu_on_normal_exception(isc); + break; + case STACK_EXCEPTION: + cpu_on_stack_exception(isc); + break; + case RESET_EXCEPTION: + cpu_on_reset_exception(isc); + break; + default: + stop_reason = STOP_EX; + break; + } + break; + } + } + /* Traps are handled at the end of instruction execution */ + } + + while (stop_reason == 0) { + trap = 0; + abort_context = C_NONE; + + if (sim_brk_summ && sim_brk_test(R[NUM_PC], SWMASK ('E'))) { + stop_reason = STOP_IBKPT; + break; + } + + if (cpu_exception_stack_depth > 0) { + cpu_exception_stack_depth--; + } + + AIO_CHECK_EVENT; + + if (sim_interval-- <= 0) { + if ((stop_reason = sim_process_event())) { + break; + } + } + + /* Process DMA requests */ + dmac_service_drqs(); + + /* + * Post-increment IU mode pointers (if needed). + * + * This is essentially a colossal hack. We never want to + * increment these pointers during an interlocked Read/Write + * operation, so we only increment after a CPU step has + * occured. + */ + if (iu_increment_a) { + increment_modep_a(); + } + if (iu_increment_b) { + increment_modep_b(); + } + + /* Interrupt Handling + * + * - NMI is always serviced first. + * - SBD interrupts are handled next in priority. + * - IO Bus boards are handled last. + */ + if (cpu_nmi) { + cpu_nmi = FALSE; + cpu_in_wait = FALSE; + cpu_on_interrupt(0); + } else if (cio_int_req) { + for (i = 0; i < CIO_SLOTS; i++) { + if ((cio_int_req & (1 << i)) && (PSW_CUR_IPL < cio[i].ipl)) { + cpu_in_wait = FALSE; + CIO_CLR_INT(i); + cpu_on_interrupt(cio[i].ivec); + break; + } + } + } else if (sbd_int_req) { + ipl = int_map[sbd_int_req]; + if (PSW_CUR_IPL < ipl) { + /* For the system board, interrupt vector is always + equal to IPL */ + cpu_in_wait = FALSE; + cpu_on_interrupt(ipl); + } + } + + if (cpu_in_wait) { + sim_idle(TMR_CLK, TRUE); + continue; + } + + /* Reset the TM bits */ + /* TODO: Figure out why we were doing this! */ + /* R[NUM_PSW] |= PSW_TM_MASK; */ + + /* Record the instruction for history */ + if (cpu_hist_size > 0) { + cpu_instr = &INST[cpu_hist_p]; + cpu_hist_p = (cpu_hist_p + 1) % cpu_hist_size; + } else { + cpu_instr = &inst; + } + + /* Decode the instruction */ + pc_incr = decode_instruction(cpu_instr); + + /* Make sure to update the valid bit for history keeping (if + * enabled) */ + cpu_instr->valid = TRUE; + + /* + * Operate on the decoded instruction. + */ + + /* Special case for coprocessor instructions */ + if (cpu_instr->mn->mode == OP_COPR) { + coprocessor_word = cpu_instr->operands[0].embedded.w; + } + + /* Get the operands */ + if (cpu_instr->mn->src_op1 >= 0) { + src1 = &cpu_instr->operands[cpu_instr->mn->src_op1]; + } + + if (cpu_instr->mn->src_op2 >= 0) { + src2 = &cpu_instr->operands[cpu_instr->mn->src_op2]; + } + + if (cpu_instr->mn->src_op3 >= 0) { + src3 = &cpu_instr->operands[cpu_instr->mn->src_op3]; + } + + if (cpu_instr->mn->dst_op >= 0) { + dst = &cpu_instr->operands[cpu_instr->mn->dst_op]; + } + + switch (cpu_instr->mn->opcode) { + case ADDW2: + case ADDH2: + case ADDB2: + a = cpu_read_op(src1); + b = cpu_read_op(dst); + add(a, b, dst); + break; + case ADDW3: + case ADDH3: + case ADDB3: + a = cpu_read_op(src1); + b = cpu_read_op(src2); + add(a, b, dst); + break; + case ALSW3: + a = cpu_read_op(src2); + b = cpu_read_op(src1); + result = (t_uint64)a << (b & 0x1f); + cpu_write_op(dst, result); + cpu_set_nz_flags(result, dst); + cpu_set_c_flag(0); + cpu_set_v_flag_op(result, dst); + break; + case ANDW2: + case ANDH2: + case ANDB2: + a = cpu_read_op(src1); + b = cpu_read_op(dst); + c = a & b; + cpu_write_op(dst, c); + cpu_set_nz_flags(c, dst); + cpu_set_c_flag(0); + cpu_set_v_flag_op(c, dst); + break; + case ANDW3: + case ANDH3: + case ANDB3: + a = cpu_read_op(src1); + b = cpu_read_op(src2); + c = a & b; + cpu_write_op(dst, c); + cpu_set_nz_flags(c, dst); + cpu_set_c_flag(0); + cpu_set_v_flag_op(c, dst); + break; + case BEH: + case BEH_D: + if (cpu_z_flag() == 1) { + pc_incr = sign_extend_h(dst->embedded.h); + } + break; + case BEB: + case BEB_D: + if (cpu_z_flag() == 1) { + pc_incr = sign_extend_b(dst->embedded.b); + } + break; + case BGH: + if ((cpu_n_flag() | cpu_z_flag()) == 0) { + pc_incr = sign_extend_h(dst->embedded.h); + } + break; + case BGB: + if ((cpu_n_flag() | cpu_z_flag()) == 0) { + pc_incr = sign_extend_b(dst->embedded.b); + } + break; + case BGEH: + if ((cpu_n_flag() == 0) | (cpu_z_flag() == 1)) { + pc_incr = sign_extend_h(dst->embedded.h); + } + break; + case BGEB: + if ((cpu_n_flag() == 0) | (cpu_z_flag() == 1)) { + pc_incr = sign_extend_b(dst->embedded.b); + } + break; + case BGEUH: + if (cpu_c_flag() == 0) { + pc_incr = sign_extend_h(dst->embedded.h); + } + break; + case BGEUB: + if (cpu_c_flag() == 0) { + pc_incr = sign_extend_b(dst->embedded.b); + } + break; + case BGUH: + if ((cpu_c_flag() | cpu_z_flag()) == 0) { + pc_incr = sign_extend_h(dst->embedded.h); + } + break; + case BGUB: + if ((cpu_c_flag() | cpu_z_flag()) == 0) { + pc_incr = sign_extend_b(dst->embedded.b); + } + break; + case BITW: + case BITH: + case BITB: + a = cpu_read_op(src1); + b = cpu_read_op(src2); + c = a & b; + cpu_set_nz_flags(c, src1); + cpu_set_c_flag(0); + cpu_set_v_flag(0); + break; + case BLH: + if ((cpu_n_flag() == 1) && (cpu_z_flag() == 0)) { + pc_incr = sign_extend_h(dst->embedded.h); + } + break; + case BLB: + if ((cpu_n_flag() == 1) && (cpu_z_flag() == 0)) { + pc_incr = sign_extend_b(dst->embedded.b); + } + break; + case BLEH: + if ((cpu_n_flag() | cpu_z_flag()) == 1) { + pc_incr = sign_extend_h(dst->embedded.h); + } + break; + case BLEB: + if ((cpu_n_flag() | cpu_z_flag()) == 1) { + pc_incr = sign_extend_b(dst->embedded.b); + } + break; + case BLEUH: + if ((cpu_c_flag() | cpu_z_flag()) == 1) { + pc_incr = sign_extend_h(dst->embedded.h); + } + break; + case BLEUB: + if ((cpu_c_flag() | cpu_z_flag()) == 1) { + pc_incr = sign_extend_b(dst->embedded.b); + } + break; + case BLUH: + if (cpu_c_flag() == 1) { + pc_incr = sign_extend_h(dst->embedded.h); + } + break; + case BLUB: + if (cpu_c_flag() == 1) { + pc_incr = sign_extend_b(dst->embedded.b); + } + break; + case BNEH: + case BNEH_D: + if (cpu_z_flag() == 0) { + pc_incr = sign_extend_h(dst->embedded.h); + } + break; + case BNEB: + case BNEB_D: + if (cpu_z_flag() == 0) { + pc_incr = sign_extend_b(dst->embedded.b); + } + break; + case BPT: + trap = BREAKPOINT_TRAP; + break; + case BRH: + pc_incr = sign_extend_h(dst->embedded.h); + break; + case BRB: + pc_incr = sign_extend_b(dst->embedded.b); + /* BRB is commonly used to halt the processor in a tight + * infinite loop. */ + if (pc_incr == 0) { + stop_reason = STOP_LOOP; + } + break; + case BSBH: + cpu_push_word(R[NUM_PC] + pc_incr); + pc_incr = sign_extend_h(dst->embedded.h); + break; + case BSBB: + cpu_push_word(R[NUM_PC] + pc_incr); + pc_incr = sign_extend_b(dst->embedded.b); + break; + case BVCH: + if (cpu_v_flag() == 0) { + pc_incr = sign_extend_h(dst->embedded.h); + } + break; + case BVCB: + if (cpu_v_flag() == 0) { + pc_incr = sign_extend_b(dst->embedded.b); + } + break; + case BVSH: + if (cpu_v_flag() == 1) { + pc_incr = sign_extend_h(dst->embedded.h); + } + break; + case BVSB: + if (cpu_v_flag() == 1) { + pc_incr = sign_extend_b(dst->embedded.b); + } + break; + case CALL: + a = cpu_effective_address(src1); + b = cpu_effective_address(dst); + write_w(R[NUM_SP] + 4, R[NUM_AP], BUS_CPU); + write_w(R[NUM_SP], R[NUM_PC] + pc_incr, BUS_CPU); + R[NUM_SP] += 8; + R[NUM_PC] = b; + R[NUM_AP] = a; + pc_incr = 0; + break; + case CFLUSH: + break; + case CALLPS: + if (cpu_execution_level() != EX_LVL_KERN) { + cpu_abort(NORMAL_EXCEPTION, PRIVILEGED_OPCODE); + break; + } + + a = R[0]; + + cpu_km = TRUE; + + abort_context = C_RESET_INT_STACK; + + irq_push_word(R[NUM_PCBP]); + + /* Set current PC to start of next instruction (always PC+2) */ + R[NUM_PC] += 2; + + /* Set old PSW ISC, TM, and ET to 0, 0, 1 */ + R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); + R[NUM_PSW] |= (1 << PSW_ET); + + cpu_context_switch_1(a); + abort_context = C_PROCESS_NEW_PCB; + cpu_context_switch_2(a); + + R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); + R[NUM_PSW] |= (7 << PSW_ISC); + R[NUM_PSW] |= (3 << PSW_ET); + + cpu_context_switch_3(a); + + abort_context = C_NONE; + + cpu_km = FALSE; + pc_incr = 0; + break; + case CLRW: + case CLRH: + case CLRB: + cpu_write_op(dst, 0); + cpu_set_n_flag(0); + cpu_set_z_flag(1); + cpu_set_c_flag(0); + cpu_set_v_flag(0); + break; + case CMPW: + case CMPH: + case CMPB: + a = cpu_read_op(src1); + b = cpu_read_op(src2); + + switch(op_type(src2)) { + case WD: + case UW: + cpu_set_n_flag((int32)b < (int32)a); + break; + case HW: + case UH: + cpu_set_n_flag((int16)b < (int16)a); + break; + case BT: + case SB: + cpu_set_n_flag((int8)b < (int8)a); + break; + default: + /* Unreachable */ + break; + } + + cpu_set_z_flag(b == a); + cpu_set_c_flag(b < a); + cpu_set_v_flag(0); + break; + case DECW: + case DECH: + case DECB: + a = cpu_read_op(dst); + sub(a, 1, dst); + break; + case DIVW2: + a = cpu_read_op(src1); + b = cpu_read_op(dst); + + if (a == 0) { + cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); + break; + } + + if (a == WORD_MASK && b == WD_MSB) { + cpu_set_v_flag(1); + } + + DIV(a, b, src1, dst, int32); + + cpu_write_op(dst, result); + cpu_set_nz_flags(result, dst); + cpu_set_c_flag(0); + break; + case DIVH2: + a = cpu_read_op(src1); + b = cpu_read_op(dst); + + if (a == 0) { + cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); + break; + } + + if (a == HALF_MASK && b == HW_MSB) { + cpu_set_v_flag(1); + } + + DIV(a, b, src1, dst, int16); + + cpu_write_op(dst, result); + cpu_set_nz_flags(result, dst); + cpu_set_c_flag(0); + break; + case DIVB2: + a = cpu_read_op(src1); + b = cpu_read_op(dst); + + if (a == 0) { + cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); + break; + } + + if (a == BYTE_MASK && b == BT_MSB) { + cpu_set_v_flag(1); + } + + result = (uint8)b / (uint8)a; + + cpu_write_op(dst, result); + cpu_set_nz_flags(result, dst); + cpu_set_c_flag(0); + break; + case DIVW3: + a = cpu_read_op(src1); + b = cpu_read_op(src2); + + if (a == 0) { + cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); + break; + } + + if (a == WORD_MASK && b == WD_MSB) { + cpu_set_v_flag(1); + } + + DIV(a, b, src1, src2, int32); + + cpu_write_op(dst, result); + cpu_set_nz_flags(result, dst); + cpu_set_c_flag(0); + break; + case DIVH3: + a = cpu_read_op(src1); + b = cpu_read_op(src2); + + if (a == 0) { + cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); + break; + } + + if (a == HALF_MASK && b == HW_MSB) { + cpu_set_v_flag(1); + } + + DIV(a, b, src1, src2, int16); + + cpu_write_op(dst, result); + cpu_set_nz_flags(result, dst); + cpu_set_c_flag(0); + break; + case DIVB3: + a = cpu_read_op(src1); + b = cpu_read_op(src2); + + if (a == 0) { + cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); + break; + } + + if (a == BYTE_MASK && b == BT_MSB) { + cpu_set_v_flag(1); + } + + result = (uint8)b / (uint8)a; + + cpu_write_op(dst, result); + cpu_set_nz_flags(result, dst); + cpu_set_c_flag(0); + break; + case MVERNO: + R[0] = CPU_VERSION; + break; + case ENBVJMP: + if (cpu_execution_level() != EX_LVL_KERN) { + cpu_abort(NORMAL_EXCEPTION, PRIVILEGED_OPCODE); + break; + } + mmu_enable(); + R[NUM_PC] = R[0]; + pc_incr = 0; + break; + case DISVJMP: + if (cpu_execution_level() != EX_LVL_KERN) { + cpu_abort(NORMAL_EXCEPTION, PRIVILEGED_OPCODE); + break; + } + mmu_disable(); + R[NUM_PC] = R[0]; + pc_incr = 0; + break; + case EXTFW: + case EXTFH: + case EXTFB: + width = (cpu_read_op(src1) & 0x1f) + 1; + offset = cpu_read_op(src2) & 0x1f; + if (width >= 32) { + mask = -1; + } else { + mask = (1ul << width) - 1; + } + mask = mask << offset; + + if (width + offset > 32) { + mask |= (1ul << ((width + offset) - 32)) - 1; + } + + a = cpu_read_op(src3); /* src */ + a &= mask; + a = a >> offset; + + cpu_write_op(dst, a); + cpu_set_nz_flags(a, dst); + cpu_set_c_flag(0); + cpu_set_v_flag_op(a, dst); + break; + case INCW: + case INCH: + case INCB: + a = cpu_read_op(dst); + add(a, 1, dst); + break; + case INSFW: + case INSFH: + case INSFB: + width = (cpu_read_op(src1) & 0x1f) + 1; + offset = cpu_read_op(src2) & 0x1f; + if (width >= 32) { + mask = -1; + } else { + mask = (1ul << width) - 1; + } + + a = cpu_read_op(src3) & mask; /* src */ + b = cpu_read_op(dst); /* dst */ + + b &= ~(mask << offset); + b |= (a << offset); + + cpu_write_op(dst, b); + cpu_set_nz_flags(b, dst); + cpu_set_c_flag(0); + cpu_set_v_flag_op(b, dst); + break; + case JMP: + R[NUM_PC] = cpu_effective_address(dst); + pc_incr = 0; + break; + case JSB: + cpu_push_word(R[NUM_PC] + pc_incr); + R[NUM_PC] = cpu_effective_address(dst); + pc_incr = 0; + break; + case LLSW3: + case LLSH3: + case LLSB3: + result = (t_uint64)cpu_read_op(src2) << (cpu_read_op(src1) & 0x1f); + cpu_write_op(dst, result); + cpu_set_nz_flags(result, dst); + cpu_set_c_flag(0); + cpu_set_v_flag_op(result, dst); + break; + case ARSW3: + case ARSH3: + case ARSB3: + a = cpu_read_op(src2); + b = cpu_read_op(src1) & 0x1f; + result = a >> b; + /* Ensure the MSB is copied appropriately */ + switch (op_type(src2)) { + case WD: + if (a & 0x80000000) { + result |= shift_32_table[b + 1]; + } + break; + case HW: + if (a & 0x8000) { + result |= shift_16_table[b + 1]; + } + break; + case BT: + if (a & 0x80) { + result |= shift_8_table[b + 1]; + } + break; + } + cpu_write_op(dst, result); + cpu_set_nz_flags(result, dst); + cpu_set_c_flag(0); + cpu_set_v_flag(0); + break; + case LRSW3: + a = (uint32) cpu_read_op(src2) >> (cpu_read_op(src1) & 0x1f); + cpu_write_op(dst, a); + cpu_set_nz_flags(a, dst); + cpu_set_c_flag(0); + cpu_set_v_flag_op(a, dst); + break; + case GATE: + cpu_km = TRUE; + if (R[NUM_SP] < read_w(R[NUM_PCBP] + 12, ACC_AF, BUS_CPU) || + R[NUM_SP] > read_w(R[NUM_PCBP] + 16, ACC_AF, BUS_CPU)) { + sim_debug(EXECUTE_MSG, &cpu_dev, + "[%08x] STACK OUT OF BOUNDS IN GATE. " + "SP=%08x, R[NUM_PCBP]+12=%08x, " + "R[NUM_PCBP]+16=%08x\n", + R[NUM_PC], + R[NUM_SP], + read_w(R[NUM_PCBP] + 12, ACC_AF, BUS_CPU), + read_w(R[NUM_PCBP] + 16, ACC_AF, BUS_CPU)); + cpu_abort(STACK_EXCEPTION, STACK_BOUND); + } + cpu_km = FALSE; + + abort_context = C_STACK_FAULT; + + /* Push PC+2 onto stack */ + write_w(R[NUM_SP], R[NUM_PC] + 2, BUS_CPU); + + /* Write 1, 0, 2 to ISC, TM, ET */ + R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); + R[NUM_PSW] |= (1 << PSW_ISC); + R[NUM_PSW] |= (2 << PSW_ET); + + /* Push PSW onto stack */ + write_w(R[NUM_SP] + 4, R[NUM_PSW], BUS_CPU); + + abort_context = C_NONE; + + /* Perform gate entry-point 2 */ + cpu_perform_gate(R[0] & 0x7c, + R[1] & 0x7ff8); + + /* Finish push of PC and PSW */ + R[NUM_SP] += 8; + pc_incr = 0; + +#if defined(REV3) + /* + * Both the WE 32100 and the WE 32200 processor manuals + * state that this is not a privileged instruction, and + * that it can be run from any processor level. This is + * true for the WE 32100, but not true for the WE + * 32200. Tests in the Rev 3 off-line processor + * diagnostics check to ensure that there is an exception + * raised when calling GATE in a non-privileged context. + */ + if (cpu_execution_level() != EX_LVL_KERN) { + cpu_abort(NORMAL_EXCEPTION, PRIVILEGED_OPCODE); + } +#endif + break; + case MCOMW: + case MCOMH: + case MCOMB: /* One's complement */ + a = ~(cpu_read_op(src1)); + cpu_write_op(dst, a); + cpu_set_nz_flags(a, dst); + cpu_set_c_flag(0); + cpu_set_v_flag_op(a, dst); + break; + case MNEGW: + case MNEGH: + case MNEGB: /* Two's complement */ + a = ~cpu_read_op(src1) + 1; + cpu_write_op(dst, a); + cpu_set_nz_flags(a, dst); + cpu_set_c_flag(0); + cpu_set_v_flag_op(a, dst); + break; + case MOVBLW: + while (R[2] != 0) { + a = read_w(R[0], ACC_AF, BUS_CPU); + write_w(R[1], a, BUS_CPU); + R[2]--; + R[0] += 4; + R[1] += 4; + } + break; + case STREND: + while (read_b(R[0], ACC_AF, BUS_CPU) != '\0') { + R[0]++; + } + break; + case SWAPWI: + case SWAPHI: + case SWAPBI: + a = cpu_read_op(dst); + cpu_write_op(dst, R[0]); + R[0] = a; + cpu_set_nz_flags(a, dst); + cpu_set_v_flag(0); + cpu_set_c_flag(0); + break; + case ROTW: + a = cpu_read_op(src1) & 0x1f; + b = (uint32) cpu_read_op(src2); + mask = (CHAR_BIT * sizeof(a) - 1); + d = (b >> a) | (b << ((~a + 1) & mask)); + cpu_write_op(dst, d); + cpu_set_nz_flags(d, dst); + cpu_set_v_flag(0); + cpu_set_c_flag(0); + break; + case MOVAW: + a = cpu_effective_address(src1); + cpu_write_op(dst, a); + cpu_set_nz_flags(a, dst); + cpu_set_v_flag(0); + cpu_set_c_flag(0); + break; + case MOVTRW: + a = cpu_effective_address(src1); + result = mmu_xlate_addr(a, ACC_MT); + cpu_write_op(dst, result); + cpu_set_nz_flags(result, dst); + cpu_set_v_flag(0); + cpu_set_c_flag(0); + break; + case MOVW: + case MOVH: + case MOVB: + a = cpu_read_op(src1); + cpu_write_op(dst, a); + + /* Flags are never set if the source or destination is the + PSW */ + if (!(op_is_psw(src1) || op_is_psw(dst))) { + cpu_set_nz_flags(a, dst); + cpu_set_c_flag(0); + cpu_set_v_flag_op(a, dst); + } + + /* However, if a move to PSW set the O bit, we have to + generate an overflow exception trap */ + if (op_is_psw(dst) && (R[NUM_PSW] & PSW_OE_MASK)) { + trap = INTEGER_OVERFLOW; + } + break; + case MODW2: + a = cpu_read_op(src1); + b = cpu_read_op(dst); + if (a == 0) { + cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); + break; + } + MOD(a, b, src1, dst, int32); + cpu_write_op(dst, result); + cpu_set_nz_flags(result, dst); + cpu_set_c_flag(0); + cpu_set_v_flag_op(result, dst); + break; + case MODH2: + a = cpu_read_op(src1); + b = cpu_read_op(dst); + if (a == 0) { + cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); + break; + } + MOD(a, b, src1, dst, int16); + cpu_write_op(dst, result); + cpu_set_nz_flags(result, dst); + cpu_set_c_flag(0); + cpu_set_v_flag_op(result, dst); + break; + case MODB2: + a = cpu_read_op(src1); + b = cpu_read_op(dst); + if (a == 0) { + cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); + break; + } + result = (uint8)b % (uint8)a; + cpu_write_op(dst, result); + cpu_set_nz_flags(result, dst); + cpu_set_c_flag(0); + cpu_set_v_flag_op(result, dst); + break; + break; + case MODW3: + a = cpu_read_op(src1); + b = cpu_read_op(src2); + if (a == 0) { + cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); + break; + } + MOD(a, b, src1, src2, int32); + cpu_write_op(dst, result); + cpu_set_nz_flags(result, dst); + cpu_set_c_flag(0); + cpu_set_v_flag_op(result, dst); + break; + case MODH3: + a = cpu_read_op(src1); + b = cpu_read_op(src2); + if (a == 0) { + cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); + break; + } + MOD(a, b, src1, src2, int16); + cpu_write_op(dst, result); + cpu_set_nz_flags(result, dst); + cpu_set_c_flag(0); + cpu_set_v_flag_op(result, dst); + break; + case MODB3: + a = cpu_read_op(src1); + b = cpu_read_op(src2); + if (a == 0) { + cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); + break; + } + result = (uint8)b % (uint8)a; + cpu_write_op(dst, result); + cpu_set_nz_flags(result, dst); + cpu_set_c_flag(0); + cpu_set_v_flag_op(result, dst); + break; + case MULW2: + result = (t_uint64)cpu_read_op(src1) * (t_uint64)cpu_read_op(dst); + cpu_write_op(dst, (uint32)(result & WORD_MASK)); + cpu_set_nz_flags((uint32)(result & WORD_MASK), dst); + cpu_set_c_flag(0); + cpu_set_v_flag_op(result, dst); + break; + case MULH2: + a = cpu_read_op(src1) * cpu_read_op(dst); + cpu_write_op(dst, a); + cpu_set_nz_flags(a, dst); + cpu_set_c_flag(0); + cpu_set_v_flag_op(result, dst); + break; + case MULB2: + a = cpu_read_op(src1) * cpu_read_op(dst); + cpu_write_op(dst, a); + cpu_set_nz_flags(a, dst); + cpu_set_c_flag(0); + cpu_set_v_flag_op(result, src1); + break; + case MULW3: + result = (t_uint64)cpu_read_op(src1) * (t_uint64)cpu_read_op(src2); + cpu_write_op(dst, (uint32)(result & WORD_MASK)); + cpu_set_nz_flags((uint32)(result & WORD_MASK), dst); + cpu_set_c_flag(0); + cpu_set_v_flag_op(result, dst); + break; + case MULH3: + a = cpu_read_op(src1) * cpu_read_op(src2); + cpu_write_op(dst, a); + cpu_set_nz_flags(a, dst); + cpu_set_c_flag(0); + cpu_set_v_flag_op(result, dst); + break; + case MULB3: + a = cpu_read_op(src1) * cpu_read_op(src2); + cpu_write_op(dst, a); + cpu_set_nz_flags(a, dst); + cpu_set_c_flag(0); + cpu_set_v_flag_op(result, dst); + break; + case NOP: + break; + case NOP2: + pc_incr += 1; + break; + case NOP3: + pc_incr += 2; + break; + case ORW2: + case ORH2: + case ORB2: + a = (cpu_read_op(src1) | cpu_read_op(dst)); + cpu_write_op(dst, a); + cpu_set_nz_flags(a, dst); + cpu_set_c_flag(0); + cpu_set_v_flag_op(a, dst); + break; + case ORW3: + case ORH3: + case ORB3: + a = (cpu_read_op(src1) | cpu_read_op(src2)); + cpu_write_op(dst, a); + cpu_set_nz_flags(a, dst); + cpu_set_c_flag(0); + cpu_set_v_flag_op(a, dst); + break; + case POPW: + /* N.B. "If dst is the stack pointer (%sp), the results + are indeterminate". The ordering here is important. If + we decrement SP before writing the results, we end up + in a weird, bad state. */ + a = read_w(R[NUM_SP] - 4, ACC_AF, BUS_CPU); + cpu_write_op(dst, a); + R[NUM_SP] -= 4; + cpu_set_nz_flags(a, dst); + cpu_set_c_flag(0); + cpu_set_v_flag(0); + break; + case PUSHAW: + a = cpu_effective_address(src1); + cpu_push_word(a); + cpu_set_nz_flags(a, src1); + cpu_set_c_flag(0); + cpu_set_v_flag(0); + break; + case PUSHW: + a = cpu_read_op(src1); + cpu_push_word(a); + cpu_set_nz_flags(a, src1); + cpu_set_c_flag(0); + cpu_set_v_flag(0); + break; + case RGEQ: + if (cpu_n_flag() == 0 || cpu_z_flag() == 1) { + R[NUM_PC] = cpu_pop_word(); + pc_incr = 0; + } + break; + case RGEQU: + if (cpu_c_flag() == 0) { + R[NUM_PC] = cpu_pop_word(); + pc_incr = 0; + } + break; + case RGTR: + if ((cpu_n_flag() | cpu_z_flag()) == 0) { + R[NUM_PC] = cpu_pop_word(); + pc_incr = 0; + } + break; + case RNEQ: + case RNEQU: + if (cpu_z_flag() == 0) { + R[NUM_PC] = cpu_pop_word(); + pc_incr = 0; + } + break; + case RET: + a = R[NUM_AP]; + b = read_w(R[NUM_SP] - 4, ACC_AF, BUS_CPU); + c = read_w(R[NUM_SP] - 8, ACC_AF, BUS_CPU); + R[NUM_AP] = b; + R[NUM_PC] = c; + R[NUM_SP] = a; + pc_incr = 0; + break; + case RETG: + abort_context = C_STACK_FAULT; + a = read_w(R[NUM_SP] - 4, ACC_AF, BUS_CPU); /* PSW */ + b = read_w(R[NUM_SP] - 8, ACC_AF, BUS_CPU); /* PC */ + abort_context = C_NONE; + if ((a & PSW_CM_MASK) < (R[NUM_PSW] & PSW_CM_MASK)) { + sim_debug(EXECUTE_MSG, &cpu_dev, + "[%08x] Illegal level change. New level=%d, Cur level=%d\n", + R[NUM_PC], + (a & PSW_CM_MASK) >> PSW_CM, + (R[NUM_PSW] & PSW_CM_MASK) >> PSW_CM); + cpu_abort(NORMAL_EXCEPTION, ILLEGAL_LEVEL_CHANGE); + break; + } + /* Clear some state and move it from the current PSW */ + a &= ~PSW_IPL_MASK; + a &= ~PSW_CFD_MASK; + a &= ~PSW_QIE_MASK; + a &= ~PSW_CD_MASK; + a &= ~PSW_R_MASK; + a &= ~PSW_ISC_MASK; + a &= ~PSW_TM_MASK; + a &= ~PSW_ET_MASK; + + a |= (R[NUM_PSW] & PSW_IPL_MASK); + a |= (R[NUM_PSW] & PSW_CFD_MASK); + a |= (R[NUM_PSW] & PSW_QIE_MASK); + a |= (R[NUM_PSW] & PSW_CD_MASK); + a |= (R[NUM_PSW] & PSW_R_MASK); + a |= (7 << PSW_ISC); + a |= (3 << PSW_ET); + + R[NUM_PSW] = a; + R[NUM_PC] = b; + + R[NUM_SP] -= 8; + pc_incr = 0; + break; + case RETPS: + if (cpu_execution_level() != EX_LVL_KERN) { + cpu_abort(NORMAL_EXCEPTION, PRIVILEGED_OPCODE); + break; + } + + /* Force kernel memory access */ + cpu_km = TRUE; + + abort_context = C_RESET_INT_STACK; + /* Restore process state */ + a = irq_pop_word(); /* New process PCBP */ + + abort_context = C_PROCESS_OLD_PCB; + b = read_w(a, ACC_AF, BUS_CPU); /* New PSW */ + + abort_context = C_PROCESS_NEW_PCB; + /* Copy the 'R' flag from the new PSW to the old PSW */ + R[NUM_PSW] &= ~PSW_R_MASK; + R[NUM_PSW] |= (b & PSW_R_MASK); + + /* a now holds the new PCBP */ + cpu_context_switch_2(a); + + /* Perform block moves, if any */ + cpu_context_switch_3(a); + + /* Restore registers if R bit is set */ + if (R[NUM_PSW] & PSW_R_MASK) { + R[NUM_FP] = read_w(a + 24, ACC_AF, BUS_CPU); + R[0] = read_w(a + 28, ACC_AF, BUS_CPU); + R[1] = read_w(a + 32, ACC_AF, BUS_CPU); + R[2] = read_w(a + 36, ACC_AF, BUS_CPU); + R[3] = read_w(a + 40, ACC_AF, BUS_CPU); + R[4] = read_w(a + 44, ACC_AF, BUS_CPU); + R[5] = read_w(a + 48, ACC_AF, BUS_CPU); + R[6] = read_w(a + 52, ACC_AF, BUS_CPU); + R[7] = read_w(a + 56, ACC_AF, BUS_CPU); + R[8] = read_w(a + 60, ACC_AF, BUS_CPU); + R[NUM_AP] = read_w(a + 20, ACC_AF, BUS_CPU); + } + + abort_context = C_NONE; + + /* Un-force kernel memory access */ + cpu_km = FALSE; + pc_incr = 0; + break; + case INTACK: + R[0] = cpu_int_ack << 2; + break; + case EXTOP: + sim_debug(EXECUTE_MSG, &cpu_dev, + "[%08x] EXTOP instruction.\n", + R[NUM_PC]); + cpu_abort(NORMAL_EXCEPTION, RESERVED_OPCODE); + break; + case SPOP: + /* Memory fault is signaled when no support processor is + active */ + if (mau_broadcast(coprocessor_word, 0, 0) != SCPE_OK) { + cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); + } + break; + case SPOPD2: + case SPOPS2: + case SPOPT2: + a = cpu_effective_address(src1); + b = cpu_effective_address(dst); + if (mau_broadcast(coprocessor_word, a, b) != SCPE_OK) { + cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); + } + break; + case SPOPRD: + case SPOPRS: + case SPOPRT: + a = cpu_effective_address(src1); + if (mau_broadcast(coprocessor_word, a, 0) != SCPE_OK) { + cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); + } + break; + case SPOPWD: + case SPOPWS: + case SPOPWT: + a = cpu_effective_address(dst); + if (mau_broadcast(coprocessor_word, 0, a) != SCPE_OK) { + cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); + } + break; + case SUBW2: + case SUBH2: + case SUBB2: + a = cpu_read_op(dst); + b = cpu_read_op(src1); + sub(a, b, dst); + break; + case SUBW3: + case SUBH3: + case SUBB3: + a = cpu_read_op(src2); + b = cpu_read_op(src1); + sub(a, b, dst); + break; + case RESTORE: + a = R[NUM_FP] - 28; /* Old FP */ + b = read_w(a, ACC_AF, BUS_CPU); /* Old FP */ + c = R[NUM_FP] - 24; /* Old save point */ + + for (d = src1->reg; d < NUM_FP; d++) { + R[d] = read_w(c, ACC_AF, BUS_CPU); + c += 4; + } + + R[NUM_FP] = b; /* Restore FP */ + R[NUM_SP] = a; /* Restore SP */ + break; + case RGTRU: + if ((cpu_c_flag() & cpu_z_flag()) == 0) { + R[NUM_PC] = cpu_pop_word(); + pc_incr = 0; + } + break; + case RLEQ: + if ((cpu_n_flag() | cpu_z_flag()) == 1) { + R[NUM_PC] = cpu_pop_word(); + pc_incr = 0; + } + break; + case RLEQU: + if ((cpu_c_flag() | cpu_z_flag()) == 1) { + R[NUM_PC] = cpu_pop_word(); + pc_incr = 0; + } + break; + case RLSS: + if ((cpu_n_flag() == 1) & (cpu_z_flag() == 0)) { + R[NUM_PC] = cpu_pop_word(); + pc_incr = 0; + } + break; + case RLSSU: + if (cpu_c_flag() == 1) { + R[NUM_PC] = cpu_pop_word(); + pc_incr = 0; + } + break; + case REQL: + if (cpu_z_flag() == 1) { + R[NUM_PC] = cpu_pop_word(); + pc_incr = 0; + } + break; + case REQLU: + if (cpu_z_flag() == 1) { + R[NUM_PC] = cpu_pop_word(); + pc_incr = 0; + } + break; + case RSB: + R[NUM_PC] = cpu_pop_word(); + pc_incr = 0; + break; + case RVC: + if (cpu_v_flag() == 0) { + R[NUM_PC] = cpu_pop_word(); + pc_incr = 0; + } + break; + case RVS: + if (cpu_v_flag() == 1) { + R[NUM_PC] = cpu_pop_word(); + pc_incr = 0; + } + break; + case SAVE: + /* Save the FP register */ + write_w(R[NUM_SP], R[NUM_FP], BUS_CPU); + + /* Save all the registers from the one identified by the + src operand up to FP (exclusive) */ + for (a = src1->reg, b = 4; a < NUM_FP; a++, b += 4) { + write_w(R[NUM_SP] + b, R[a], BUS_CPU); + } + + R[NUM_SP] = R[NUM_SP] + 28; + R[NUM_FP] = R[NUM_SP]; + break; + case STRCPY: + /* The STRCPY instruction will always copy the NULL + * terminator of a string. However, copying the NULL + * terminator never increments the source or destination + * pointer! */ + while (1) { + a = read_b(R[0], ACC_AF, BUS_CPU); + write_b(R[1], (uint8) a, BUS_CPU); + if (a == '\0') { + break; + } + R[0]++; + R[1]++; + } + break; + case TSTW: + a = cpu_read_op(src1); + cpu_set_n_flag((int32)a < 0); + cpu_set_z_flag(a == 0); + cpu_set_c_flag(0); + cpu_set_v_flag(0); + break; + case TSTH: + a = cpu_read_op(src1); + cpu_set_n_flag((int16)a < 0); + cpu_set_z_flag(a == 0); + cpu_set_c_flag(0); + cpu_set_v_flag(0); + break; + case TSTB: + a = cpu_read_op(src1); + cpu_set_n_flag((int8)a < 0); + cpu_set_z_flag(a == 0); + cpu_set_c_flag(0); + cpu_set_v_flag(0); + break; + case WAIT: + if (cpu_execution_level() != EX_LVL_KERN) { + cpu_abort(NORMAL_EXCEPTION, PRIVILEGED_OPCODE); + break; + } + cpu_in_wait = TRUE; + break; + case XORW2: + case XORH2: + case XORB2: + a = (cpu_read_op(src1) ^ cpu_read_op(dst)); + cpu_write_op(dst, a); + cpu_set_nz_flags(a, dst); + cpu_set_c_flag(0); + cpu_set_v_flag_op(a, dst); + break; + case XORW3: + case XORH3: + case XORB3: + a = (cpu_read_op(src1) ^ cpu_read_op(src2)); + cpu_write_op(dst, a); + cpu_set_nz_flags(a, dst); + cpu_set_c_flag(0); + cpu_set_v_flag_op(a, dst); + break; +#if defined(REV3) + case ADDPB2: + a = cpu_read_op(src1); + b = cpu_read_op(dst); + result = add_bcd(a, b); /* sets flags */ + cpu_write_op(dst, result); + break; + case ADDPB3: + a = cpu_read_op(src1); + b = cpu_read_op(src2); + result = add_bcd(a, b); /* sets flags */ + cpu_write_op(dst, result); + break; + case DTB: + a = cpu_read_op(dst); + result = a - 1; + cpu_write_op(dst, (uint32)(result & WORD_MASK)); + if ((int32)result > -1) { + pc_incr = sign_extend_b(src1->embedded.b); + } + sim_debug(EXECUTE_MSG, &cpu_dev, + "[%08x] DTB: dst=%08x r=%08x emb=%04x\n", + R[NUM_PC], a, (uint32)(result & WORD_MASK), src1->embedded.h); + break; + case DTH: + a = cpu_read_op(dst); + result = a - 1; + cpu_write_op(dst, (uint32)(result & WORD_MASK)); + if ((int32)result > -1) { + pc_incr = sign_extend_h(src1->embedded.h); + } + sim_debug(EXECUTE_MSG, &cpu_dev, + "[%08x] DTH: dst=%08x r=%08x emb=%04x\n", + R[NUM_PC], a, (uint32)(result & WORD_MASK), src1->embedded.h); + break; + case TEDTB: + if (cpu_z_flag() == 0) { + a = cpu_read_op(dst); + result = a - 1; + cpu_write_op(dst, (uint32)(result & WORD_MASK)); + if ((int32)result > -1) { + pc_incr = sign_extend_b(src1->embedded.b); + } + } + break; + case TEDTH: + if (cpu_z_flag() == 0) { + a = cpu_read_op(dst); + result = a - 1; + cpu_write_op(dst, (uint32)(result & WORD_MASK)); + if ((int32)result > -1) { + pc_incr = sign_extend_h(src1->embedded.h); + } + } + break; + case TGDTB: + if ((cpu_n_flag() | cpu_z_flag()) == 1) { + a = cpu_read_op(dst); + result = a - 1; + cpu_write_op(dst, (uint32)(result & WORD_MASK)); + if ((int32)result > -1) { + pc_incr = sign_extend_b(src1->embedded.b); + } + } + break; + case TGDTH: + if ((cpu_n_flag() | cpu_z_flag()) == 1) { + a = cpu_read_op(dst); + result = a - 1; + cpu_write_op(dst, (uint32)(result & WORD_MASK)); + if ((int32)result > -1) { + pc_incr = sign_extend_h(src1->embedded.h); + } + } + break; + case TGEDTB: + if ((cpu_n_flag() == 1) & (cpu_z_flag() == 0)) { + a = cpu_read_op(dst); + result = a - 1; + cpu_write_op(dst, (uint32)(result & WORD_MASK)); + if ((int32)result > -1) { + pc_incr = sign_extend_b(src1->embedded.b); + } + } + break; + case TGEDTH: + if ((cpu_n_flag() == 1) & (cpu_z_flag() == 0)) { + a = cpu_read_op(dst); + result = a - 1; + cpu_write_op(dst, (uint32)(result & WORD_MASK)); + if ((int32)result > -1) { + pc_incr = sign_extend_h(src1->embedded.h); + } + } + break; + case TNEDTB: + if (cpu_z_flag() == 1) { + a = cpu_read_op(dst); + result = a - 1; + cpu_write_op(dst, (uint32)(result & WORD_MASK)); + if ((int32)result > -1) { + pc_incr = sign_extend_b(src1->embedded.b); + } + } + break; + case TNEDTH: + if (cpu_z_flag() == 1) { + a = cpu_read_op(dst); + result = a - 1; + cpu_write_op(dst, (uint32)(result & WORD_MASK)); + if ((int32)result > -1) { + pc_incr = sign_extend_h(src1->embedded.h); + } + } + break; + case SUBPB2: + a = cpu_read_op(src1); + b = cpu_read_op(dst); + result = sub_bcd(b, a); /* sets flags */ + cpu_write_op(dst, result); + break; + case SUBPB3: + a = cpu_read_op(src1); + b = cpu_read_op(src2); + result = sub_bcd(b, a); /* sets flags */ + cpu_write_op(dst, result); + break; + case PACKB: + a = cpu_read_op(src1); + b = ((a & 0x0f00) >> 4) | (a & 0xf); + cpu_write_op(dst, b); + break; + case UNPACKB: + a = cpu_read_op(src1); /* d1, d0 */ + b = cpu_read_op(src2); /* d3, d2 */ + c = ((b & 0xf0) << 8) | ((a & 0xf0) << 4) | + ((b & 0xf) << 4) | ((a & 0xf)); /* d3, d1, d2, d0 */ + cpu_write_op(dst, c); + break; + case CASWI: + a = cpu_read_op(src1); + b = cpu_read_op(src2); + c = cpu_read_op(dst); + result = c - b; + + if (result == 0) { + cpu_write_op(dst, a); + } else { + cpu_write_op(src2, c); + } + + cpu_set_n_flag((int32)result < 0); + cpu_set_z_flag(result == 0); + cpu_set_c_flag((uint32)b > (uint32)c); + cpu_set_v_flag_op(result, dst); + break; + case SETX: + R[NUM_PSW] |= (1 << PSW_X); + break; + case CLRX: + R[NUM_PSW] &= ~(1 << PSW_X); + break; + case RETQINT: + abort_context = C_RESET_INT_STACK; + + /* Get old PSW value from interrupt stack */ + a = read_w(R[NUM_ISP] - 4, ACC_AF, BUS_CPU); + + /* Update PSW */ + R[NUM_PSW] &= ~(QIE_PSW_MASK); + R[NUM_PSW] |= (a & QIE_PSW_MASK); + + /* Get old PC value from interrupt stack */ + b = read_w(R[NUM_ISP] - 8, ACC_AF, BUS_CPU); + + /* Set new PC */ + R[NUM_PC] = b; + + /* Finish interrupt stack pop */ + R[NUM_ISP] = R[NUM_ISP] - 8; + + /* Set new PSW ISC/TM/ET to 7/0/3 */ + R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); + R[NUM_PSW] |= 7 << PSW_ISC; + R[NUM_PSW] |= 3 << PSW_ET; + + /* Done */ + abort_context = C_NONE; + break; + case UCALLPS: + if ((R[NUM_PSW] & PSW_EXUC_MASK) == 0) { + cpu_abort(NORMAL_EXCEPTION, ILLEGAL_OPCODE); + } + + cpu_km = TRUE; + + /* Get the new PCBP */ + abort_context = C_RESET_SYSTEM_DATA; + a = read_w(0x488, ACC_AF, BUS_CPU); + + /* Save the existing PCBP */ + abort_context = C_RESET_INT_STACK; + irq_push_word(R[NUM_PCBP]); + + /* Prepare the new PC to be pushed on the stack */ + R[NUM_PC] = R[NUM_PC] + 2; + + /* Set ISC/TM/ET to 0/0/1 */ + R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); + R[NUM_PSW] |= 1 << PSW_ET; + + cpu_context_switch_1(a); + abort_context = C_PROCESS_NEW_PCB; + cpu_context_switch_2(a); + + /* Set ISC/TM/ET to 7/0/3 */ + R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); + R[NUM_PSW] |= 7 << PSW_ISC; + R[NUM_PSW] |= 3 << PSW_ET; + + cpu_context_switch_3(a); + + abort_context = C_NONE; + cpu_km = FALSE; + + break; +#endif + default: + sim_debug(EXECUTE_MSG, &cpu_dev, + "[%08x] Illegal Opcode 0x%x\n", + R[NUM_PC], cpu_instr->mn->opcode); + cpu_abort(NORMAL_EXCEPTION, ILLEGAL_OPCODE); + }; + + /* Increment the PC appropriately */ + R[NUM_PC] += pc_incr; + + /* If TE and TM are both set, generate a trace trap */ + if ((R[NUM_PSW] & PSW_TE_MASK) && (R[NUM_PSW] & PSW_TM_MASK)) { + trap = TRACE_TRAP; + } + + /* Handle traps */ + if (trap) { + R[NUM_PSW] &= ~(PSW_ET_MASK); + R[NUM_PSW] &= ~(PSW_ISC_MASK); + R[NUM_PSW] |= NORMAL_EXCEPTION; + R[NUM_PSW] |= (uint32) (trap << PSW_ISC); + cpu_on_normal_exception(trap); + } + } + + return stop_reason; +} + +static SIM_INLINE void cpu_on_process_exception(uint8 isc) +{ + uint32 new_pcbp; + + sim_debug(EXECUTE_MSG, &cpu_dev, + "[cpu_on_process_exception %d] SP=%08x PCBP=%08x ISP=%08x\n", + isc, R[NUM_SP], R[NUM_PCBP], R[NUM_ISP]); + + cpu_km = TRUE; + + abort_context = C_RESET_SYSTEM_DATA; + new_pcbp = read_w(0x84, ACC_AF, BUS_CPU); + + abort_context = C_RESET_INT_STACK; + irq_push_word(R[NUM_PCBP]); + + cpu_context_switch_2(new_pcbp); + + /* Set TM and ET to 0 and 3 in new PSW */ + R[NUM_PSW] &= ~(PSW_TM_MASK|PSW_ET_MASK); + R[NUM_PSW] |= (3 << PSW_ET); + + cpu_km = FALSE; + abort_context = C_NONE; + return; +} + +static SIM_INLINE void cpu_on_reset_exception(uint8 isc) +{ + uint32 new_pcbp; + + sim_debug(EXECUTE_MSG, &cpu_dev, + "[cpu_on_reset_exception %d] SP=%08x PCBP=%08x ISP=%08x\n", + isc, R[NUM_SP], R[NUM_PCBP], R[NUM_ISP]); + + if (isc == EXTERNAL_RESET) { + R[NUM_PSW] &= ~(PSW_R_MASK); + } + + cpu_km = TRUE; + + mmu_disable(); + + abort_context = C_RESET_SYSTEM_DATA; + new_pcbp = read_w(0x80, ACC_AF, BUS_CPU); + + abort_context = C_RESET_NEW_PCB; + cpu_context_switch_2(new_pcbp); + + cpu_km = FALSE; + abort_context = C_NONE; +} + +static SIM_INLINE void cpu_on_stack_exception(uint8 isc) +{ + uint32 new_pcbp; + + sim_debug(EXECUTE_MSG, &cpu_dev, + "[cpu_on_stack_exception %d] SP=%08x PCBP=%08x ISP=%08x\n", + isc, R[NUM_SP], R[NUM_PCBP], R[NUM_ISP]); + + abort_context = C_RESET_SYSTEM_DATA; + cpu_km = TRUE; + new_pcbp = read_w(0x88, ACC_AF, BUS_CPU); + + abort_context = C_RESET_INT_STACK; + irq_push_word(R[NUM_PCBP]); + + abort_context = C_PROCESS_OLD_PCB; + R[NUM_PSW] &= ~(PSW_ET_MASK|PSW_ISC_MASK); + R[NUM_PSW] |= (2 << PSW_ET); + R[NUM_PSW] |= (uint32) (isc << PSW_ISC); + + cpu_context_switch_1(new_pcbp); + cpu_context_switch_2(new_pcbp); + + /* Set ISC, TM, and ET to 7, 0, 3 in new PSW */ + R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); + R[NUM_PSW] |= (7 << PSW_ISC); + R[NUM_PSW] |= (3 << PSW_ET); + + cpu_km = FALSE; + abort_context = C_NONE; +} + +static SIM_INLINE void cpu_on_normal_exception(uint8 isc) +{ + sim_debug(EXECUTE_MSG, &cpu_dev, + "[cpu_on_normal_exception %d] %%sp=%08x abort_context=%d\n", + isc, R[NUM_SP], abort_context); + + cpu_km = TRUE; + if (R[NUM_SP] < read_w(R[NUM_PCBP] + 12, ACC_AF, BUS_CPU) || + R[NUM_SP] > read_w(R[NUM_PCBP] + 16, ACC_AF, BUS_CPU)) { + sim_debug(EXECUTE_MSG, &cpu_dev, + "STACK OUT OF BOUNDS IN EXCEPTION HANDLER. " + "SP=%08x, R[NUM_PCBP]+12=%08x, " + "R[NUM_PCBP]+16=%08x\n", + R[NUM_SP], + read_w(R[NUM_PCBP] + 12, ACC_AF, BUS_CPU), + read_w(R[NUM_PCBP] + 16, ACC_AF, BUS_CPU)); + cpu_abort(STACK_EXCEPTION, STACK_BOUND); + } + cpu_km = FALSE; + + /* Set context for STACK (FAULT) */ + abort_context = C_STACK_FAULT; + /* Save address of next instruction to stack */ + write_w(R[NUM_SP], R[NUM_PC], BUS_CPU); + + /* Write 0, 3 to TM, ET fields of PSW */ + R[NUM_PSW] &= ~(PSW_TM_MASK|PSW_ET_MASK); + R[NUM_PSW] |= (3 << PSW_ET); + + /* Save PSW to stack */ + write_w(R[NUM_SP] + 4, R[NUM_PSW], BUS_CPU); + + /* Set context for RESET (GATE VECTOR) */ + abort_context = C_RESET_GATE_VECTOR; + cpu_perform_gate(0, ((uint32) isc) << 3); + + /* Finish push of old PC and PSW */ + R[NUM_SP] += 8; + abort_context = C_NONE; +} + +static SIM_INLINE void cpu_perform_gate(uint32 index1, uint32 index2) +{ + uint32 gate_l2, new_psw; + + abort_context = C_NORMAL_GATE_VECTOR; + cpu_km = TRUE; + + gate_l2 = read_w(index1, ACC_AF, BUS_CPU) + index2; + + /* Get new PSW from second-level table */ + new_psw = read_w(gate_l2, ACC_AF, BUS_CPU); + + /* Clear state in PSW */ + new_psw &= ~(PSW_PM_MASK|PSW_IPL_MASK|PSW_R_MASK| + PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); + + /* Set PM in new PSW */ + new_psw |= (R[NUM_PSW] & PSW_CM_MASK) >> 2; /* PM */ + new_psw |= (R[NUM_PSW] & PSW_IPL_MASK); /* IPL */ + new_psw |= (R[NUM_PSW] & PSW_R_MASK); /* R */ + + /* Set new PSW ISC, TM, and ET to 7, 1, 3 */ + new_psw |= (7 << PSW_ISC); /* ISC */ + new_psw |= (1 << PSW_TM); /* TM */ + new_psw |= (3 << PSW_ET); /* ET */ + + R[NUM_PC] = read_w(gate_l2 + 4, ACC_AF, BUS_CPU); + R[NUM_PSW] = new_psw; + + cpu_km = FALSE; + abort_context = C_NONE; +} + +/* + * TODO: Setting 'data' to the effective address is bogus. We're only + * doing it because we want to get the address when we trace the + * instructions using "SHOW CPU HISTORY". We should just put + * effective_address as a field in the operand struct and make + * cpu_show_hist smarter. + */ +static uint32 cpu_effective_address(operand *op) +{ +#if defined(REV3) + uint32 tmp; +#endif + + /* Register Deferred */ + if (op->mode == 5 && op->reg != 11) { + return R[op->reg]; + } + + /* Absolute */ + if (op->mode == 7 && op->reg == 15) { + return op->embedded.w; + } + + /* Absolute Deferred */ + if (op->mode == 14 && op->reg == 15) { + /* May cause exception */ + return read_w(op->embedded.w, ACC_AF, BUS_CPU); + } + + /* FP Short Offset */ + if (op->mode == 6 && op->reg != 15) { + return R[NUM_FP] + sign_extend_b(op->embedded.b); + } + + /* AP Short Offset */ + if (op->mode == 7 && op->reg != 15) { + return R[NUM_AP] + sign_extend_b(op->embedded.b); + } + + /* Word Displacement */ + if (op->mode == 8) { + return R[op->reg] + op->embedded.w; + } + + /* Word Displacement Deferred */ + if (op->mode == 9) { + return read_w(R[op->reg] + op->embedded.w, ACC_AF, BUS_CPU); + } + + /* Halfword Displacement */ + if (op->mode == 10) { + return R[op->reg] + sign_extend_h(op->embedded.h); + } + + /* Halfword Displacement Deferred */ + if (op->mode == 11) { + return read_w(R[op->reg] + sign_extend_h(op->embedded.h), ACC_AF, BUS_CPU); + } + + /* Byte Displacement */ + if (op->mode == 12) { + return R[op->reg] + sign_extend_b(op->embedded.b); + } + + /* Byte Displacement Deferred */ + if (op->mode == 13) { + return read_w(R[op->reg] + sign_extend_b(op->embedded.b), ACC_AF, BUS_CPU); + } + +#if defined(REV3) + /* Auto pre-decrement */ + if (op->mode == 0x10) { + switch(op_type(op)) { + case BT: + case SB: + R[op->reg] -= 1; + break; + case HW: + case UH: + R[op->reg] -= 2; + break; + case WD: + case UW: + R[op->reg] -= 4; + break; + } + return R[op->reg]; + } + + /* Auto post-decrement */ + if (op->mode == 0x12) { + tmp = R[op->reg]; + switch(op_type(op)) { + case BT: + case SB: + R[op->reg] -= 1; + break; + case HW: + case UH: + R[op->reg] -= 2; + break; + case WD: + case UW: + R[op->reg] -= 4; + break; + } + return tmp; + } + + /* Auto pre-increment */ + if (op->mode == 0x14) { + switch(op_type(op)) { + case BT: + case SB: + R[op->reg] += 1; + break; + case HW: + case UH: + R[op->reg] += 2; + break; + case WD: + case UW: + R[op->reg] += 4; + break; + } + return R[op->reg]; + } + + /* Auto post-increment */ + if (op->mode == 0x16) { + tmp = R[op->reg]; + switch(op_type(op)) { + case BT: + case SB: + R[op->reg] += 1; + break; + case HW: + case UH: + R[op->reg] += 2; + break; + case WD: + case UW: + R[op->reg] += 4; + break; + } + return tmp; + } + + /* Indexed with byte displacement */ + if (op->mode == 0xab) { + tmp = sign_extend_b(op->embedded.b); + tmp += R[op->reg]; + tmp += R[op->reg2]; + return tmp; + } + + /* Indexed with halfword displacement */ + if (op->mode == 0xbb) { + tmp = sign_extend_h(op->embedded.h); + tmp += R[op->reg]; + tmp += R[op->reg2]; + return tmp; + } + + /* Indexed with scaling */ + if (op->mode == 0xdb) { + switch(op_type(op)) { + case BT: + case SB: + tmp = R[op->reg]; + break; + case HW: + case UH: + tmp = R[op->reg] * 2; + break; + case WD: + case UW: + tmp = R[op->reg] * 4; + break; + default: + /* We should never reach this */ + tmp = 0; + break; + } + + return tmp + R[op->reg2]; + } +#endif + + if (cpu_unit.flags & UNIT_OPBRK) { + stop_reason = STOP_OPCODE; + } + + return 0; +} + +/* + * Read and Write routines for operands. + * + * The rules for dealing with the type (signed/unsigned, + * byte/halfword/word) of operands are fairly complex. + * + * 1. The expanded operand mode does not affect the treatment of + * Literal Mode operands. All literals are signed. + * + * 2. The expanded operand mode does not affect the length of + * Immediate Mode operands, but does affect whether they are signed + * or unsigned. + * + * 3. When using expanded-mode operands, the new type remains in + * effect for the operands that folow in the instruction unless + * another expanded operand mode overrides it. (This rule in + * particular is managed by decode_instruction()) + * + * 4. The expanded operand mode is illegal with coprocessor instructions + * and CALL, SAVE, RESTORE, SWAP INTERLOCKED, PUSAHW, PUSHAW, POPW, + * and JSB. (Illegal Operand Fault) + * + * 5. When writing a byte, the Negative (N) flag is set based on the + * high bit of the data type being written, regardless of the SIGN + * of the extended datatype. e.g.: {ubyte} and {sbyte} both check + * for bit 7, {uhalf} and {shalf} both check for bit 15, and + * {uword} and {sword} both check for bit 31. + * + * 6. For instructions with a signed destination, V is set if the sign + * bit of the output value is different from any truncated bit of + * the result. For instructions with an unsigned destination, V is + * set if any truncated bit is 1. + */ + + +/* + * Read the data referenced by an operand. Performs sign or zero + * extension as required by the read width and operand type, then + * returns the read value. + * + * "All operations are performed only on 32-bit quantities even though + * an instruction may specify a byte or halfword operand. The WE + * 32100 Microprocessor reads in the correct number of bits for the + * operand and extends the data automatically to 32 bits. It uses + * sign extension when reading signed data or halfwords and zero + * extension when reading unsigned data or bytes (or bit fields that + * contain less than 32 bits). The data type of the source operand + * determines how many bits are fetched and what type of extension is + * applied. Bytes are treated as unsigned, while halfwords and words + * are considered signed. The type of extension applied can be + * changed using the expanded-operand type mode as described in 3.4.5 + * Expanded-Operand Type Mode. For sign extension, the value of the + * MSB or sign bit of the data fills the high-order bits to form a + * 32-bit value. In zero extension, zeros fill the high order bits. + * The microprocessor automatically extends a byte or halfword to 32 + * bits before performing an operation. Figure 3-3 illustrates sign + * and zero extension. An arithmetic, logical, data transfer, or bit + * field operation always yields an intermediate result that is 32 + * bits in length. If the result is to be stored in a register, the + * processor writes all 32 bits to that register. The processor + * automatically strips any surplus high-order bits from a result + * when writing bytes or halfwords to memory." -- "WE 32100 + * Microprocessor Information Manual", Section 3.1.1 + * + */ +static uint32 cpu_read_op(operand * op) +{ + uint32 eff; + uint32 data; + + /* Register */ + if (op->mode == 4 && op->reg != 15) { + switch (op_type(op)) { + case WD: + case UW: + data = R[op->reg]; + break; + case HW: + data = sign_extend_h(R[op->reg] & HALF_MASK); + break; + case UH: + data = R[op->reg] & HALF_MASK; + break; + case BT: + data = R[op->reg] & BYTE_MASK; + break; + case SB: + data = sign_extend_b(R[op->reg] & BYTE_MASK); + break; + default: + sim_debug(EXECUTE_MSG, &cpu_dev, + "[%08x] cpu_read_op: unknown op type (1): %d\n", + R[NUM_PC], op_type(op)); + stop_reason = STOP_ERR; + data = 0; + break; + } + + op->data = data; + return data; + } + + /* Literal */ + if (op->mode < 4 || op->mode == 15) { + /* Both positive and negative literals are _always_ treated as + signed bytes, and they are _always_ sign extended. They + simply ignore expanded datatypes. */ + data = sign_extend_b(op->embedded.b); + op->data = data; + return data; + } + + /* Immediate */ + if (op->reg == 15 && + (op->mode == 4 || op->mode == 5 || op->mode == 6)) { + switch (op->mode) { + case 4: /* Word Immediate */ + data = op->embedded.w; + op->data = data; + return data; + case 5: /* Halfword Immediate */ + data = sign_extend_h(op->embedded.h); + op->data = data; + return data; + case 6: /* Byte Immedaite */ + data = sign_extend_b(op->embedded.b); + op->data = data; + return data; + } + } + + /* At this point, we'll need to find an effective address */ + eff = cpu_effective_address(op); + + switch (op_type(op)) { + case WD: /* Signed Word */ + case UW: /* Unsigned Word */ + data = read_w(eff, ACC_OF, BUS_CPU); + op->data = data; + return data; + case HW: /* Signed Halfword */ + data = sign_extend_h(read_h(eff, ACC_OF, BUS_CPU)); + op->data = data; + return data; + case UH: /* Unsigned Halfword */ + data = read_h(eff, ACC_OF, BUS_CPU); + op->data = data; + return data; + case SB: /* Signed Byte */ + data = sign_extend_b(read_b(eff, ACC_OF, BUS_CPU)); + op->data = data; + return data; + case BT: /* Unsigned Byte */ + data = read_b(eff, ACC_OF, BUS_CPU); + op->data = data; + return data; + default: + sim_debug(EXECUTE_MSG, &cpu_dev, + "[%08x] cpu_read_op: unknown op type (2): %d\n", + R[NUM_PC], op_type(op)); + stop_reason = STOP_ERR; + return 0; + } +} + + +static void cpu_write_op(operand * op, t_uint64 val) +{ + uint32 eff; + op->data = (uint32) val; + + /* Writing to a register. */ + if (op->mode == 4 && op->reg != 15) { + if (PRIVREG(op->reg) && cpu_execution_level() != EX_LVL_KERN) { + cpu_abort(NORMAL_EXCEPTION, PRIVILEGED_REGISTER); + return; + } + + /* Registers always get the full 32-bits written, EXCEPT for + * the PSW, which has some Read-Only fields. */ + if (op->reg == NUM_PSW) { + WRITE_PSW((uint32) val); + } else { + R[op->reg] = (uint32) val; + } + + return; + } + + /* Literal mode is not legal. */ + if (op->mode < 4 || op->mode == 15) { + cpu_abort(NORMAL_EXCEPTION, INVALID_DESCRIPTOR); + return; + } + + /* Immediate mode is not legal. */ + if (op->reg == 15 && + (op->mode == 4 || op->mode == 5 || op->mode == 6)) { + cpu_abort(NORMAL_EXCEPTION, INVALID_DESCRIPTOR); + return; + } + + eff = cpu_effective_address(op); + + switch (op_type(op)) { + case UW: + case WD: + write_w(eff, (uint32) val, BUS_CPU); + break; + case HW: + case UH: + write_h(eff, val & HALF_MASK, BUS_CPU); + break; + case SB: + case BT: + write_b(eff, val & BYTE_MASK, BUS_CPU); + break; + default: + sim_debug(EXECUTE_MSG, &cpu_dev, + "[%08x] cpu_read_op: unknown op type (3): %d\n", + R[NUM_PC], op_type(op)); + stop_reason = STOP_ERR; + break; + } +} + +/* + * Returns the correct datatype for an operand -- either extended type + * or default type. + */ +static SIM_INLINE int8 op_type(operand *op) { + if (op->etype > -1) { + return op->etype; + } else { + return op->dtype; + } +} + +static SIM_INLINE t_bool op_signed(operand *op) { + return (op_type(op) == WD || op_type(op) == HW || op_type(op) == SB); +} + +static SIM_INLINE uint32 sign_extend_b(uint8 val) +{ + if (val & 0x80) + return ((uint32) val) | 0xffffff00; + return (uint32) val; +} + +static SIM_INLINE uint32 sign_extend_h(uint16 val) +{ + if (val & 0x8000) + return ((uint32) val) | 0xffff0000; + return (uint32) val; +} + +/* + * Returns the current CPU execution level. + */ +static SIM_INLINE uint8 cpu_execution_level() +{ + return (R[NUM_PSW] & PSW_CM_MASK) >> PSW_CM; +} + +static SIM_INLINE t_bool cpu_z_flag() +{ + return (R[NUM_PSW] & PSW_Z_MASK) != 0; +} + +static SIM_INLINE t_bool cpu_n_flag() +{ + return (R[NUM_PSW] & PSW_N_MASK) != 0; +} + +static SIM_INLINE t_bool cpu_c_flag() +{ + return (R[NUM_PSW] & PSW_C_MASK) != 0; +} + +static SIM_INLINE t_bool cpu_v_flag() +{ + return (R[NUM_PSW] & PSW_V_MASK) != 0; +} + +#if defined(REV3) +static SIM_INLINE t_bool cpu_x_flag() +{ + return (R[NUM_PSW] & PSW_X_MASK) != 0; +} +#endif + +static SIM_INLINE void cpu_set_z_flag(t_bool val) +{ + if (val) { + R[NUM_PSW] |= PSW_Z_MASK; + } else { + R[NUM_PSW] &= ~PSW_Z_MASK; + } +} + +static SIM_INLINE void cpu_set_n_flag(t_bool val) +{ + if (val) { + R[NUM_PSW] |= PSW_N_MASK; + } else { + R[NUM_PSW] &= ~PSW_N_MASK; + } +} + +static SIM_INLINE void cpu_set_c_flag(t_bool val) +{ + if (val) { + R[NUM_PSW] |= PSW_C_MASK; + } else { + R[NUM_PSW] &= ~PSW_C_MASK; + } +} + +#if defined(REV3) +static SIM_INLINE void cpu_set_x_flag(t_bool val) +{ + if (val) { + R[NUM_PSW] |= PSW_X_MASK; + } else { + R[NUM_PSW] &= ~PSW_X_MASK; + } +} +#endif + +static SIM_INLINE void cpu_set_v_flag_op(t_uint64 val, operand *op) +{ + switch(op_type(op)) { + case WD: + case UW: + cpu_set_v_flag(0); + break; + case HW: + case UH: + cpu_set_v_flag(val > HALF_MASK); + break; + case BT: + case SB: + default: + cpu_set_v_flag(val > BYTE_MASK); + break; + } +} + +static SIM_INLINE void cpu_set_v_flag(t_bool val) +{ + if (val) { + R[NUM_PSW] |= PSW_V_MASK; + if (R[NUM_PSW] & PSW_OE_MASK) { + cpu_abort(NORMAL_EXCEPTION, INTEGER_OVERFLOW); + } + } else { + R[NUM_PSW] &= ~PSW_V_MASK; + } +} + +static void cpu_set_nz_flags(t_uint64 data, operand *dst) +{ + int8 type = op_type(dst); + + switch (type) { + case WD: + case UW: + cpu_set_n_flag(!!(WD_MSB & data)); + cpu_set_z_flag((data & WORD_MASK) == 0); + break; + case HW: + case UH: + cpu_set_n_flag(HW_MSB & data); + cpu_set_z_flag((data & HALF_MASK) == 0); + break; + case BT: + case SB: + cpu_set_n_flag(BT_MSB & data); + cpu_set_z_flag((data & BYTE_MASK) == 0); + break; + } +} + +static SIM_INLINE void cpu_push_word(uint32 val) +{ + write_w(R[NUM_SP], val, BUS_CPU); + R[NUM_SP] += 4; +} + +static SIM_INLINE uint32 cpu_pop_word() +{ + uint32 result; + /* We always read fromthe stack first BEFORE decrementing, + in case this causes a fault. */ + result = read_w(R[NUM_SP] - 4, ACC_AF, BUS_CPU); + R[NUM_SP] -= 4; + return result; +} + +static SIM_INLINE void irq_push_word(uint32 val) +{ + write_w(R[NUM_ISP], val, BUS_CPU); + R[NUM_ISP] += 4; +} + +static SIM_INLINE uint32 irq_pop_word() +{ + R[NUM_ISP] -= 4; + return read_w(R[NUM_ISP], ACC_AF, BUS_CPU); +} + +static SIM_INLINE t_bool op_is_psw(operand *op) +{ + return (op->mode == 4 && op->reg == NUM_PSW); +} + +static SIM_INLINE void sub(t_uint64 a, t_uint64 b, operand *dst) +{ + t_uint64 result; + + result = a - b; + + cpu_write_op(dst, result); + + cpu_set_nz_flags(result, dst); + cpu_set_c_flag((uint32)b > (uint32)a); + cpu_set_v_flag_op(result, dst); +} + +static SIM_INLINE void add(t_uint64 a, t_uint64 b, operand *dst) +{ + t_uint64 result; + + result = a + b; + + cpu_write_op(dst, result); + + cpu_set_nz_flags(result, dst); + + switch(op_type(dst)) { + case WD: + cpu_set_c_flag(result > WORD_MASK); + cpu_set_v_flag(((a ^ ~b) & (a ^ result)) & WD_MSB); + break; + case UW: + cpu_set_c_flag(result > WORD_MASK); + cpu_set_v_flag(result > WORD_MASK); + break; + case HW: + cpu_set_c_flag(result > HALF_MASK); + cpu_set_v_flag(((a ^ ~b) & (a ^ result)) & HW_MSB); + break; + case UH: + cpu_set_c_flag(result > HALF_MASK); + cpu_set_v_flag(result > HALF_MASK); + break; + case BT: + cpu_set_c_flag(result > BYTE_MASK); + cpu_set_v_flag(result > BYTE_MASK); + break; + case SB: + cpu_set_c_flag(result > BYTE_MASK); + cpu_set_v_flag(((a ^ ~b) & (a ^ result)) & BT_MSB); + break; + } +} + +#if defined(REV3) +/* + * Return the packed BCD byte result of adding two packed BCD input + * bytes. This will set the C and X carry flags appropraitely if there + * is a carry. + */ +static SIM_INLINE uint8 add_bcd(uint8 packed_a, uint8 packed_b) +{ + uint16 l, h, result; + + l = (packed_a & 0x0f) + (packed_b & 0x0f) + (cpu_x_flag() ? 1 : 0); + if ((l & 0xff) > 9) { + l += 6; + } + + h = ((packed_a >> 4) & 0x0f) + ((packed_b >> 4) & 0x0f) + (l > 15 ? 1 : 0); + if ((h & 0xff) > 9) { + h += 6; + } + + result = ((l & 0x0f) | (h << 4)) & 0xff; + + cpu_set_c_flag(h > 15); + cpu_set_x_flag(h > 15); + cpu_set_z_flag(result == 0); + cpu_set_n_flag(0); + cpu_set_v_flag(0); + + return (uint8)result; +} + +/* + * Return the packed BCD byte result of subtracting two packed BCD + * input bytes. This will set the C and X carry flags appropraitely if + * there is a carry. + */ +static SIM_INLINE uint8 sub_bcd(uint8 packed_a, uint8 packed_b) +{ + uint16 l, h, result; + + l = (packed_a & 0x0f) - (packed_b & 0x0f) - (cpu_x_flag() ? 1 : 0); + if ((l & 0x10) != 0) { + l -= 6; + } + + h = ((packed_a >> 4) & 0x0f) - ((packed_b >> 4) & 0x0f) - ((l & 0x10) != 0 ? 1 : 0); + if ((h & 0x10) != 0) { + h -= 6; + } + + result = ((l & 0x0f)|(h << 4)) & 0xff; + + cpu_set_c_flag(h > 15); + cpu_set_x_flag(h > 15); + cpu_set_z_flag(result == 0); + cpu_set_n_flag(0); + cpu_set_v_flag(0); + + return (uint8)result; +} +#endif + +/* + * Set PSW's ET and ISC fields, and store global exception or fault + * state appropriately. + */ +void cpu_abort(uint8 et, uint8 isc) +{ + /* We don't trap Integer Overflow if the OE bit is not set */ + if ((R[NUM_PSW] & PSW_OE_MASK) == 0 && isc == INTEGER_OVERFLOW) { + return; + } + + R[NUM_PSW] &= ~(PSW_ET_MASK); /* Clear ET */ + R[NUM_PSW] &= ~(PSW_ISC_MASK); /* Clear ISC */ + R[NUM_PSW] |= et; /* Set ET */ + R[NUM_PSW] |= (uint32) (isc << PSW_ISC); /* Set ISC */ + + longjmp(save_env, ABORT_EXC); +} + +CONST char *cpu_description(DEVICE *dptr) +{ +#if defined(REV3) + return "3B2/700 CPU (WE 32200)"; +#else + return "3B2/400 CPU (WE 32100)"; +#endif +} + +t_stat cpu_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ +#if defined(REV3) + fprintf(st, "3B2/700 CPU Help\n\n"); + fprintf(st, "The 3B2/700 CPU simulates a WE 32200 at 22 MHz.\n\n"); +#else + fprintf(st, "3B2/400 CPU Help\n\n"); + fprintf(st, "The 3B2/400 CPU simulates a WE 32100 at 10 MHz.\n\n"); +#endif + + fprint_set_help(st, dptr); + fprint_show_help(st, dptr); + fprint_reg_help(st, dptr); + +#if defined(REV3) + fprintf(st, "\nAdditional documentation for the 3B2/700 Simulator is available on the web:\n\n"); +#else + fprintf(st, "\nAdditional documentation for the 3B2/400 Simulator is available on the web:\n\n"); +#endif + fprintf(st, " https://loomcom.com/3b2/emulator.html\n\n"); + + return SCPE_OK; +} diff --git a/3B2/3b2_cpu.h b/3B2/3b2_cpu.h index 054853e7..d7b0314b 100644 --- a/3B2/3b2_cpu.h +++ b/3B2/3b2_cpu.h @@ -1,684 +1,684 @@ -/* 3b2_cpu.h: WE32100 and WE32200 CPU - - Copyright (c) 2017-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#ifndef _3B2_CPU_H_ -#define _3B2_CPU_H_ - -#include "3b2_defs.h" - -/* Execution Modes */ -#define EX_LVL_KERN 0 -#define EX_LVL_EXEC 1 -#define EX_LVL_SUPR 2 -#define EX_LVL_USER 3 - -#define MAX_HIST_SIZE 10000000 -#define MIN_HIST_SIZE 64 -#define MEM_SIZE (cpu_unit.capac) - -#define UNIT_V_MSIZE (UNIT_V_UF) -#define UNIT_MSIZE (1 << UNIT_V_MSIZE) - -#define WD_MSB 0x80000000 -#define HW_MSB 0x8000 -#define BT_MSB 0x80 -#define WORD_MASK 0xffffffff -#define HALF_MASK 0xffffu -#define BYTE_MASK 0xff - -/* Exception Types */ -#define RESET_EXCEPTION 0 -#define PROCESS_EXCEPTION 1 -#define STACK_EXCEPTION 2 -#define NORMAL_EXCEPTION 3 - -/* Reset Exceptions */ -#define OLD_PCB_FAULT 0 -#define SYSTEM_DATA_FAULT 1 -#define INTERRUPT_STACK_FAULT 2 -#define EXTERNAL_RESET 3 -#define NEW_PCB_FAULT 4 -#define GATE_VECTOR_FAULT 6 - -/* Process Exceptions */ -#define GATE_PCB_FAULT 1 - -/* Stack Exceptions */ -#define STACK_BOUND 0 -#define STACK_FAULT 1 -#define INTERRUPT_ID_FETCH 3 - -/* Normal Exceptions */ -#define INTEGER_ZERO_DIVIDE 0 -#define TRACE_TRAP 1 -#define ILLEGAL_OPCODE 2 -#define RESERVED_OPCODE 3 -#define INVALID_DESCRIPTOR 4 -#define EXTERNAL_MEMORY_FAULT 5 -#define N_GATE_VECTOR 6 -#define ILLEGAL_LEVEL_CHANGE 7 -#define RESERVED_DATATYPE 8 -#define INTEGER_OVERFLOW 9 -#define PRIVILEGED_OPCODE 10 -#define BREAKPOINT_TRAP 14 -#define PRIVILEGED_REGISTER 15 - -#define PSW_ET 0 -#define PSW_TM 2 -#define PSW_ISC 3 -#define PSW_I 7 -#define PSW_R 8 -#define PSW_PM 9 -#define PSW_CM 11 -#define PSW_IPL 13 -#define PSW_TE 17 -#define PSW_C 18 -#define PSW_V 19 -#define PSW_Z 20 -#define PSW_N 21 -#define PSW_OE 22 -#define PSW_CD 23 -#define PSW_QIE 24 -#define PSW_CFD 25 - -#define PSW_ET_MASK 3u -#define PSW_TM_MASK (1u << PSW_TM) -#define PSW_ISC_MASK (15u << PSW_ISC) -#define PSW_I_MASK (1u << PSW_I) -#define PSW_R_MASK (1u << PSW_R) -#define PSW_PM_MASK (3u << PSW_PM) -#define PSW_CM_MASK (3u << PSW_CM) -#define PSW_IPL_MASK (15u << PSW_IPL) -#define PSW_TE_MASK (1u << PSW_TE) -#define PSW_C_MASK (1u << PSW_C) -#define PSW_V_MASK (1u << PSW_V) -#define PSW_N_MASK (1u << PSW_N) -#define PSW_Z_MASK (1u << PSW_Z) -#define PSW_OE_MASK (1u << PSW_OE) -#define PSW_CD_MASK (1u << PSW_CD) -#define PSW_QIE_MASK (1u << PSW_QIE) -#define PSW_CFD_MASK (1u << PSW_CFD) -#define PSW_CUR_IPL (((R[NUM_PSW] & PSW_IPL_MASK) >> PSW_IPL) & 0xf) - -#if defined(REV3) -#define PSW_X 26 -#define PSW_AR 27 -#define PSW_EXUC 28 -#define PSW_EA 29 - -#define PSW_X_MASK (1u << PSW_X) -#define PSW_AR_MASK (1u << PSW_AR) -#define PSW_EXUC_MASK (1u << PSW_EXUC) -#define PSW_EA_MASK (1u << PSW_EA) -#endif - -#if defined(REV3) -#define QIE_PSW_MASK (PSW_EA_MASK|PSW_EXUC_MASK|PSW_X_MASK| \ - PSW_CFD_MASK|PSW_QIE_MASK|PSW_CD_MASK|PSW_OE_MASK| \ - PSW_N_MASK|PSW_Z_MASK|PSW_V_MASK|PSW_C_MASK| \ - PSW_TE_MASK|PSW_IPL_MASK|PSW_PM_MASK|PSW_I_MASK) -#else -#define QIE_PSW_MASK (PSW_CFD_MASK|PSW_QIE_MASK|PSW_CD_MASK|PSW_OE_MASK| \ - PSW_N_MASK|PSW_Z_MASK|PSW_V_MASK|PSW_C_MASK| \ - PSW_TE_MASK|PSW_IPL_MASK|PSW_PM_MASK|PSW_I_MASK) -#endif - -/* A helper to set the PSW, preserving read-only fields */ -#define PSW_RO_MASK 0x17f /* ET, TM, ISC, and R are read-only! */ -#define WRITE_PSW(V) (R[NUM_PSW] = ((R[NUM_PSW] & PSW_RO_MASK) | ((V) & ~PSW_RO_MASK))) - -/* Exceptional conditions handled within the instruction loop */ -#define ABORT_EXC 1 /* CPU exception */ - -/* Contexts for aborts */ -#define C_NONE 0 /* No context. Normal handling. */ -#define C_NORMAL_GATE_VECTOR 1 -#define C_PROCESS_GATE_PCB 2 -#define C_PROCESS_OLD_PCB 3 -#define C_PROCESS_NEW_PCB 4 -#define C_RESET_GATE_VECTOR 5 -#define C_RESET_INT_STACK 6 -#define C_RESET_NEW_PCB 7 -#define C_RESET_SYSTEM_DATA 8 -#define C_STACK_FAULT 9 - -/* Register numbers */ -#define NUM_FP 9 -#define NUM_AP 10 -#define NUM_PSW 11 -#define NUM_SP 12 -#define NUM_PCBP 13 -#define NUM_ISP 14 -#define NUM_PC 15 - -/* System board interrupt priority levels */ -#define CPU_IPL_8 8 -#define CPU_IPL_9 9 -#define CPU_IPL_11 11 -#define CPU_IPL_13 13 -#define CPU_IPL_15 15 - -/* Processor permission levels */ -#define L_KERNEL 0 -#define L_EXEC 1 -#define L_SUPER 2 -#define L_USER 3 - -/* Currently selected processor permission level */ -#define CPU_CM (cpu_km ? L_KERNEL : ((R[NUM_PSW] >> PSW_CM) & 3)) - -/* Data types operated on by instructions. NB: These integer values - have meaning when decoding instructions, so this is not just an - enum. Please don't change them. */ -#define UW 0 /* Unsigned Word */ -#define UH 2 /* Unsigned Halfword */ -#define BT 3 /* Unsigned Byte */ -#define WD 4 /* Signed Word */ -#define HW 6 /* Signed Halfword */ -#define SB 7 /* Signed Byte */ - -#define NA -1 - -/* - * - * Mode Syntax Mode Reg. Bytes Notes - * ---------------------------------------------------------------------- - * Absolute $expr 7 15 5 - * Abs. Deferred *$expr 14 15 5 - * Byte Disp. expr(%rn) 12 0-10,12-15 2 - * Byte Disp. Def. *expr(%rn) 13 0-10,12-15 2 - * Halfword Disp. expr(%rn) 10 0-10,12-15 3 - * Halfword Disp. Def. *expr(%rn) 11 0-10,12-15 3 - * Word Disp. expr(%rn) 8 0-10,12-15 5 - * Word Disp. Def. *expr(%rn) 9 0-10,12-15 5 - * AP Short Offset so(%ap) 7 0-14 1 1 - * FP Short Offset so(%fp) 6 0-14 1 1 - * Byte Immediate &imm8 6 15 2 2,3 - * Halfword Immediate &imm16 5 15 3 2,3 - * Word Immediate &imm32 4 15 5 2,3 - * Positive Literal &lit 0-3 0-15 1 2,3 - * Negative Literal &lit 15 0-15 1 2,3 - * Register %rn 4 0-14 1 1,3 - * Register Deferred (%rn) 5 0-10,12-14 1 1 - * Expanded Op. Type {type}opnd 14 0-14 2-6 4 - * - * Notes: - * - * 1. Mode field has special meaning if register field is 15; see - * absolute or immediate mode. - * 2. Mode may not be used for a destination operand. - * 3. Mode may not be used if the instruction takes effective address - * of the operand. - * 4. 'type' overrides instruction type; 'type' determines the operand - * type, except that it does not determine the length for immediate - * or literals or whether literals are signed or unsigned. 'opnd' - * determines actual address mode. For total bytes, add 1 to byte - * count for address mode determined by 'opnd'. - * - */ - -/* - * Opcodes - */ -typedef enum { - SPOPRD = 0x02, - SPOPD2 = 0x03, - MOVAW = 0x04, - SPOPRT = 0x06, - SPOPT2 = 0x07, - RET = 0x08, -#if defined(REV3) - CASWI = 0x09, - SETX = 0x0A, - CLRX = 0x0B, -#endif - MOVTRW = 0x0C, -#if defined(REV3) - TEDTH = 0x0D, - PACKB = 0x0E, - UNPACKB = 0x0F, -#endif - SAVE = 0x10, - SPOPWD = 0x13, - EXTOP = 0x14, - SPOPWT = 0x17, - RESTORE = 0x18, -#if defined(REV3) - DTH = 0x19, -#endif - SWAPWI = 0x1C, -#if defined(REV3) - TGEDTH = 0x1D, -#endif - SWAPHI = 0x1E, - SWAPBI = 0x1F, - POPW = 0x20, - SPOPRS = 0x22, - SPOPS2 = 0x23, - JMP = 0x24, - CFLUSH = 0x27, - TSTW = 0x28, -#if defined(REV3) - DTB = 0x29, -#endif - TSTH = 0x2A, - TSTB = 0x2B, - CALL = 0x2C, -#if defined(REV3) - TGDTH = 0x2D, -#endif - BPT = 0x2E, - WAIT = 0x2F, - EMB = 0x30, /* Multi-byte */ - SPOP = 0x32, - SPOPWS = 0x33, - JSB = 0x34, - BSBH = 0x36, - BSBB = 0x37, - BITW = 0x38, - BITH = 0x3A, - BITB = 0x3B, - CMPW = 0x3C, -#if defined(REV3) - TNEDTH = 0x3D, -#endif - CMPH = 0x3E, - CMPB = 0x3F, - RGEQ = 0x40, - BGEH = 0x42, - BGEB = 0x43, - RGTR = 0x44, - BGH = 0x46, - BGB = 0x47, - RLSS = 0x48, - BLH = 0x4A, - BLB = 0x4B, - RLEQ = 0x4C, -#if defined(REV3) - TEDTB = 0x4D, -#endif - BLEH = 0x4E, - BLEB = 0x4F, - RGEQU = 0x50, - BGEUH = 0x52, - BGEUB = 0x53, - RGTRU = 0x54, - BGUH = 0x56, - BGUB = 0x57, - RLSSU = 0x58, - BLUH = 0x5A, - BLUB = 0x5B, - RLEQU = 0x5C, -#if defined(REV3) - TGEDTB = 0x5D, -#endif - BLEUH = 0x5E, - BLEUB = 0x5F, - RVC = 0x60, - BVCH = 0x62, - BVCB = 0x63, - RNEQU = 0x64, - BNEH_D = 0x66, - BNEB_D = 0x67, - RVS = 0x68, - BVSH = 0x6A, - BVSB = 0x6B, - REQLU = 0x6C, -#if defined(REV3) - TGDTB = 0x6D, -#endif - BEH_D = 0x6E, - BEB_D = 0x6F, - NOP = 0x70, - NOP3 = 0x72, - NOP2 = 0x73, - RNEQ = 0x74, - BNEH = 0x76, - BNEB = 0x77, - RSB = 0x78, - BRH = 0x7A, - BRB = 0x7B, - REQL = 0x7C, -#if defined(REV3) - TNEDTB = 0x7D, -#endif - BEH = 0x7E, - BEB = 0x7F, - CLRW = 0x80, - CLRH = 0x82, - CLRB = 0x83, - MOVW = 0x84, - MOVH = 0x86, - MOVB = 0x87, - MCOMW = 0x88, - MCOMH = 0x8A, - MCOMB = 0x8B, - MNEGW = 0x8C, - MNEGH = 0x8E, - MNEGB = 0x8F, - INCW = 0x90, - INCH = 0x92, - INCB = 0x93, - DECW = 0x94, - DECH = 0x96, - DECB = 0x97, -#if defined(REV3) - RETQINT = 0x98, - SUBPB2 = 0x9B, -#endif - ADDW2 = 0x9C, - ADDH2 = 0x9E, - ADDB2 = 0x9F, - PUSHW = 0xA0, -#if defined(REV3) - ADDPB2 = 0xA3, -#endif - MODW2 = 0xA4, - MODH2 = 0xA6, - MODB2 = 0xA7, - MULW2 = 0xA8, - MULH2 = 0xAA, - MULB2 = 0xAB, - DIVW2 = 0xAC, - DIVH2 = 0xAE, - DIVB2 = 0xAF, - ORW2 = 0xB0, - ORH2 = 0xB2, - ORB2 = 0xB3, - XORW2 = 0xB4, - XORH2 = 0xB6, - XORB2 = 0xB7, - ANDW2 = 0xB8, - ANDH2 = 0xBA, - ANDB2 = 0xBB, - SUBW2 = 0xBC, - SUBH2 = 0xBE, - SUBB2 = 0xBF, - ALSW3 = 0xC0, - ARSW3 = 0xC4, - ARSH3 = 0xC6, - ARSB3 = 0xC7, - INSFW = 0xC8, - INSFH = 0xCA, - INSFB = 0xCB, - EXTFW = 0xCC, - EXTFH = 0xCE, - EXTFB = 0xCF, - LLSW3 = 0xD0, - LLSH3 = 0xD2, - LLSB3 = 0xD3, - LRSW3 = 0xD4, - ROTW = 0xD8, -#if defined(REV3) - SUBPB3 = 0xDB, -#endif - ADDW3 = 0xDC, - ADDH3 = 0xDE, - ADDB3 = 0xDF, - PUSHAW = 0xE0, -#if defined(REV3) - ADDPB3 = 0xE3, -#endif - MODW3 = 0xE4, - MODH3 = 0xE6, - MODB3 = 0xE7, - MULW3 = 0xE8, - MULH3 = 0xEA, - MULB3 = 0xEB, - DIVW3 = 0xEC, - DIVH3 = 0xEE, - DIVB3 = 0xEF, - ORW3 = 0xF0, - ORH3 = 0xF2, - ORB3 = 0xF3, - XORW3 = 0xF4, - XORH3 = 0xF6, - XORB3 = 0xF7, - ANDW3 = 0xF8, - ANDH3 = 0xFA, - ANDB3 = 0xFB, - SUBW3 = 0xFC, - SUBH3 = 0xFE, - SUBB3 = 0xFF, - MVERNO = 0x3009, - ENBVJMP = 0x300d, - DISVJMP = 0x3013, - MOVBLW = 0x3019, - STREND = 0x301f, - INTACK = 0x302f, - STRCPY = 0x3035, - RETG = 0x3045, - GATE = 0x3061, - CALLPS = 0x30ac, -#if defined(REV3) - UCALLPS = 0x30c0, -#endif - RETPS = 0x30c8 -} opcode; - -/* - * Each instruction expects operands of a certain type. - * - * The large majority of instructions expect operands that have a - * descriptor as the first byte. This descriptor carries all the - * information necessary to compute the addressing mode of the - * operand. - * - * e.g.: - * - * MOVB 6(%r1),%r0 - * +------+------+------+------+ - * | 0x87 | 0xc1 | 0x06 | 0x40 | - * +------+------+------+------+ - * ^^^^^^ - * Descriptor byte. mode = 13 (0x0c), register = 1 (0x01) - * - * - * Branch instructions have either an 8-bit or a 16-bit signed - * displacement value, and lack a descriptor byte. - * - * e.g.: - * - * BCCB 0x03 - * +------+------+ - * | 0x53 | 0x03 | 8-bit displacement - * +------+------+ - * - * BCCH 0x01ff - * +------+------+------+ - * | 0x52 | 0xff | 0x01 | 16-bit displacement - * +------+------+------+ - * - * - * TODO: Describe coprocessor instructions - * - */ -typedef enum { - OP_NONE, /* NULL type */ - OP_DESC, /* Descriptor byte */ - OP_DESB, /* Descriptor with byte displacement (WE32200 only) */ - OP_DESH, /* Descriptor with halfword displacement (WE32200 only) */ - OP_BYTE, /* 8-bit signed value */ - OP_HALF, /* 16-bit signed value */ - OP_COPR /* Coprocessor instruction */ -} op_mode; - -/* Describes a CPU opcode. - * - * e.g.: - * - * {0x09, 3, OP_DESC, WD, "CASWI", 0, 1, -1, 2} - * - * - Opcode 0x09. - * - Followed by three operands. - * - Operands use descriptor bytes. - * - Default data type is word (32 bit). - * - Operand 0 is source 1. - * - Operand 1 is source 2. - * - Operand 2 is destination. - * - */ -typedef struct { - uint16 opcode; - int8 op_count; /* Number of operands */ - op_mode mode; /* Dispatch mode */ - int8 dtype; /* Default data type */ - char mnemonic[8]; - int8 src_op1; - int8 src_op2; - int8 src_op3; - int8 dst_op; -} mnemonic; - -/* - * Structure that describes each operand in a decoded instruction - */ -typedef struct { - uint8 mode; /* Embedded data addressing mode */ - uint8 reg; /* Operand register (0-15) */ -#if defined(REV3) - uint8 reg2; /* Operand register 2 (16-31) */ -#endif - int8 dtype; /* Default type for the operand */ - int8 etype; /* Expanded type (-1 if none) */ - union { - uint32 w; - uint16 h; - uint8 b; - } embedded; /* Data consumed as part of the instruction - stream, i.e. literals, displacement, - etc. */ - uint32 data; /* Data either read or written during - instruction execution */ -} operand; - -/* - * An inst is a combination of a decoded instruction and - * 0 to 4 operands. Also used for history record keeping. - */ -typedef struct { - mnemonic *mn; - uint32 psw; - uint32 sp; - uint32 pc; - t_bool valid; - operand operands[4]; -} instr; - -/* Function prototypes */ -t_stat sys_boot(int32 flag, CONST char *ptr); -t_stat cpu_svc(UNIT *uptr); -t_stat cpu_ex(t_value *vptr, t_addr addr, UNIT *uptr, int32 sw); -t_stat cpu_dep(t_value val, t_addr addr, UNIT *uptr, int32 sw); -t_stat cpu_reset(DEVICE *dptr); -t_stat cpu_set_size(UNIT *uptr, int32 val, CONST char *cptr, void *desc); -t_stat cpu_set_hist(UNIT *uptr, int32 val, CONST char *cptr, void *desc); -t_stat cpu_show_hist(FILE *st, UNIT *uptr, int32 val, CONST void *desc); -t_stat cpu_show_virt(FILE *st, UNIT *uptr, int32 val, CONST void *desc); -t_stat cpu_show_stack(FILE *st, UNIT *uptr, int32 val, CONST void *desc); -t_stat cpu_show_cio(FILE *st, UNIT *uptr, int32 val, CONST void *desc); -t_stat cpu_set_halt(UNIT *uptr, int32 val, char *cptr, void *desc); -t_stat cpu_clear_halt(UNIT *uptr, int32 val, char *cptr, void *desc); -t_stat cpu_boot(int32 unit_num, DEVICE *dptr); -t_stat cpu_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); -CONST char *cpu_description(DEVICE *dptr); - -t_bool cpu_is_pc_a_subroutine_call (t_addr **ret_addrs); - -void cpu_register_name(uint8 reg, char *buf, size_t len); -void cpu_show_operand(FILE *st, operand *op); -void fprint_sym_hist(FILE *st, instr *ip); -t_stat fprint_sym_m(FILE *of, t_addr addr, t_value *val); - -instr *cpu_next_instruction(void); - -uint8 decode_instruction(instr *instr); -void cpu_on_interrupt(uint16 vec); -void cpu_abort(uint8 et, uint8 isc); - -/* Helper macros */ - -#define MOD(A,B,OP1,OP2,SZ) { \ - if (op_signed(OP1) && !op_signed(OP2)) { \ - result = (SZ)(B) % (A); \ - } else if (!op_signed(OP1) && op_signed(OP2)) { \ - result = (B) % (SZ)(A); \ - } else if (op_signed(OP1) && op_signed(OP2)) { \ - result = (SZ)(B) % (SZ)(A); \ - } else { \ - result = (B) % (A); \ - } \ - } - -#define DIV(A,B,OP1,OP2,SZ) { \ - if (op_signed(OP1) && !op_signed(OP2)) { \ - result = (SZ)(B) / (A); \ - } else if (!op_signed(OP1) && op_signed(OP2)) { \ - result = (B) / (SZ)(A); \ - } else if (op_signed(OP1) && op_signed(OP2)) { \ - result = (SZ)(B) / (SZ)(A); \ - } else { \ - result = (B) / (A); \ - } \ - } - -#define OP_R_W(d,a,p) { \ - (d) = (uint32) (a)[(p)++]; \ - (d) |= (uint32) (a)[(p)++] << 8u; \ - (d) |= (uint32) (a)[(p)++] << 16u; \ - (d) |= (uint32) (a)[(p)++] << 24u; \ - } - -#define OP_R_H(d,a,p) { \ - (d) = (uint16) (a)[(p)++]; \ - (d) |= (uint16) (a)[(p)++] << 8u; \ - } - -#define OP_R_B(d,a,p) { \ - (d) = (uint8) (a)[(p)++]; \ - } - -#define CPU_SET_INT(flags) (sbd_int_req |= flags) -#define CPU_CLR_INT(flags) (sbd_int_req &= ~(flags)) - -extern t_bool rom_loaded; -extern volatile int32 stop_reason; -extern uint16 sbd_int_req; -extern instr *cpu_instr; -extern t_bool cpu_nmi; -extern uint8 *ROM; -extern uint8 *RAM; -extern uint32 R[NUM_REGISTERS]; -extern REG cpu_reg[]; -extern UNIT cpu_unit; -extern uint8 fault; -extern t_bool cpu_km; - -#endif +/* 3b2_cpu.h: WE32100 and WE32200 CPU + + Copyright (c) 2017-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#ifndef _3B2_CPU_H_ +#define _3B2_CPU_H_ + +#include "3b2_defs.h" + +/* Execution Modes */ +#define EX_LVL_KERN 0 +#define EX_LVL_EXEC 1 +#define EX_LVL_SUPR 2 +#define EX_LVL_USER 3 + +#define MAX_HIST_SIZE 10000000 +#define MIN_HIST_SIZE 64 +#define MEM_SIZE (cpu_unit.capac) + +#define UNIT_V_MSIZE (UNIT_V_UF) +#define UNIT_MSIZE (1 << UNIT_V_MSIZE) + +#define WD_MSB 0x80000000 +#define HW_MSB 0x8000 +#define BT_MSB 0x80 +#define WORD_MASK 0xffffffff +#define HALF_MASK 0xffffu +#define BYTE_MASK 0xff + +/* Exception Types */ +#define RESET_EXCEPTION 0 +#define PROCESS_EXCEPTION 1 +#define STACK_EXCEPTION 2 +#define NORMAL_EXCEPTION 3 + +/* Reset Exceptions */ +#define OLD_PCB_FAULT 0 +#define SYSTEM_DATA_FAULT 1 +#define INTERRUPT_STACK_FAULT 2 +#define EXTERNAL_RESET 3 +#define NEW_PCB_FAULT 4 +#define GATE_VECTOR_FAULT 6 + +/* Process Exceptions */ +#define GATE_PCB_FAULT 1 + +/* Stack Exceptions */ +#define STACK_BOUND 0 +#define STACK_FAULT 1 +#define INTERRUPT_ID_FETCH 3 + +/* Normal Exceptions */ +#define INTEGER_ZERO_DIVIDE 0 +#define TRACE_TRAP 1 +#define ILLEGAL_OPCODE 2 +#define RESERVED_OPCODE 3 +#define INVALID_DESCRIPTOR 4 +#define EXTERNAL_MEMORY_FAULT 5 +#define N_GATE_VECTOR 6 +#define ILLEGAL_LEVEL_CHANGE 7 +#define RESERVED_DATATYPE 8 +#define INTEGER_OVERFLOW 9 +#define PRIVILEGED_OPCODE 10 +#define BREAKPOINT_TRAP 14 +#define PRIVILEGED_REGISTER 15 + +#define PSW_ET 0 +#define PSW_TM 2 +#define PSW_ISC 3 +#define PSW_I 7 +#define PSW_R 8 +#define PSW_PM 9 +#define PSW_CM 11 +#define PSW_IPL 13 +#define PSW_TE 17 +#define PSW_C 18 +#define PSW_V 19 +#define PSW_Z 20 +#define PSW_N 21 +#define PSW_OE 22 +#define PSW_CD 23 +#define PSW_QIE 24 +#define PSW_CFD 25 + +#define PSW_ET_MASK 3u +#define PSW_TM_MASK (1u << PSW_TM) +#define PSW_ISC_MASK (15u << PSW_ISC) +#define PSW_I_MASK (1u << PSW_I) +#define PSW_R_MASK (1u << PSW_R) +#define PSW_PM_MASK (3u << PSW_PM) +#define PSW_CM_MASK (3u << PSW_CM) +#define PSW_IPL_MASK (15u << PSW_IPL) +#define PSW_TE_MASK (1u << PSW_TE) +#define PSW_C_MASK (1u << PSW_C) +#define PSW_V_MASK (1u << PSW_V) +#define PSW_N_MASK (1u << PSW_N) +#define PSW_Z_MASK (1u << PSW_Z) +#define PSW_OE_MASK (1u << PSW_OE) +#define PSW_CD_MASK (1u << PSW_CD) +#define PSW_QIE_MASK (1u << PSW_QIE) +#define PSW_CFD_MASK (1u << PSW_CFD) +#define PSW_CUR_IPL (((R[NUM_PSW] & PSW_IPL_MASK) >> PSW_IPL) & 0xf) + +#if defined(REV3) +#define PSW_X 26 +#define PSW_AR 27 +#define PSW_EXUC 28 +#define PSW_EA 29 + +#define PSW_X_MASK (1u << PSW_X) +#define PSW_AR_MASK (1u << PSW_AR) +#define PSW_EXUC_MASK (1u << PSW_EXUC) +#define PSW_EA_MASK (1u << PSW_EA) +#endif + +#if defined(REV3) +#define QIE_PSW_MASK (PSW_EA_MASK|PSW_EXUC_MASK|PSW_X_MASK| \ + PSW_CFD_MASK|PSW_QIE_MASK|PSW_CD_MASK|PSW_OE_MASK| \ + PSW_N_MASK|PSW_Z_MASK|PSW_V_MASK|PSW_C_MASK| \ + PSW_TE_MASK|PSW_IPL_MASK|PSW_PM_MASK|PSW_I_MASK) +#else +#define QIE_PSW_MASK (PSW_CFD_MASK|PSW_QIE_MASK|PSW_CD_MASK|PSW_OE_MASK| \ + PSW_N_MASK|PSW_Z_MASK|PSW_V_MASK|PSW_C_MASK| \ + PSW_TE_MASK|PSW_IPL_MASK|PSW_PM_MASK|PSW_I_MASK) +#endif + +/* A helper to set the PSW, preserving read-only fields */ +#define PSW_RO_MASK 0x17f /* ET, TM, ISC, and R are read-only! */ +#define WRITE_PSW(V) (R[NUM_PSW] = ((R[NUM_PSW] & PSW_RO_MASK) | ((V) & ~PSW_RO_MASK))) + +/* Exceptional conditions handled within the instruction loop */ +#define ABORT_EXC 1 /* CPU exception */ + +/* Contexts for aborts */ +#define C_NONE 0 /* No context. Normal handling. */ +#define C_NORMAL_GATE_VECTOR 1 +#define C_PROCESS_GATE_PCB 2 +#define C_PROCESS_OLD_PCB 3 +#define C_PROCESS_NEW_PCB 4 +#define C_RESET_GATE_VECTOR 5 +#define C_RESET_INT_STACK 6 +#define C_RESET_NEW_PCB 7 +#define C_RESET_SYSTEM_DATA 8 +#define C_STACK_FAULT 9 + +/* Register numbers */ +#define NUM_FP 9 +#define NUM_AP 10 +#define NUM_PSW 11 +#define NUM_SP 12 +#define NUM_PCBP 13 +#define NUM_ISP 14 +#define NUM_PC 15 + +/* System board interrupt priority levels */ +#define CPU_IPL_8 8 +#define CPU_IPL_9 9 +#define CPU_IPL_11 11 +#define CPU_IPL_13 13 +#define CPU_IPL_15 15 + +/* Processor permission levels */ +#define L_KERNEL 0 +#define L_EXEC 1 +#define L_SUPER 2 +#define L_USER 3 + +/* Currently selected processor permission level */ +#define CPU_CM (cpu_km ? L_KERNEL : ((R[NUM_PSW] >> PSW_CM) & 3)) + +/* Data types operated on by instructions. NB: These integer values + have meaning when decoding instructions, so this is not just an + enum. Please don't change them. */ +#define UW 0 /* Unsigned Word */ +#define UH 2 /* Unsigned Halfword */ +#define BT 3 /* Unsigned Byte */ +#define WD 4 /* Signed Word */ +#define HW 6 /* Signed Halfword */ +#define SB 7 /* Signed Byte */ + +#define NA -1 + +/* + * + * Mode Syntax Mode Reg. Bytes Notes + * ---------------------------------------------------------------------- + * Absolute $expr 7 15 5 + * Abs. Deferred *$expr 14 15 5 + * Byte Disp. expr(%rn) 12 0-10,12-15 2 + * Byte Disp. Def. *expr(%rn) 13 0-10,12-15 2 + * Halfword Disp. expr(%rn) 10 0-10,12-15 3 + * Halfword Disp. Def. *expr(%rn) 11 0-10,12-15 3 + * Word Disp. expr(%rn) 8 0-10,12-15 5 + * Word Disp. Def. *expr(%rn) 9 0-10,12-15 5 + * AP Short Offset so(%ap) 7 0-14 1 1 + * FP Short Offset so(%fp) 6 0-14 1 1 + * Byte Immediate &imm8 6 15 2 2,3 + * Halfword Immediate &imm16 5 15 3 2,3 + * Word Immediate &imm32 4 15 5 2,3 + * Positive Literal &lit 0-3 0-15 1 2,3 + * Negative Literal &lit 15 0-15 1 2,3 + * Register %rn 4 0-14 1 1,3 + * Register Deferred (%rn) 5 0-10,12-14 1 1 + * Expanded Op. Type {type}opnd 14 0-14 2-6 4 + * + * Notes: + * + * 1. Mode field has special meaning if register field is 15; see + * absolute or immediate mode. + * 2. Mode may not be used for a destination operand. + * 3. Mode may not be used if the instruction takes effective address + * of the operand. + * 4. 'type' overrides instruction type; 'type' determines the operand + * type, except that it does not determine the length for immediate + * or literals or whether literals are signed or unsigned. 'opnd' + * determines actual address mode. For total bytes, add 1 to byte + * count for address mode determined by 'opnd'. + * + */ + +/* + * Opcodes + */ +typedef enum { + SPOPRD = 0x02, + SPOPD2 = 0x03, + MOVAW = 0x04, + SPOPRT = 0x06, + SPOPT2 = 0x07, + RET = 0x08, +#if defined(REV3) + CASWI = 0x09, + SETX = 0x0A, + CLRX = 0x0B, +#endif + MOVTRW = 0x0C, +#if defined(REV3) + TEDTH = 0x0D, + PACKB = 0x0E, + UNPACKB = 0x0F, +#endif + SAVE = 0x10, + SPOPWD = 0x13, + EXTOP = 0x14, + SPOPWT = 0x17, + RESTORE = 0x18, +#if defined(REV3) + DTH = 0x19, +#endif + SWAPWI = 0x1C, +#if defined(REV3) + TGEDTH = 0x1D, +#endif + SWAPHI = 0x1E, + SWAPBI = 0x1F, + POPW = 0x20, + SPOPRS = 0x22, + SPOPS2 = 0x23, + JMP = 0x24, + CFLUSH = 0x27, + TSTW = 0x28, +#if defined(REV3) + DTB = 0x29, +#endif + TSTH = 0x2A, + TSTB = 0x2B, + CALL = 0x2C, +#if defined(REV3) + TGDTH = 0x2D, +#endif + BPT = 0x2E, + WAIT = 0x2F, + EMB = 0x30, /* Multi-byte */ + SPOP = 0x32, + SPOPWS = 0x33, + JSB = 0x34, + BSBH = 0x36, + BSBB = 0x37, + BITW = 0x38, + BITH = 0x3A, + BITB = 0x3B, + CMPW = 0x3C, +#if defined(REV3) + TNEDTH = 0x3D, +#endif + CMPH = 0x3E, + CMPB = 0x3F, + RGEQ = 0x40, + BGEH = 0x42, + BGEB = 0x43, + RGTR = 0x44, + BGH = 0x46, + BGB = 0x47, + RLSS = 0x48, + BLH = 0x4A, + BLB = 0x4B, + RLEQ = 0x4C, +#if defined(REV3) + TEDTB = 0x4D, +#endif + BLEH = 0x4E, + BLEB = 0x4F, + RGEQU = 0x50, + BGEUH = 0x52, + BGEUB = 0x53, + RGTRU = 0x54, + BGUH = 0x56, + BGUB = 0x57, + RLSSU = 0x58, + BLUH = 0x5A, + BLUB = 0x5B, + RLEQU = 0x5C, +#if defined(REV3) + TGEDTB = 0x5D, +#endif + BLEUH = 0x5E, + BLEUB = 0x5F, + RVC = 0x60, + BVCH = 0x62, + BVCB = 0x63, + RNEQU = 0x64, + BNEH_D = 0x66, + BNEB_D = 0x67, + RVS = 0x68, + BVSH = 0x6A, + BVSB = 0x6B, + REQLU = 0x6C, +#if defined(REV3) + TGDTB = 0x6D, +#endif + BEH_D = 0x6E, + BEB_D = 0x6F, + NOP = 0x70, + NOP3 = 0x72, + NOP2 = 0x73, + RNEQ = 0x74, + BNEH = 0x76, + BNEB = 0x77, + RSB = 0x78, + BRH = 0x7A, + BRB = 0x7B, + REQL = 0x7C, +#if defined(REV3) + TNEDTB = 0x7D, +#endif + BEH = 0x7E, + BEB = 0x7F, + CLRW = 0x80, + CLRH = 0x82, + CLRB = 0x83, + MOVW = 0x84, + MOVH = 0x86, + MOVB = 0x87, + MCOMW = 0x88, + MCOMH = 0x8A, + MCOMB = 0x8B, + MNEGW = 0x8C, + MNEGH = 0x8E, + MNEGB = 0x8F, + INCW = 0x90, + INCH = 0x92, + INCB = 0x93, + DECW = 0x94, + DECH = 0x96, + DECB = 0x97, +#if defined(REV3) + RETQINT = 0x98, + SUBPB2 = 0x9B, +#endif + ADDW2 = 0x9C, + ADDH2 = 0x9E, + ADDB2 = 0x9F, + PUSHW = 0xA0, +#if defined(REV3) + ADDPB2 = 0xA3, +#endif + MODW2 = 0xA4, + MODH2 = 0xA6, + MODB2 = 0xA7, + MULW2 = 0xA8, + MULH2 = 0xAA, + MULB2 = 0xAB, + DIVW2 = 0xAC, + DIVH2 = 0xAE, + DIVB2 = 0xAF, + ORW2 = 0xB0, + ORH2 = 0xB2, + ORB2 = 0xB3, + XORW2 = 0xB4, + XORH2 = 0xB6, + XORB2 = 0xB7, + ANDW2 = 0xB8, + ANDH2 = 0xBA, + ANDB2 = 0xBB, + SUBW2 = 0xBC, + SUBH2 = 0xBE, + SUBB2 = 0xBF, + ALSW3 = 0xC0, + ARSW3 = 0xC4, + ARSH3 = 0xC6, + ARSB3 = 0xC7, + INSFW = 0xC8, + INSFH = 0xCA, + INSFB = 0xCB, + EXTFW = 0xCC, + EXTFH = 0xCE, + EXTFB = 0xCF, + LLSW3 = 0xD0, + LLSH3 = 0xD2, + LLSB3 = 0xD3, + LRSW3 = 0xD4, + ROTW = 0xD8, +#if defined(REV3) + SUBPB3 = 0xDB, +#endif + ADDW3 = 0xDC, + ADDH3 = 0xDE, + ADDB3 = 0xDF, + PUSHAW = 0xE0, +#if defined(REV3) + ADDPB3 = 0xE3, +#endif + MODW3 = 0xE4, + MODH3 = 0xE6, + MODB3 = 0xE7, + MULW3 = 0xE8, + MULH3 = 0xEA, + MULB3 = 0xEB, + DIVW3 = 0xEC, + DIVH3 = 0xEE, + DIVB3 = 0xEF, + ORW3 = 0xF0, + ORH3 = 0xF2, + ORB3 = 0xF3, + XORW3 = 0xF4, + XORH3 = 0xF6, + XORB3 = 0xF7, + ANDW3 = 0xF8, + ANDH3 = 0xFA, + ANDB3 = 0xFB, + SUBW3 = 0xFC, + SUBH3 = 0xFE, + SUBB3 = 0xFF, + MVERNO = 0x3009, + ENBVJMP = 0x300d, + DISVJMP = 0x3013, + MOVBLW = 0x3019, + STREND = 0x301f, + INTACK = 0x302f, + STRCPY = 0x3035, + RETG = 0x3045, + GATE = 0x3061, + CALLPS = 0x30ac, +#if defined(REV3) + UCALLPS = 0x30c0, +#endif + RETPS = 0x30c8 +} opcode; + +/* + * Each instruction expects operands of a certain type. + * + * The large majority of instructions expect operands that have a + * descriptor as the first byte. This descriptor carries all the + * information necessary to compute the addressing mode of the + * operand. + * + * e.g.: + * + * MOVB 6(%r1),%r0 + * +------+------+------+------+ + * | 0x87 | 0xc1 | 0x06 | 0x40 | + * +------+------+------+------+ + * ^^^^^^ + * Descriptor byte. mode = 13 (0x0c), register = 1 (0x01) + * + * + * Branch instructions have either an 8-bit or a 16-bit signed + * displacement value, and lack a descriptor byte. + * + * e.g.: + * + * BCCB 0x03 + * +------+------+ + * | 0x53 | 0x03 | 8-bit displacement + * +------+------+ + * + * BCCH 0x01ff + * +------+------+------+ + * | 0x52 | 0xff | 0x01 | 16-bit displacement + * +------+------+------+ + * + * + * TODO: Describe coprocessor instructions + * + */ +typedef enum { + OP_NONE, /* NULL type */ + OP_DESC, /* Descriptor byte */ + OP_DESB, /* Descriptor with byte displacement (WE32200 only) */ + OP_DESH, /* Descriptor with halfword displacement (WE32200 only) */ + OP_BYTE, /* 8-bit signed value */ + OP_HALF, /* 16-bit signed value */ + OP_COPR /* Coprocessor instruction */ +} op_mode; + +/* Describes a CPU opcode. + * + * e.g.: + * + * {0x09, 3, OP_DESC, WD, "CASWI", 0, 1, -1, 2} + * + * - Opcode 0x09. + * - Followed by three operands. + * - Operands use descriptor bytes. + * - Default data type is word (32 bit). + * - Operand 0 is source 1. + * - Operand 1 is source 2. + * - Operand 2 is destination. + * + */ +typedef struct { + uint16 opcode; + int8 op_count; /* Number of operands */ + op_mode mode; /* Dispatch mode */ + int8 dtype; /* Default data type */ + char mnemonic[8]; + int8 src_op1; + int8 src_op2; + int8 src_op3; + int8 dst_op; +} mnemonic; + +/* + * Structure that describes each operand in a decoded instruction + */ +typedef struct { + uint8 mode; /* Embedded data addressing mode */ + uint8 reg; /* Operand register (0-15) */ +#if defined(REV3) + uint8 reg2; /* Operand register 2 (16-31) */ +#endif + int8 dtype; /* Default type for the operand */ + int8 etype; /* Expanded type (-1 if none) */ + union { + uint32 w; + uint16 h; + uint8 b; + } embedded; /* Data consumed as part of the instruction + stream, i.e. literals, displacement, + etc. */ + uint32 data; /* Data either read or written during + instruction execution */ +} operand; + +/* + * An inst is a combination of a decoded instruction and + * 0 to 4 operands. Also used for history record keeping. + */ +typedef struct { + mnemonic *mn; + uint32 psw; + uint32 sp; + uint32 pc; + t_bool valid; + operand operands[4]; +} instr; + +/* Function prototypes */ +t_stat sys_boot(int32 flag, CONST char *ptr); +t_stat cpu_svc(UNIT *uptr); +t_stat cpu_ex(t_value *vptr, t_addr addr, UNIT *uptr, int32 sw); +t_stat cpu_dep(t_value val, t_addr addr, UNIT *uptr, int32 sw); +t_stat cpu_reset(DEVICE *dptr); +t_stat cpu_set_size(UNIT *uptr, int32 val, CONST char *cptr, void *desc); +t_stat cpu_set_hist(UNIT *uptr, int32 val, CONST char *cptr, void *desc); +t_stat cpu_show_hist(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +t_stat cpu_show_virt(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +t_stat cpu_show_stack(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +t_stat cpu_show_cio(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +t_stat cpu_set_halt(UNIT *uptr, int32 val, char *cptr, void *desc); +t_stat cpu_clear_halt(UNIT *uptr, int32 val, char *cptr, void *desc); +t_stat cpu_boot(int32 unit_num, DEVICE *dptr); +t_stat cpu_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); +CONST char *cpu_description(DEVICE *dptr); + +t_bool cpu_is_pc_a_subroutine_call (t_addr **ret_addrs); + +void cpu_register_name(uint8 reg, char *buf, size_t len); +void cpu_show_operand(FILE *st, operand *op); +void fprint_sym_hist(FILE *st, instr *ip); +t_stat fprint_sym_m(FILE *of, t_addr addr, t_value *val); + +instr *cpu_next_instruction(void); + +uint8 decode_instruction(instr *instr); +void cpu_on_interrupt(uint16 vec); +void cpu_abort(uint8 et, uint8 isc); + +/* Helper macros */ + +#define MOD(A,B,OP1,OP2,SZ) { \ + if (op_signed(OP1) && !op_signed(OP2)) { \ + result = (SZ)(B) % (A); \ + } else if (!op_signed(OP1) && op_signed(OP2)) { \ + result = (B) % (SZ)(A); \ + } else if (op_signed(OP1) && op_signed(OP2)) { \ + result = (SZ)(B) % (SZ)(A); \ + } else { \ + result = (B) % (A); \ + } \ + } + +#define DIV(A,B,OP1,OP2,SZ) { \ + if (op_signed(OP1) && !op_signed(OP2)) { \ + result = (SZ)(B) / (A); \ + } else if (!op_signed(OP1) && op_signed(OP2)) { \ + result = (B) / (SZ)(A); \ + } else if (op_signed(OP1) && op_signed(OP2)) { \ + result = (SZ)(B) / (SZ)(A); \ + } else { \ + result = (B) / (A); \ + } \ + } + +#define OP_R_W(d,a,p) { \ + (d) = (uint32) (a)[(p)++]; \ + (d) |= (uint32) (a)[(p)++] << 8u; \ + (d) |= (uint32) (a)[(p)++] << 16u; \ + (d) |= (uint32) (a)[(p)++] << 24u; \ + } + +#define OP_R_H(d,a,p) { \ + (d) = (uint16) (a)[(p)++]; \ + (d) |= (uint16) (a)[(p)++] << 8u; \ + } + +#define OP_R_B(d,a,p) { \ + (d) = (uint8) (a)[(p)++]; \ + } + +#define CPU_SET_INT(flags) (sbd_int_req |= flags) +#define CPU_CLR_INT(flags) (sbd_int_req &= ~(flags)) + +extern t_bool rom_loaded; +extern volatile int32 stop_reason; +extern uint16 sbd_int_req; +extern instr *cpu_instr; +extern t_bool cpu_nmi; +extern uint8 *ROM; +extern uint8 *RAM; +extern uint32 R[NUM_REGISTERS]; +extern REG cpu_reg[]; +extern UNIT cpu_unit; +extern uint8 fault; +extern t_bool cpu_km; + +#endif diff --git a/3B2/3b2_csr.h b/3B2/3b2_csr.h index 3247f222..d3b1837a 100644 --- a/3B2/3b2_csr.h +++ b/3B2/3b2_csr.h @@ -1,46 +1,46 @@ -/* 3b2_csr.h: Common CSR/CSER header - - Copyright (c) 2021-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#ifndef _3B2_CSR_H_ -#define _3B2_CSR_H_ - -#if defined(REV3) -#include "3b2_rev3_csr.h" -#else -#include "3b2_rev2_csr.h" -#endif - -#define SET_CSR(FLAGS) (csr_data |= (FLAGS)) -#define CLR_CSR(FLAGS) (csr_data &= ~(FLAGS)) -#define CSR(FLAGS) ((csr_data & FLAGS) != 0) - -extern CSR_DATA csr_data; - -#endif /* _3B2_CSR_H_ */ +/* 3b2_csr.h: Common CSR/CSER header + + Copyright (c) 2021-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#ifndef _3B2_CSR_H_ +#define _3B2_CSR_H_ + +#if defined(REV3) +#include "3b2_rev3_csr.h" +#else +#include "3b2_rev2_csr.h" +#endif + +#define SET_CSR(FLAGS) (csr_data |= (FLAGS)) +#define CLR_CSR(FLAGS) (csr_data &= ~(FLAGS)) +#define CSR(FLAGS) ((csr_data & FLAGS) != 0) + +extern CSR_DATA csr_data; + +#endif /* _3B2_CSR_H_ */ diff --git a/3B2/3b2_ctc.c b/3B2/3b2_ctc.c index b6a0c896..657e427c 100644 --- a/3B2/3b2_ctc.c +++ b/3B2/3b2_ctc.c @@ -1,748 +1,748 @@ -/* 3b2_ctc.c: CM195H 23MB Cartridge Tape Controller CIO Card - - Copyright (c) 2018-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#include "3b2_ctc.h" - -#include "sim_disk.h" - -#include "3b2_io.h" -#include "3b2_mem.h" - -#define CTQRESIZE 20 -#define CTQCESIZE 16 - -#define DELAY_SYSGEN 2500 -#define DELAY_FMT 1000000 -#define DELAY_RW 10000 -#define DELAY_OPEN 2500 -#define DELAY_CLOSE 2500 -#define DELAY_CONFIG 2500 -#define DELAY_DLM 1000 -#define DELAY_ULM 1000 -#define DELAY_FCF 1000 -#define DELAY_DOS 1000 -#define DELAY_DSD 1000 -#define DELAY_UNK 1000 -#define DELAY_CATCHUP 10000 - -#define CTC_DIAG_CRC1 0xa4a5752f -#define CTC_DIAG_CRC2 0xd3d20eb3 -#define CTC_DIAG_CRC3 0x0f387ce3 /* Used by SVR 2.0.5 */ - -#define TAPE_DEV 0 /* CTAPE device */ -#define XMF_DEV 1 /* XM Floppy device */ - -#define VTOC_BLOCK 0 - -static uint8 int_slot; /* Interrupting card ID */ -static uint8 int_subdev; /* Interrupting subdevice */ -static t_bool ctc_conf = FALSE; /* Has a CTC card been configured? */ -static uint32 ctc_crc; /* CRC32 of downloaded memory */ - -struct partition vtoc_table[VTOC_PART] = { - { 2, 0, 5272, 8928 }, /* 00 */ - { 3, 1, 126, 5146 }, /* 01 */ - { 4, 0, 14200, 31341 }, /* 02 */ - { 0, 0, 2, 45539 }, /* 03 */ - { 0, 1, 0, 0 }, /* 04 */ - { 0, 1, 0, 0 }, /* 05 */ - { 5, 1, 0, 45541 }, /* 06 */ - { 1, 1, 0, 126 }, /* 07 */ - { 0, 1, 0, 0 }, /* 08 */ - { 0, 1, 0, 0 }, /* 09 */ - { 0, 1, 0, 0 }, /* 10 */ - { 0, 1, 0, 0 }, /* 11 */ - { 0, 1, 0, 0 }, /* 12 */ - { 0, 1, 0, 0 }, /* 13 */ - { 0, 1, 0, 0 }, /* 14 */ - { 0, 1, 0, 0 } /* 15 */ -}; - -/* State. Although we technically have two devices (tape and floppy), - * only the tape drive is supported at this time. */ - -CTC_STATE ctc_state[2]; - -UNIT ctc_unit = { - UDATA (&ctc_svc, UNIT_FIX|UNIT_ATTABLE|UNIT_DISABLE| - UNIT_ROABLE|UNIT_BINK, CTC_CAPACITY) -}; - -MTAB ctc_mod[] = { - { MTAB_XTD|MTAB_VUN, 0, "write enabled", "WRITEENABLED", - &set_writelock, &show_writelock, NULL, "Write enable tape drive" }, - { MTAB_XTD|MTAB_VUN, 1, NULL, "LOCKED", - &set_writelock, NULL, NULL, "Write lock tape drive" }, - { 0 } -}; - -static DEBTAB ctc_debug[] = { - { "IO", IO_DBG, "I/O" }, - { "TRACE", TRACE_DBG, "Call Trace" }, - { NULL } -}; - -DEVICE ctc_dev = { - "CTC", /* name */ - &ctc_unit, /* units */ - NULL, /* registers */ - ctc_mod, /* modifiers */ - 1, /* #units */ - 16, /* address radix */ - 32, /* address width */ - 1, /* address incr. */ - 16, /* data radix */ - 8, /* data width */ - NULL, /* examine routine */ - NULL, /* deposit routine */ - &ctc_reset, /* reset routine */ - NULL, /* boot routine */ - &ctc_attach, /* attach routine */ - &ctc_detach, /* detach routine */ - NULL, /* context */ - DEV_DISABLE|DEV_DIS|DEV_DEBUG|DEV_SECTORS, /* flags */ - 0, /* debug control flags */ - ctc_debug, /* debug flag names */ - NULL, /* memory size change */ - NULL, /* logical name */ - NULL, /* help routine */ - NULL, /* attach help routine */ - NULL, /* help context */ - NULL, /* device description */ -}; - -static void cio_irq(uint8 slot, uint8 dev, int32 delay) -{ - int_slot = slot; - int_subdev = dev & 0x3f; - sim_activate_after(&ctc_unit, delay); -} - -/* - * Write a VTOC and pdinfo to the tape file - */ -static t_stat ctc_write_vtoc(struct vtoc *vtoc, struct pdinfo *pdinfo, uint32 maxpass) -{ - uint8 buf[PD_BYTES]; - uint32 wr, offset; - - memcpy(buf, vtoc, sizeof(struct vtoc)); - offset = sizeof(struct vtoc); - memcpy(buf + offset, pdinfo, sizeof(struct pdinfo)); - offset += sizeof(struct pdinfo); - memcpy(buf + offset, &maxpass, sizeof(uint32)); - - return sim_disk_wrsect(&ctc_unit, VTOC_BLOCK, buf, &wr, 1); -} - -/* - * Load a VTOC and pdinfo from the tape file - */ -static t_stat ctc_read_vtoc(struct vtoc *vtoc, struct pdinfo *pdinfo, uint32 *maxpass) -{ - uint8 buf[PD_BYTES]; - uint32 wr, offset; - t_stat result; - - result = sim_disk_rdsect(&ctc_unit, VTOC_BLOCK, buf, &wr, 1); - - if (result != SCPE_OK) { - return result; - } - - memcpy(vtoc, buf, sizeof(struct vtoc)); - offset = sizeof(struct vtoc); - memcpy(pdinfo, buf + offset, sizeof(struct pdinfo)); - offset += sizeof(struct pdinfo); - memcpy(maxpass, buf + offset, sizeof(uint32)); - - return result; -} - -/* - * Update the host's in-memory copy of the VTOC and pdinfo - */ -static void ctc_update_vtoc(uint32 maxpass, - uint32 vtoc_addr, uint32 pdinfo_addr, - struct vtoc *vtoc, struct pdinfo *pdinfo) -{ - uint32 i; - - pwrite_w(vtoc_addr + 12, VTOC_VALID, BUS_PER); - pwrite_w(vtoc_addr + 16, vtoc->version, BUS_PER); - for (i = 0; i < 8; i++) { - pwrite_b(vtoc_addr + 20 + i, (uint8)(vtoc->volume[i]), BUS_PER); - } - pwrite_h(vtoc_addr + 28, vtoc->sectorsz, BUS_PER); - pwrite_h(vtoc_addr + 30, vtoc->nparts, BUS_PER); - - for (i = 0; i < VTOC_PART; i++) { - pwrite_h(vtoc_addr + 72 + (i * 12) + 0, vtoc_table[i].id, BUS_PER); - pwrite_h(vtoc_addr + 72 + (i * 12) + 2, vtoc_table[i].flag, BUS_PER); - pwrite_w(vtoc_addr + 72 + (i * 12) + 4, vtoc_table[i].sstart, BUS_PER); - pwrite_w(vtoc_addr + 72 + (i * 12) + 8, vtoc_table[i].ssize, BUS_PER); - } - - /* Write the pdinfo */ - pwrite_w(pdinfo_addr, pdinfo->driveid, BUS_PER); - pwrite_w(pdinfo_addr + 4, pdinfo->sanity, BUS_PER); - pwrite_w(pdinfo_addr + 8, pdinfo->version, BUS_PER); - for (i = 0; i < 12; i++) { - pwrite_b(pdinfo_addr + 12 + i, pdinfo->serial[i], BUS_PER); - } - pwrite_w(pdinfo_addr + 24, pdinfo->cyls, BUS_PER); - pwrite_w(pdinfo_addr + 28, pdinfo->tracks, BUS_PER); - pwrite_w(pdinfo_addr + 32, pdinfo->sectors, BUS_PER); - pwrite_w(pdinfo_addr + 36, pdinfo->bytes, BUS_PER); - pwrite_w(pdinfo_addr + 40, pdinfo->logicalst, BUS_PER); - pwrite_w(pdinfo_addr + 44, pdinfo->errlogst, BUS_PER); - pwrite_w(pdinfo_addr + 48, pdinfo->errlogsz, BUS_PER); - pwrite_w(pdinfo_addr + 52, pdinfo->mfgst, BUS_PER); - pwrite_w(pdinfo_addr + 56, pdinfo->mfgsz, BUS_PER); - pwrite_w(pdinfo_addr + 60, pdinfo->defectst, BUS_PER); - pwrite_w(pdinfo_addr + 64, pdinfo->defectsz, BUS_PER); - pwrite_w(pdinfo_addr + 68, pdinfo->relno, BUS_PER); - pwrite_w(pdinfo_addr + 72, pdinfo->relst, BUS_PER); - pwrite_w(pdinfo_addr + 76, pdinfo->relsz, BUS_PER); - pwrite_w(pdinfo_addr + 80, pdinfo->relnext, BUS_PER); - - /* Now something horrible happens. We sneak RIGHT off the end of - * the pdinfo struct and reach deep into the pdsector struct that - * it is part of. */ - - pwrite_w(pdinfo_addr + 128, maxpass, BUS_PER); -} - -/* - * Handle a single request taken from the Request Queue. - * - * Note that the driver stuffs parameters into various different - * fields of the Request Queue entry seemingly at random, and also - * expects response parameters to be placed in specific fields of the - * Completion Queue entry. It can be confusing to follow. - */ -static void ctc_cmd(uint8 slot, - cio_entry *rqe, uint8 *rapp_data, - cio_entry *cqe, uint8 *capp_data) -{ - uint32 vtoc_addr, pdinfo_addr, ctjob_addr; - uint32 maxpass, blkno, delay, last_byte; - uint8 dev, c; - uint8 sec_buf[VTOC_SECSZ]; - int32 b, i, j; - int32 block_count, read_bytes, remainder, dest; - t_seccnt secrw = 0; - struct vtoc vtoc = {{0}}; - struct pdinfo pdinfo = {0}; - t_stat result; - - uint32 lba; /* Logical Block Address */ - - maxpass = 0; - dev = rqe->subdevice & 1; /* Tape or Floppy device */ - - capp_data[7] = rqe->opcode; - cqe->subdevice = rqe->subdevice; - - switch(rqe->opcode) { - case CIO_DLM: - for (i = 0; i < rqe->byte_count; i++) { - ctc_crc = cio_crc32_shift(ctc_crc, pread_b(rqe->address + i, BUS_PER)); - } - sim_debug(TRACE_DBG, &ctc_dev, - "[ctc_cmd] CIO Download Memory: bytecnt=%04x " - "addr=%08x return_addr=%08x subdev=%02x (CRC=%08x)\n", - rqe->byte_count, rqe->address, - rqe->address, rqe->subdevice, ctc_crc); - delay = DELAY_DLM; - cqe->address = rqe->address + rqe->byte_count; - cqe->opcode = CTC_SUCCESS; - break; - case CIO_ULM: - sim_debug(TRACE_DBG, &ctc_dev, - "[ctc_cmd] CIO Upload Memory: return opcode 0\n"); - delay = DELAY_ULM; - cqe->opcode = CTC_SUCCESS; - break; - case CIO_FCF: - sim_debug(TRACE_DBG, &ctc_dev, - "[ctc_cmd] CIO Force Function Call (CRC=%08x)\n", ctc_crc); - delay = DELAY_FCF; - - /* If the currently running program is a diagnostic program, - * we are expected to write results into memory at address - * 0x200f000 */ - if (ctc_crc == CTC_DIAG_CRC1 || - ctc_crc == CTC_DIAG_CRC2 || - ctc_crc == CTC_DIAG_CRC3) { - pwrite_h(0x200f000, 0x1, BUS_PER); /* Test success */ - pwrite_h(0x200f002, 0x0, BUS_PER); /* Test Number */ - pwrite_h(0x200f004, 0x0, BUS_PER); /* Actual */ - pwrite_h(0x200f006, 0x0, BUS_PER); /* Expected */ - pwrite_b(0x200f008, 0x1, BUS_PER); /* Success flag again */ - } - - /* An interesting (?) side-effect of FORCE FUNCTION CALL is - * that it resets the card state such that a new SYSGEN is - * required in order for new commands to work. In fact, an - * INT0/INT1 combo _without_ a RESET can sysgen the board. So, - * we reset the command bits here. */ - cio[slot].sysgen_s = 0; - cqe->opcode = CTC_SUCCESS; - break; - case CIO_DOS: - sim_debug(TRACE_DBG, &ctc_dev, - "[ctc_cmd] CIO_DOS (%d)\n", - rqe->opcode); - delay = DELAY_DOS; - cqe->opcode = CTC_SUCCESS; - break; - case CIO_DSD: - sim_debug(TRACE_DBG, &ctc_dev, - "[ctc_cmd] CTC_DSD (%d)\n", - rqe->opcode); - delay = DELAY_DSD; - /* Write subdevice information to the host. */ - pwrite_h(rqe->address, CTC_NUM_SD, BUS_PER); - pwrite_h(rqe->address + 2, CTC_SD_FT25, BUS_PER); - pwrite_h(rqe->address + 4, CTC_SD_FD5, BUS_PER); - cqe->opcode = CTC_SUCCESS; - break; - case CTC_FORMAT: - sim_debug(TRACE_DBG, &ctc_dev, - "[ctc_cmd] CTC_FORMAT (%d)\n", - rqe->opcode); - - delay = DELAY_FMT; - - /* FORMAT stores the job pointer in the jio_start field of the - * completion queue entry's application data */ - capp_data[0] = rapp_data[4]; - capp_data[1] = rapp_data[5]; - capp_data[2] = rapp_data[6]; - capp_data[3] = rapp_data[7]; - - if (dev == XMF_DEV) { - cqe->opcode = CTC_NOTREADY; - break; - } - - if ((ctc_unit.flags & UNIT_ATT) == 0) { - cqe->opcode = CTC_NOMEDIA; - break; - } - - if (ctc_unit.flags & UNIT_WPRT) { - cqe->opcode = CTC_RDONLY; - break; - } - - /* Write a valid VTOC and pdinfo to the tape */ - - vtoc.sanity = VTOC_VALID; - vtoc.version = 1; - strcpy((char *)vtoc.volume, "ctctape"); - vtoc.sectorsz = PD_BYTES; - vtoc.nparts = VTOC_PART; - - pdinfo.driveid = PD_DRIVEID; - pdinfo.sanity = PD_VALID; - pdinfo.version = 0; - memset(pdinfo.serial, 0, 12); - pdinfo.cyls = PD_CYLS; - pdinfo.tracks = PD_TRACKS; - pdinfo.sectors = PD_SECTORS; - pdinfo.bytes = PD_BYTES; - pdinfo.logicalst = PD_LOGICALST; - pdinfo.errlogst = 0xffffffff; - pdinfo.errlogsz = 0xffffffff; - pdinfo.mfgst = 0xffffffff; - pdinfo.mfgsz = 0xffffffff; - pdinfo.defectst = 0xffffffff; - pdinfo.defectsz = 0xffffffff; - pdinfo.relno = 0xffffffff; - pdinfo.relst = 0xffffffff; - pdinfo.relsz = 0xffffffff; - pdinfo.relnext = 0xffffffff; - - maxpass = rqe->address; - - ctc_write_vtoc(&vtoc, &pdinfo, maxpass); - - cqe->opcode = CTC_SUCCESS; - - /* The address field holds the total amount of time (in 25ms - * chunks) used during this format session. We'll fudge and - * say 1 minute for formatting. */ - cqe->address = 2400; - - break; - case CTC_OPEN: - sim_debug(TRACE_DBG, &ctc_dev, - "[ctc_cmd] CTC_OPEN (%d)\n", - rqe->opcode); - - delay = DELAY_OPEN; - - ctc_state[dev].time = 0; /* Opening always resets session time to 0 */ - ctc_state[dev].bytnum = 0; - - vtoc_addr = rqe->address; - pdinfo_addr = ATOW(rapp_data, 4); - ctjob_addr = ATOW(rapp_data, 8); - - /* For OPEN commands, the Completion Queue Entry's address - * field contains a pointer to the ctjobstat. */ - cqe->address = ctjob_addr; - - if (dev == XMF_DEV) { - cqe->opcode = CTC_NOTREADY; - break; - } - - if ((ctc_unit.flags & UNIT_ATT) == 0) { - cqe->opcode = CTC_NOMEDIA; - break; - } - - /* Load the vtoc, pdinfo, and maxpass from the tape */ - ctc_read_vtoc(&vtoc, &pdinfo, &maxpass); - - ctc_update_vtoc(maxpass, vtoc_addr, pdinfo_addr, &vtoc, &pdinfo); - cqe->opcode = CTC_SUCCESS; - break; - case CTC_CLOSE: - sim_debug(TRACE_DBG, &ctc_dev, - "[ctc_cmd] CTC_CLOSE (%d)\n", - rqe->opcode); - - delay = DELAY_CLOSE; - - /* The Request Queue Entry's address field contains the - * ctjobstat pointer, which the driver will want to find in - * the first word of our Completion Queue Entry's application - * data. This must be in place whether we have media attached - * or not. */ - capp_data[3] = rqe->address & 0xff; - capp_data[2] = (rqe->address & 0xff00) >> 8; - capp_data[1] = (rqe->address & 0xff0000) >> 16; - capp_data[0] = (rqe->address & 0xff000000) >> 24; - - /* The Completion Queue Entry's address field holds the total - * tape time used in this session. */ - cqe->address = ctc_state[dev].time; - cqe->opcode = CTC_SUCCESS; - - break; - case CTC_WRITE: - case CTC_VWRITE: - sim_debug(TRACE_DBG, &ctc_dev, - "[ctc_cmd] CTC_WRITE or CTC_VWRITE (%d)\n", - rqe->opcode); - - delay = DELAY_RW; - - cqe->byte_count = rqe->byte_count; - cqe->subdevice = rqe->subdevice; - cqe->address = ATOW(rapp_data, 4); - - if (dev == XMF_DEV) { - cqe->opcode = CTC_NOTREADY; - break; - } - - if ((ctc_unit.flags & UNIT_ATT) == 0) { - cqe->opcode = CTC_NOMEDIA; - break; - } - - if (ctc_unit.flags & UNIT_WPRT) { - cqe->opcode = CTC_RDONLY; - break; - } - - blkno = ATOW(rapp_data, 0); - - for (b = 0; b < rqe->byte_count / VTOC_SECSZ; b++) { - ctc_state[dev].time += 10; - for (j = 0; j < VTOC_SECSZ; j++) { - /* Fill the buffer */ - sec_buf[j] = pread_b(rqe->address + (b * VTOC_SECSZ) + j, BUS_PER); - } - lba = blkno + b; - result = sim_disk_wrsect(&ctc_unit, lba, sec_buf, &secrw, 1); - if (result == SCPE_OK) { - sim_debug(TRACE_DBG, &ctc_dev, - "[ctc_cmd] ... CTC_WRITE: 512 bytes at block %d (0x%x)\n", - lba, lba); - cqe->opcode = CTC_SUCCESS; - } else { - cqe->opcode = CTC_RWERROR; - break; - } - } - - break; - case CTC_READ: - sim_debug(TRACE_DBG, &ctc_dev, - "[ctc_cmd] CTC_READ (%d)\n", - rqe->opcode); - delay = DELAY_RW; - cqe->byte_count = rqe->byte_count; - cqe->subdevice = rqe->subdevice; - cqe->address = ATOW(rapp_data, 4); - dest = rqe->address; - - if (dev == XMF_DEV) { - cqe->opcode = CTC_NOTREADY; - break; - } - - if ((ctc_unit.flags & UNIT_ATT) == 0) { - cqe->opcode = CTC_NOMEDIA; - break; - } - - /* - * This read routine supports both streaming and block - * oriented modes. - * - * Read requests from the host give a block number, and a - * number of bytes to read. In streaming mode, however, there - * is no requirement that the number of bytes to read has to - * be block-aligned, so we must support reading an arbitrary - * number of bytes from the tape stream and remembering the - * current position in the byte stream. - * - */ - - /* The block number to begin reading from is supplied in the - * request queue entry's APP_DATA field. */ - blkno = ATOW(rapp_data, 0); - - /* Since we may start reading from the data stream at an - * arbitrary location, we compute the offset of the last byte - * to be read, and use that to figure out how many bytes will - * be left over to read from an "extra" block */ - last_byte = ctc_state[dev].bytnum + rqe->byte_count; - remainder = last_byte % VTOC_SECSZ; - - /* The number of blocks we have to read in total is computed - * by looking at the byte count, PLUS any remainder that will - * be left after crossing a block boundary */ - block_count = rqe->byte_count / VTOC_SECSZ; - if (((rqe->byte_count % VTOC_SECSZ) > 0 || remainder > 0)) { - block_count++; - } - - /* Now step over each block, and start reading from the - * necessary location. */ - for (b = 0; b < block_count; b++) { - uint32 start_byte; - /* Add some read time to the read time counter */ - ctc_state[dev].time += 10; - start_byte = ctc_state[dev].bytnum % VTOC_SECSZ; - lba = blkno + b; - result = sim_disk_rdsect(&ctc_unit, lba, sec_buf, &secrw, 1); - if (result == SCPE_OK) { - /* If this is the last "extra" block, we will only - * read the remainder of bytes from it. Otherwise, we - * need to consume the whole block. */ - if (b == (block_count - 1) && remainder > 0) { - read_bytes = remainder; - } else { - read_bytes = VTOC_SECSZ - start_byte; - } - for (j = 0; j < read_bytes; j++) { - uint32 offset; - /* Drain the buffer */ - if (b == 0 && (j + start_byte) < VTOC_SECSZ) { - /* This is a partial read of the first block, - * continuing to read from a previous partial - * block read. */ - offset = j + start_byte; - } else { - offset = j; - } - c = sec_buf[offset]; - pwrite_b(dest++, c, BUS_PER); - ctc_state[dev].bytnum++; - } - } else { - sim_debug(TRACE_DBG, &ctc_dev, - "[ctc_cmd] Error reading sector at address %d. Giving up\n", lba); - break; - } - } - - if (result == SCPE_OK) { - cqe->opcode = CTC_SUCCESS; - } else { - cqe->opcode = CTC_RWERROR; - } - - break; - case CTC_CONFIG: - sim_debug(TRACE_DBG, &ctc_dev, - "[ctc_cmd] CTC_CONFIG (%d)\n", - rqe->opcode); - delay = DELAY_CONFIG; - cqe->opcode = CTC_SUCCESS; - break; - default: - sim_debug(TRACE_DBG, &ctc_dev, - "[ctc_cmd] UNHANDLED OP: %d (0x%02x)\n", - rqe->opcode, rqe->opcode); - delay = DELAY_UNK; - cqe->opcode = CTC_HWERROR; - break; - } - - cio_irq(slot, rqe->subdevice, delay); -} - -void ctc_sysgen(uint8 slot) -{ - cio_entry cqe = {0}; - uint8 rapp_data[12] = {0}; - - ctc_crc = 0; - - sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] Handling Sysgen.\n"); - sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] rqp=%08x\n", cio[slot].rqp); - sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] cqp=%08x\n", cio[slot].cqp); - sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] rqs=%d\n", cio[slot].rqs); - sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] cqs=%d\n", cio[slot].cqs); - sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] ivec=%d\n", cio[slot].ivec); - sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] no_rque=%d\n", cio[slot].no_rque); - - cqe.opcode = 3; /* Sysgen success! */ - - cio_cexpress(slot, CTQCESIZE, &cqe, rapp_data); - cio_cqueue(slot, CIO_STAT, CTQCESIZE, &cqe, rapp_data); - - int_slot = slot; - sim_activate_after(&ctc_unit, DELAY_SYSGEN); -} - -void ctc_express(uint8 slot) -{ - cio_entry rqe, cqe; - uint8 rapp_data[12] = {0}; - uint8 capp_data[8] = {0}; - - sim_debug(TRACE_DBG, &ctc_dev, "[ctc_express] Handling Express Request\n"); - - cio_rexpress(slot, CTQRESIZE, &rqe, rapp_data); - ctc_cmd(slot, &rqe, rapp_data, &cqe, capp_data); - - cio_cexpress(slot, CTQCESIZE, &cqe, capp_data); -} - -void ctc_full(uint8 slot) -{ - cio_entry rqe, cqe; - uint8 rapp_data[12] = {0}; - uint8 capp_data[8] = {0}; - - sim_debug(TRACE_DBG, &ctc_dev, "[ctc_full] Handling Full Request\n"); - - while (cio_cqueue_avail(slot, CTQCESIZE) && - cio_rqueue(slot, TAPE_DEV, CTQRESIZE, &rqe, rapp_data) == SCPE_OK) { - ctc_cmd(slot, &rqe, rapp_data, &cqe, capp_data); - } - cio_cqueue(slot, CIO_STAT, CTQCESIZE, &cqe, capp_data); -} - -t_stat ctc_reset(DEVICE *dptr) -{ - uint8 slot; - t_stat r; - - ctc_crc = 0; - - memset(ctc_state, 0, 2 * sizeof(CTC_STATE)); - - if (dptr->flags & DEV_DIS) { - cio_remove_all(CTC_ID); - ctc_conf = FALSE; - return SCPE_OK; - } - - if (!ctc_conf) { - r = cio_install(CTC_ID, "CTC", CTC_IPL, - &ctc_express, &ctc_full, &ctc_sysgen, NULL, - &slot); - if (r != SCPE_OK) { - return r; - } - ctc_conf = TRUE; - } - - return SCPE_OK; -} - -t_stat ctc_svc(UNIT *uptr) -{ - uint16 lp, ulp; - - if (cio[int_slot].ivec > 0) { - sim_debug(TRACE_DBG, &ctc_dev, - "[cio_svc] IRQ for board %d (VEC=%d)\n", - int_slot, cio[int_slot].ivec); - CIO_SET_INT(int_slot); - } - - /* Check to see if the completion queue has more work in it. We - * need to schedule an interrupt for each job if we've fallen - * behind (this should be rare) */ - lp = cio_c_lp(int_slot, CTQCESIZE); - ulp = cio_c_ulp(int_slot, CTQCESIZE); - - if ((ulp + CTQCESIZE) % (CTQCESIZE * cio[int_slot].cqs) != lp) { - sim_debug(TRACE_DBG, &ctc_dev, - "[cio_svc] Completion queue has fallen behind (lp=%04x ulp=%04x)\n", - lp, ulp); - /* Schedule a catch-up interrupt */ - sim_activate_abs(&ctc_unit, DELAY_CATCHUP); - } - - return SCPE_OK; -} - -t_stat ctc_attach(UNIT *uptr, CONST char *cptr) -{ - return sim_disk_attach(uptr, cptr, VTOC_SECSZ, 1, TRUE, 0, "CIPHER23", 0, 0); -} - -t_stat ctc_detach(UNIT *uptr) -{ - return sim_disk_detach(uptr); -} +/* 3b2_ctc.c: CM195H 23MB Cartridge Tape Controller CIO Card + + Copyright (c) 2018-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#include "3b2_ctc.h" + +#include "sim_disk.h" + +#include "3b2_io.h" +#include "3b2_mem.h" + +#define CTQRESIZE 20 +#define CTQCESIZE 16 + +#define DELAY_SYSGEN 2500 +#define DELAY_FMT 1000000 +#define DELAY_RW 10000 +#define DELAY_OPEN 2500 +#define DELAY_CLOSE 2500 +#define DELAY_CONFIG 2500 +#define DELAY_DLM 1000 +#define DELAY_ULM 1000 +#define DELAY_FCF 1000 +#define DELAY_DOS 1000 +#define DELAY_DSD 1000 +#define DELAY_UNK 1000 +#define DELAY_CATCHUP 10000 + +#define CTC_DIAG_CRC1 0xa4a5752f +#define CTC_DIAG_CRC2 0xd3d20eb3 +#define CTC_DIAG_CRC3 0x0f387ce3 /* Used by SVR 2.0.5 */ + +#define TAPE_DEV 0 /* CTAPE device */ +#define XMF_DEV 1 /* XM Floppy device */ + +#define VTOC_BLOCK 0 + +static uint8 int_slot; /* Interrupting card ID */ +static uint8 int_subdev; /* Interrupting subdevice */ +static t_bool ctc_conf = FALSE; /* Has a CTC card been configured? */ +static uint32 ctc_crc; /* CRC32 of downloaded memory */ + +struct partition vtoc_table[VTOC_PART] = { + { 2, 0, 5272, 8928 }, /* 00 */ + { 3, 1, 126, 5146 }, /* 01 */ + { 4, 0, 14200, 31341 }, /* 02 */ + { 0, 0, 2, 45539 }, /* 03 */ + { 0, 1, 0, 0 }, /* 04 */ + { 0, 1, 0, 0 }, /* 05 */ + { 5, 1, 0, 45541 }, /* 06 */ + { 1, 1, 0, 126 }, /* 07 */ + { 0, 1, 0, 0 }, /* 08 */ + { 0, 1, 0, 0 }, /* 09 */ + { 0, 1, 0, 0 }, /* 10 */ + { 0, 1, 0, 0 }, /* 11 */ + { 0, 1, 0, 0 }, /* 12 */ + { 0, 1, 0, 0 }, /* 13 */ + { 0, 1, 0, 0 }, /* 14 */ + { 0, 1, 0, 0 } /* 15 */ +}; + +/* State. Although we technically have two devices (tape and floppy), + * only the tape drive is supported at this time. */ + +CTC_STATE ctc_state[2]; + +UNIT ctc_unit = { + UDATA (&ctc_svc, UNIT_FIX|UNIT_ATTABLE|UNIT_DISABLE| + UNIT_ROABLE|UNIT_BINK, CTC_CAPACITY) +}; + +MTAB ctc_mod[] = { + { MTAB_XTD|MTAB_VUN, 0, "write enabled", "WRITEENABLED", + &set_writelock, &show_writelock, NULL, "Write enable tape drive" }, + { MTAB_XTD|MTAB_VUN, 1, NULL, "LOCKED", + &set_writelock, NULL, NULL, "Write lock tape drive" }, + { 0 } +}; + +static DEBTAB ctc_debug[] = { + { "IO", IO_DBG, "I/O" }, + { "TRACE", TRACE_DBG, "Call Trace" }, + { NULL } +}; + +DEVICE ctc_dev = { + "CTC", /* name */ + &ctc_unit, /* units */ + NULL, /* registers */ + ctc_mod, /* modifiers */ + 1, /* #units */ + 16, /* address radix */ + 32, /* address width */ + 1, /* address incr. */ + 16, /* data radix */ + 8, /* data width */ + NULL, /* examine routine */ + NULL, /* deposit routine */ + &ctc_reset, /* reset routine */ + NULL, /* boot routine */ + &ctc_attach, /* attach routine */ + &ctc_detach, /* detach routine */ + NULL, /* context */ + DEV_DISABLE|DEV_DIS|DEV_DEBUG|DEV_SECTORS, /* flags */ + 0, /* debug control flags */ + ctc_debug, /* debug flag names */ + NULL, /* memory size change */ + NULL, /* logical name */ + NULL, /* help routine */ + NULL, /* attach help routine */ + NULL, /* help context */ + NULL, /* device description */ +}; + +static void cio_irq(uint8 slot, uint8 dev, int32 delay) +{ + int_slot = slot; + int_subdev = dev & 0x3f; + sim_activate_after(&ctc_unit, delay); +} + +/* + * Write a VTOC and pdinfo to the tape file + */ +static t_stat ctc_write_vtoc(struct vtoc *vtoc, struct pdinfo *pdinfo, uint32 maxpass) +{ + uint8 buf[PD_BYTES]; + uint32 wr, offset; + + memcpy(buf, vtoc, sizeof(struct vtoc)); + offset = sizeof(struct vtoc); + memcpy(buf + offset, pdinfo, sizeof(struct pdinfo)); + offset += sizeof(struct pdinfo); + memcpy(buf + offset, &maxpass, sizeof(uint32)); + + return sim_disk_wrsect(&ctc_unit, VTOC_BLOCK, buf, &wr, 1); +} + +/* + * Load a VTOC and pdinfo from the tape file + */ +static t_stat ctc_read_vtoc(struct vtoc *vtoc, struct pdinfo *pdinfo, uint32 *maxpass) +{ + uint8 buf[PD_BYTES]; + uint32 wr, offset; + t_stat result; + + result = sim_disk_rdsect(&ctc_unit, VTOC_BLOCK, buf, &wr, 1); + + if (result != SCPE_OK) { + return result; + } + + memcpy(vtoc, buf, sizeof(struct vtoc)); + offset = sizeof(struct vtoc); + memcpy(pdinfo, buf + offset, sizeof(struct pdinfo)); + offset += sizeof(struct pdinfo); + memcpy(maxpass, buf + offset, sizeof(uint32)); + + return result; +} + +/* + * Update the host's in-memory copy of the VTOC and pdinfo + */ +static void ctc_update_vtoc(uint32 maxpass, + uint32 vtoc_addr, uint32 pdinfo_addr, + struct vtoc *vtoc, struct pdinfo *pdinfo) +{ + uint32 i; + + pwrite_w(vtoc_addr + 12, VTOC_VALID, BUS_PER); + pwrite_w(vtoc_addr + 16, vtoc->version, BUS_PER); + for (i = 0; i < 8; i++) { + pwrite_b(vtoc_addr + 20 + i, (uint8)(vtoc->volume[i]), BUS_PER); + } + pwrite_h(vtoc_addr + 28, vtoc->sectorsz, BUS_PER); + pwrite_h(vtoc_addr + 30, vtoc->nparts, BUS_PER); + + for (i = 0; i < VTOC_PART; i++) { + pwrite_h(vtoc_addr + 72 + (i * 12) + 0, vtoc_table[i].id, BUS_PER); + pwrite_h(vtoc_addr + 72 + (i * 12) + 2, vtoc_table[i].flag, BUS_PER); + pwrite_w(vtoc_addr + 72 + (i * 12) + 4, vtoc_table[i].sstart, BUS_PER); + pwrite_w(vtoc_addr + 72 + (i * 12) + 8, vtoc_table[i].ssize, BUS_PER); + } + + /* Write the pdinfo */ + pwrite_w(pdinfo_addr, pdinfo->driveid, BUS_PER); + pwrite_w(pdinfo_addr + 4, pdinfo->sanity, BUS_PER); + pwrite_w(pdinfo_addr + 8, pdinfo->version, BUS_PER); + for (i = 0; i < 12; i++) { + pwrite_b(pdinfo_addr + 12 + i, pdinfo->serial[i], BUS_PER); + } + pwrite_w(pdinfo_addr + 24, pdinfo->cyls, BUS_PER); + pwrite_w(pdinfo_addr + 28, pdinfo->tracks, BUS_PER); + pwrite_w(pdinfo_addr + 32, pdinfo->sectors, BUS_PER); + pwrite_w(pdinfo_addr + 36, pdinfo->bytes, BUS_PER); + pwrite_w(pdinfo_addr + 40, pdinfo->logicalst, BUS_PER); + pwrite_w(pdinfo_addr + 44, pdinfo->errlogst, BUS_PER); + pwrite_w(pdinfo_addr + 48, pdinfo->errlogsz, BUS_PER); + pwrite_w(pdinfo_addr + 52, pdinfo->mfgst, BUS_PER); + pwrite_w(pdinfo_addr + 56, pdinfo->mfgsz, BUS_PER); + pwrite_w(pdinfo_addr + 60, pdinfo->defectst, BUS_PER); + pwrite_w(pdinfo_addr + 64, pdinfo->defectsz, BUS_PER); + pwrite_w(pdinfo_addr + 68, pdinfo->relno, BUS_PER); + pwrite_w(pdinfo_addr + 72, pdinfo->relst, BUS_PER); + pwrite_w(pdinfo_addr + 76, pdinfo->relsz, BUS_PER); + pwrite_w(pdinfo_addr + 80, pdinfo->relnext, BUS_PER); + + /* Now something horrible happens. We sneak RIGHT off the end of + * the pdinfo struct and reach deep into the pdsector struct that + * it is part of. */ + + pwrite_w(pdinfo_addr + 128, maxpass, BUS_PER); +} + +/* + * Handle a single request taken from the Request Queue. + * + * Note that the driver stuffs parameters into various different + * fields of the Request Queue entry seemingly at random, and also + * expects response parameters to be placed in specific fields of the + * Completion Queue entry. It can be confusing to follow. + */ +static void ctc_cmd(uint8 slot, + cio_entry *rqe, uint8 *rapp_data, + cio_entry *cqe, uint8 *capp_data) +{ + uint32 vtoc_addr, pdinfo_addr, ctjob_addr; + uint32 maxpass, blkno, delay, last_byte; + uint8 dev, c; + uint8 sec_buf[VTOC_SECSZ]; + int32 b, i, j; + int32 block_count, read_bytes, remainder, dest; + t_seccnt secrw = 0; + struct vtoc vtoc = {{0}}; + struct pdinfo pdinfo = {0}; + t_stat result; + + uint32 lba; /* Logical Block Address */ + + maxpass = 0; + dev = rqe->subdevice & 1; /* Tape or Floppy device */ + + capp_data[7] = rqe->opcode; + cqe->subdevice = rqe->subdevice; + + switch(rqe->opcode) { + case CIO_DLM: + for (i = 0; i < rqe->byte_count; i++) { + ctc_crc = cio_crc32_shift(ctc_crc, pread_b(rqe->address + i, BUS_PER)); + } + sim_debug(TRACE_DBG, &ctc_dev, + "[ctc_cmd] CIO Download Memory: bytecnt=%04x " + "addr=%08x return_addr=%08x subdev=%02x (CRC=%08x)\n", + rqe->byte_count, rqe->address, + rqe->address, rqe->subdevice, ctc_crc); + delay = DELAY_DLM; + cqe->address = rqe->address + rqe->byte_count; + cqe->opcode = CTC_SUCCESS; + break; + case CIO_ULM: + sim_debug(TRACE_DBG, &ctc_dev, + "[ctc_cmd] CIO Upload Memory: return opcode 0\n"); + delay = DELAY_ULM; + cqe->opcode = CTC_SUCCESS; + break; + case CIO_FCF: + sim_debug(TRACE_DBG, &ctc_dev, + "[ctc_cmd] CIO Force Function Call (CRC=%08x)\n", ctc_crc); + delay = DELAY_FCF; + + /* If the currently running program is a diagnostic program, + * we are expected to write results into memory at address + * 0x200f000 */ + if (ctc_crc == CTC_DIAG_CRC1 || + ctc_crc == CTC_DIAG_CRC2 || + ctc_crc == CTC_DIAG_CRC3) { + pwrite_h(0x200f000, 0x1, BUS_PER); /* Test success */ + pwrite_h(0x200f002, 0x0, BUS_PER); /* Test Number */ + pwrite_h(0x200f004, 0x0, BUS_PER); /* Actual */ + pwrite_h(0x200f006, 0x0, BUS_PER); /* Expected */ + pwrite_b(0x200f008, 0x1, BUS_PER); /* Success flag again */ + } + + /* An interesting (?) side-effect of FORCE FUNCTION CALL is + * that it resets the card state such that a new SYSGEN is + * required in order for new commands to work. In fact, an + * INT0/INT1 combo _without_ a RESET can sysgen the board. So, + * we reset the command bits here. */ + cio[slot].sysgen_s = 0; + cqe->opcode = CTC_SUCCESS; + break; + case CIO_DOS: + sim_debug(TRACE_DBG, &ctc_dev, + "[ctc_cmd] CIO_DOS (%d)\n", + rqe->opcode); + delay = DELAY_DOS; + cqe->opcode = CTC_SUCCESS; + break; + case CIO_DSD: + sim_debug(TRACE_DBG, &ctc_dev, + "[ctc_cmd] CTC_DSD (%d)\n", + rqe->opcode); + delay = DELAY_DSD; + /* Write subdevice information to the host. */ + pwrite_h(rqe->address, CTC_NUM_SD, BUS_PER); + pwrite_h(rqe->address + 2, CTC_SD_FT25, BUS_PER); + pwrite_h(rqe->address + 4, CTC_SD_FD5, BUS_PER); + cqe->opcode = CTC_SUCCESS; + break; + case CTC_FORMAT: + sim_debug(TRACE_DBG, &ctc_dev, + "[ctc_cmd] CTC_FORMAT (%d)\n", + rqe->opcode); + + delay = DELAY_FMT; + + /* FORMAT stores the job pointer in the jio_start field of the + * completion queue entry's application data */ + capp_data[0] = rapp_data[4]; + capp_data[1] = rapp_data[5]; + capp_data[2] = rapp_data[6]; + capp_data[3] = rapp_data[7]; + + if (dev == XMF_DEV) { + cqe->opcode = CTC_NOTREADY; + break; + } + + if ((ctc_unit.flags & UNIT_ATT) == 0) { + cqe->opcode = CTC_NOMEDIA; + break; + } + + if (ctc_unit.flags & UNIT_WPRT) { + cqe->opcode = CTC_RDONLY; + break; + } + + /* Write a valid VTOC and pdinfo to the tape */ + + vtoc.sanity = VTOC_VALID; + vtoc.version = 1; + strcpy((char *)vtoc.volume, "ctctape"); + vtoc.sectorsz = PD_BYTES; + vtoc.nparts = VTOC_PART; + + pdinfo.driveid = PD_DRIVEID; + pdinfo.sanity = PD_VALID; + pdinfo.version = 0; + memset(pdinfo.serial, 0, 12); + pdinfo.cyls = PD_CYLS; + pdinfo.tracks = PD_TRACKS; + pdinfo.sectors = PD_SECTORS; + pdinfo.bytes = PD_BYTES; + pdinfo.logicalst = PD_LOGICALST; + pdinfo.errlogst = 0xffffffff; + pdinfo.errlogsz = 0xffffffff; + pdinfo.mfgst = 0xffffffff; + pdinfo.mfgsz = 0xffffffff; + pdinfo.defectst = 0xffffffff; + pdinfo.defectsz = 0xffffffff; + pdinfo.relno = 0xffffffff; + pdinfo.relst = 0xffffffff; + pdinfo.relsz = 0xffffffff; + pdinfo.relnext = 0xffffffff; + + maxpass = rqe->address; + + ctc_write_vtoc(&vtoc, &pdinfo, maxpass); + + cqe->opcode = CTC_SUCCESS; + + /* The address field holds the total amount of time (in 25ms + * chunks) used during this format session. We'll fudge and + * say 1 minute for formatting. */ + cqe->address = 2400; + + break; + case CTC_OPEN: + sim_debug(TRACE_DBG, &ctc_dev, + "[ctc_cmd] CTC_OPEN (%d)\n", + rqe->opcode); + + delay = DELAY_OPEN; + + ctc_state[dev].time = 0; /* Opening always resets session time to 0 */ + ctc_state[dev].bytnum = 0; + + vtoc_addr = rqe->address; + pdinfo_addr = ATOW(rapp_data, 4); + ctjob_addr = ATOW(rapp_data, 8); + + /* For OPEN commands, the Completion Queue Entry's address + * field contains a pointer to the ctjobstat. */ + cqe->address = ctjob_addr; + + if (dev == XMF_DEV) { + cqe->opcode = CTC_NOTREADY; + break; + } + + if ((ctc_unit.flags & UNIT_ATT) == 0) { + cqe->opcode = CTC_NOMEDIA; + break; + } + + /* Load the vtoc, pdinfo, and maxpass from the tape */ + ctc_read_vtoc(&vtoc, &pdinfo, &maxpass); + + ctc_update_vtoc(maxpass, vtoc_addr, pdinfo_addr, &vtoc, &pdinfo); + cqe->opcode = CTC_SUCCESS; + break; + case CTC_CLOSE: + sim_debug(TRACE_DBG, &ctc_dev, + "[ctc_cmd] CTC_CLOSE (%d)\n", + rqe->opcode); + + delay = DELAY_CLOSE; + + /* The Request Queue Entry's address field contains the + * ctjobstat pointer, which the driver will want to find in + * the first word of our Completion Queue Entry's application + * data. This must be in place whether we have media attached + * or not. */ + capp_data[3] = rqe->address & 0xff; + capp_data[2] = (rqe->address & 0xff00) >> 8; + capp_data[1] = (rqe->address & 0xff0000) >> 16; + capp_data[0] = (rqe->address & 0xff000000) >> 24; + + /* The Completion Queue Entry's address field holds the total + * tape time used in this session. */ + cqe->address = ctc_state[dev].time; + cqe->opcode = CTC_SUCCESS; + + break; + case CTC_WRITE: + case CTC_VWRITE: + sim_debug(TRACE_DBG, &ctc_dev, + "[ctc_cmd] CTC_WRITE or CTC_VWRITE (%d)\n", + rqe->opcode); + + delay = DELAY_RW; + + cqe->byte_count = rqe->byte_count; + cqe->subdevice = rqe->subdevice; + cqe->address = ATOW(rapp_data, 4); + + if (dev == XMF_DEV) { + cqe->opcode = CTC_NOTREADY; + break; + } + + if ((ctc_unit.flags & UNIT_ATT) == 0) { + cqe->opcode = CTC_NOMEDIA; + break; + } + + if (ctc_unit.flags & UNIT_WPRT) { + cqe->opcode = CTC_RDONLY; + break; + } + + blkno = ATOW(rapp_data, 0); + + for (b = 0; b < rqe->byte_count / VTOC_SECSZ; b++) { + ctc_state[dev].time += 10; + for (j = 0; j < VTOC_SECSZ; j++) { + /* Fill the buffer */ + sec_buf[j] = pread_b(rqe->address + (b * VTOC_SECSZ) + j, BUS_PER); + } + lba = blkno + b; + result = sim_disk_wrsect(&ctc_unit, lba, sec_buf, &secrw, 1); + if (result == SCPE_OK) { + sim_debug(TRACE_DBG, &ctc_dev, + "[ctc_cmd] ... CTC_WRITE: 512 bytes at block %d (0x%x)\n", + lba, lba); + cqe->opcode = CTC_SUCCESS; + } else { + cqe->opcode = CTC_RWERROR; + break; + } + } + + break; + case CTC_READ: + sim_debug(TRACE_DBG, &ctc_dev, + "[ctc_cmd] CTC_READ (%d)\n", + rqe->opcode); + delay = DELAY_RW; + cqe->byte_count = rqe->byte_count; + cqe->subdevice = rqe->subdevice; + cqe->address = ATOW(rapp_data, 4); + dest = rqe->address; + + if (dev == XMF_DEV) { + cqe->opcode = CTC_NOTREADY; + break; + } + + if ((ctc_unit.flags & UNIT_ATT) == 0) { + cqe->opcode = CTC_NOMEDIA; + break; + } + + /* + * This read routine supports both streaming and block + * oriented modes. + * + * Read requests from the host give a block number, and a + * number of bytes to read. In streaming mode, however, there + * is no requirement that the number of bytes to read has to + * be block-aligned, so we must support reading an arbitrary + * number of bytes from the tape stream and remembering the + * current position in the byte stream. + * + */ + + /* The block number to begin reading from is supplied in the + * request queue entry's APP_DATA field. */ + blkno = ATOW(rapp_data, 0); + + /* Since we may start reading from the data stream at an + * arbitrary location, we compute the offset of the last byte + * to be read, and use that to figure out how many bytes will + * be left over to read from an "extra" block */ + last_byte = ctc_state[dev].bytnum + rqe->byte_count; + remainder = last_byte % VTOC_SECSZ; + + /* The number of blocks we have to read in total is computed + * by looking at the byte count, PLUS any remainder that will + * be left after crossing a block boundary */ + block_count = rqe->byte_count / VTOC_SECSZ; + if (((rqe->byte_count % VTOC_SECSZ) > 0 || remainder > 0)) { + block_count++; + } + + /* Now step over each block, and start reading from the + * necessary location. */ + for (b = 0; b < block_count; b++) { + uint32 start_byte; + /* Add some read time to the read time counter */ + ctc_state[dev].time += 10; + start_byte = ctc_state[dev].bytnum % VTOC_SECSZ; + lba = blkno + b; + result = sim_disk_rdsect(&ctc_unit, lba, sec_buf, &secrw, 1); + if (result == SCPE_OK) { + /* If this is the last "extra" block, we will only + * read the remainder of bytes from it. Otherwise, we + * need to consume the whole block. */ + if (b == (block_count - 1) && remainder > 0) { + read_bytes = remainder; + } else { + read_bytes = VTOC_SECSZ - start_byte; + } + for (j = 0; j < read_bytes; j++) { + uint32 offset; + /* Drain the buffer */ + if (b == 0 && (j + start_byte) < VTOC_SECSZ) { + /* This is a partial read of the first block, + * continuing to read from a previous partial + * block read. */ + offset = j + start_byte; + } else { + offset = j; + } + c = sec_buf[offset]; + pwrite_b(dest++, c, BUS_PER); + ctc_state[dev].bytnum++; + } + } else { + sim_debug(TRACE_DBG, &ctc_dev, + "[ctc_cmd] Error reading sector at address %d. Giving up\n", lba); + break; + } + } + + if (result == SCPE_OK) { + cqe->opcode = CTC_SUCCESS; + } else { + cqe->opcode = CTC_RWERROR; + } + + break; + case CTC_CONFIG: + sim_debug(TRACE_DBG, &ctc_dev, + "[ctc_cmd] CTC_CONFIG (%d)\n", + rqe->opcode); + delay = DELAY_CONFIG; + cqe->opcode = CTC_SUCCESS; + break; + default: + sim_debug(TRACE_DBG, &ctc_dev, + "[ctc_cmd] UNHANDLED OP: %d (0x%02x)\n", + rqe->opcode, rqe->opcode); + delay = DELAY_UNK; + cqe->opcode = CTC_HWERROR; + break; + } + + cio_irq(slot, rqe->subdevice, delay); +} + +void ctc_sysgen(uint8 slot) +{ + cio_entry cqe = {0}; + uint8 rapp_data[12] = {0}; + + ctc_crc = 0; + + sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] Handling Sysgen.\n"); + sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] rqp=%08x\n", cio[slot].rqp); + sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] cqp=%08x\n", cio[slot].cqp); + sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] rqs=%d\n", cio[slot].rqs); + sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] cqs=%d\n", cio[slot].cqs); + sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] ivec=%d\n", cio[slot].ivec); + sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] no_rque=%d\n", cio[slot].no_rque); + + cqe.opcode = 3; /* Sysgen success! */ + + cio_cexpress(slot, CTQCESIZE, &cqe, rapp_data); + cio_cqueue(slot, CIO_STAT, CTQCESIZE, &cqe, rapp_data); + + int_slot = slot; + sim_activate_after(&ctc_unit, DELAY_SYSGEN); +} + +void ctc_express(uint8 slot) +{ + cio_entry rqe, cqe; + uint8 rapp_data[12] = {0}; + uint8 capp_data[8] = {0}; + + sim_debug(TRACE_DBG, &ctc_dev, "[ctc_express] Handling Express Request\n"); + + cio_rexpress(slot, CTQRESIZE, &rqe, rapp_data); + ctc_cmd(slot, &rqe, rapp_data, &cqe, capp_data); + + cio_cexpress(slot, CTQCESIZE, &cqe, capp_data); +} + +void ctc_full(uint8 slot) +{ + cio_entry rqe, cqe; + uint8 rapp_data[12] = {0}; + uint8 capp_data[8] = {0}; + + sim_debug(TRACE_DBG, &ctc_dev, "[ctc_full] Handling Full Request\n"); + + while (cio_cqueue_avail(slot, CTQCESIZE) && + cio_rqueue(slot, TAPE_DEV, CTQRESIZE, &rqe, rapp_data) == SCPE_OK) { + ctc_cmd(slot, &rqe, rapp_data, &cqe, capp_data); + } + cio_cqueue(slot, CIO_STAT, CTQCESIZE, &cqe, capp_data); +} + +t_stat ctc_reset(DEVICE *dptr) +{ + uint8 slot; + t_stat r; + + ctc_crc = 0; + + memset(ctc_state, 0, 2 * sizeof(CTC_STATE)); + + if (dptr->flags & DEV_DIS) { + cio_remove_all(CTC_ID); + ctc_conf = FALSE; + return SCPE_OK; + } + + if (!ctc_conf) { + r = cio_install(CTC_ID, "CTC", CTC_IPL, + &ctc_express, &ctc_full, &ctc_sysgen, NULL, + &slot); + if (r != SCPE_OK) { + return r; + } + ctc_conf = TRUE; + } + + return SCPE_OK; +} + +t_stat ctc_svc(UNIT *uptr) +{ + uint16 lp, ulp; + + if (cio[int_slot].ivec > 0) { + sim_debug(TRACE_DBG, &ctc_dev, + "[cio_svc] IRQ for board %d (VEC=%d)\n", + int_slot, cio[int_slot].ivec); + CIO_SET_INT(int_slot); + } + + /* Check to see if the completion queue has more work in it. We + * need to schedule an interrupt for each job if we've fallen + * behind (this should be rare) */ + lp = cio_c_lp(int_slot, CTQCESIZE); + ulp = cio_c_ulp(int_slot, CTQCESIZE); + + if ((ulp + CTQCESIZE) % (CTQCESIZE * cio[int_slot].cqs) != lp) { + sim_debug(TRACE_DBG, &ctc_dev, + "[cio_svc] Completion queue has fallen behind (lp=%04x ulp=%04x)\n", + lp, ulp); + /* Schedule a catch-up interrupt */ + sim_activate_abs(&ctc_unit, DELAY_CATCHUP); + } + + return SCPE_OK; +} + +t_stat ctc_attach(UNIT *uptr, CONST char *cptr) +{ + return sim_disk_attach(uptr, cptr, VTOC_SECSZ, 1, TRUE, 0, "CIPHER23", 0, 0); +} + +t_stat ctc_detach(UNIT *uptr) +{ + return sim_disk_detach(uptr); +} diff --git a/3B2/3b2_ctc.h b/3B2/3b2_ctc.h index 8ba64510..0d3cb700 100644 --- a/3B2/3b2_ctc.h +++ b/3B2/3b2_ctc.h @@ -1,153 +1,153 @@ -/* 3b2_ctc.h: CM195H 23MB Cartridge Tape Controller CIO Card - - Copyright (c) 2018-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -/* - * CTC is an intelligent feature card for the 3B2 that supports a - * Cipher "FloppyTape(tm)" 525 drive that can read and write 23MB - * DC600A cartridges. - * - * The CTC card is based on the Common I/O (CIO) platform. - * - * Notes: - * ------ - * - * The Cipher FloppyTape is an odd beast. Although it's a tape drive, - * it is controlled by a floppy controller. It is divided into virtual - * sectors that can be addressed by Cylinder / Track / Sector. - * Stepping and head select pulses dictate where on the tape to read - * from or write to. Moreover, System V maps a filesystem onto the - * tape, and a properly formatted tape drive will have a VTOC on - * partition 0. - * - */ - -#ifndef _3B2_CTC_H_ -#define _3B2_CTC_H_ - -#include "3b2_defs.h" - -#define CTC_ID 0x0005 -#define CTC_IPL 12 -#define CTC_VERSION 1 - -/* Request Opcodes */ -#define CTC_CONFIG 30 -#define CTC_CLOSE 31 -#define CTC_FORMAT 32 -#define CTC_OPEN 33 -#define CTC_READ 34 -#define CTC_WRITE 35 -#define CTC_VWRITE 36 - -/* Completion Opcodes */ -#define CTC_SUCCESS 0 -#define CTC_HWERROR 32 -#define CTC_RDONLY 33 -#define CTC_NOTREADY 36 -#define CTC_RWERROR 37 -#define CTC_NOMEDIA 42 - -/* VTOC values */ -#define VTOC_VERSION 1 -#define VTOC_SECSZ 512 -#define VTOC_PART 16 /* Number of "partitions" on tape */ -#define VTOC_VALID 0x600DDEEE /* Magic number for valid VTOC */ - -#define CTC_NUM_SD 2 -#define CTC_SD_FT25 4 -#define CTC_SD_FD5 1 - -/* Physical Device Info (pdinfo) values */ -#define PD_VALID 0xCA5E600D /* Magic number for valid PDINFO */ -#define PD_DRIVEID 5 -#define PD_VERSION 0 -#define PD_CYLS 6 -#define PD_TRACKS 245 -#define PD_SECTORS 31 -#define PD_BYTES 512 -#define PD_LOGICALST 29 - -#define CTC_CAPACITY (PD_CYLS * PD_TRACKS * PD_SECTORS) /* In blocks */ - -struct partition { - uint16 id; /* Partition ID */ - uint16 flag; /* Permission Flags */ - uint32 sstart; /* Starting Sector */ - uint32 ssize; /* Size in Sectors */ -}; - -struct vtoc { - uint32 bootinfo[3]; /* n/a */ - uint32 sanity; /* magic number */ - uint32 version; /* layout version */ - uint8 volume[8]; /* volume name */ - uint16 sectorsz; /* sector size in bytes */ - uint16 nparts; /* number of partitions */ - uint32 reserved[10]; /* free space */ - struct partition part[VTOC_PART]; /* partition headers */ - uint32 timestamp[VTOC_PART]; /* partition timestamp */ -}; - -struct pdinfo { - uint32 driveid; /* identifies the device type */ - uint32 sanity; /* verifies device sanity */ - uint32 version; /* version number */ - uint8 serial[12]; /* serial number of the device */ - uint32 cyls; /* number of cylinders per drive */ - uint32 tracks; /* number tracks per cylinder */ - uint32 sectors; /* number sectors per track */ - uint32 bytes; /* number of bytes per sector */ - uint32 logicalst; /* sector address of logical sector 0 */ - uint32 errlogst; /* sector address of error log area */ - uint32 errlogsz; /* size in bytes of error log area */ - uint32 mfgst; /* sector address of mfg. defect info */ - uint32 mfgsz; /* size in bytes of mfg. defect info */ - uint32 defectst; /* sector address of the defect map */ - uint32 defectsz; /* size in bytes of defect map */ - uint32 relno; /* number of relocation areas */ - uint32 relst; /* sector address of relocation area */ - uint32 relsz; /* size in sectors of relocation area */ - uint32 relnext; /* address of next avail reloc sector */ -}; - -typedef struct { - uint32 time; /* Time used during a tape session (in 25ms chunks) */ - uint32 bytnum; /* Byte number, for streaming mode */ -} CTC_STATE; - -t_stat ctc_reset(DEVICE *dptr); -t_stat ctc_svc(UNIT *uptr); -t_stat ctc_attach(UNIT *uptr, CONST char *cptr); -t_stat ctc_detach(UNIT *uptr); -void ctc_sysgen(uint8 slot); -void ctc_express(uint8 slot); -void ctc_full(uint8 slot); - -#endif /* _3B2_CTC_H_ */ +/* 3b2_ctc.h: CM195H 23MB Cartridge Tape Controller CIO Card + + Copyright (c) 2018-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +/* + * CTC is an intelligent feature card for the 3B2 that supports a + * Cipher "FloppyTape(tm)" 525 drive that can read and write 23MB + * DC600A cartridges. + * + * The CTC card is based on the Common I/O (CIO) platform. + * + * Notes: + * ------ + * + * The Cipher FloppyTape is an odd beast. Although it's a tape drive, + * it is controlled by a floppy controller. It is divided into virtual + * sectors that can be addressed by Cylinder / Track / Sector. + * Stepping and head select pulses dictate where on the tape to read + * from or write to. Moreover, System V maps a filesystem onto the + * tape, and a properly formatted tape drive will have a VTOC on + * partition 0. + * + */ + +#ifndef _3B2_CTC_H_ +#define _3B2_CTC_H_ + +#include "3b2_defs.h" + +#define CTC_ID 0x0005 +#define CTC_IPL 12 +#define CTC_VERSION 1 + +/* Request Opcodes */ +#define CTC_CONFIG 30 +#define CTC_CLOSE 31 +#define CTC_FORMAT 32 +#define CTC_OPEN 33 +#define CTC_READ 34 +#define CTC_WRITE 35 +#define CTC_VWRITE 36 + +/* Completion Opcodes */ +#define CTC_SUCCESS 0 +#define CTC_HWERROR 32 +#define CTC_RDONLY 33 +#define CTC_NOTREADY 36 +#define CTC_RWERROR 37 +#define CTC_NOMEDIA 42 + +/* VTOC values */ +#define VTOC_VERSION 1 +#define VTOC_SECSZ 512 +#define VTOC_PART 16 /* Number of "partitions" on tape */ +#define VTOC_VALID 0x600DDEEE /* Magic number for valid VTOC */ + +#define CTC_NUM_SD 2 +#define CTC_SD_FT25 4 +#define CTC_SD_FD5 1 + +/* Physical Device Info (pdinfo) values */ +#define PD_VALID 0xCA5E600D /* Magic number for valid PDINFO */ +#define PD_DRIVEID 5 +#define PD_VERSION 0 +#define PD_CYLS 6 +#define PD_TRACKS 245 +#define PD_SECTORS 31 +#define PD_BYTES 512 +#define PD_LOGICALST 29 + +#define CTC_CAPACITY (PD_CYLS * PD_TRACKS * PD_SECTORS) /* In blocks */ + +struct partition { + uint16 id; /* Partition ID */ + uint16 flag; /* Permission Flags */ + uint32 sstart; /* Starting Sector */ + uint32 ssize; /* Size in Sectors */ +}; + +struct vtoc { + uint32 bootinfo[3]; /* n/a */ + uint32 sanity; /* magic number */ + uint32 version; /* layout version */ + uint8 volume[8]; /* volume name */ + uint16 sectorsz; /* sector size in bytes */ + uint16 nparts; /* number of partitions */ + uint32 reserved[10]; /* free space */ + struct partition part[VTOC_PART]; /* partition headers */ + uint32 timestamp[VTOC_PART]; /* partition timestamp */ +}; + +struct pdinfo { + uint32 driveid; /* identifies the device type */ + uint32 sanity; /* verifies device sanity */ + uint32 version; /* version number */ + uint8 serial[12]; /* serial number of the device */ + uint32 cyls; /* number of cylinders per drive */ + uint32 tracks; /* number tracks per cylinder */ + uint32 sectors; /* number sectors per track */ + uint32 bytes; /* number of bytes per sector */ + uint32 logicalst; /* sector address of logical sector 0 */ + uint32 errlogst; /* sector address of error log area */ + uint32 errlogsz; /* size in bytes of error log area */ + uint32 mfgst; /* sector address of mfg. defect info */ + uint32 mfgsz; /* size in bytes of mfg. defect info */ + uint32 defectst; /* sector address of the defect map */ + uint32 defectsz; /* size in bytes of defect map */ + uint32 relno; /* number of relocation areas */ + uint32 relst; /* sector address of relocation area */ + uint32 relsz; /* size in sectors of relocation area */ + uint32 relnext; /* address of next avail reloc sector */ +}; + +typedef struct { + uint32 time; /* Time used during a tape session (in 25ms chunks) */ + uint32 bytnum; /* Byte number, for streaming mode */ +} CTC_STATE; + +t_stat ctc_reset(DEVICE *dptr); +t_stat ctc_svc(UNIT *uptr); +t_stat ctc_attach(UNIT *uptr, CONST char *cptr); +t_stat ctc_detach(UNIT *uptr); +void ctc_sysgen(uint8 slot); +void ctc_express(uint8 slot); +void ctc_full(uint8 slot); + +#endif /* _3B2_CTC_H_ */ diff --git a/3B2/3b2_defs.h b/3B2/3b2_defs.h index eca4d7f3..88587126 100644 --- a/3B2/3b2_defs.h +++ b/3B2/3b2_defs.h @@ -1,166 +1,166 @@ -/* 3b2_defs.h: AT&T 3B2 Shared Simulator Definitions - - Copyright (c) 2017-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#ifndef _3B2_DEFS_H_ -#define _3B2_DEFS_H_ - -#include - -#include "sim_defs.h" - -#if defined(REV3) -#include "3b2_rev3_defs.h" -#else -#include "3b2_rev2_defs.h" -#endif - -#ifndef FALSE -#define FALSE 0 -#endif -#ifndef TRUE -#define TRUE 1 -#endif - -#if defined(__GNUC__) -#define noret void __attribute__((noreturn)) -#else -#define noret void -#endif - -#ifndef MAX -#define MAX(x, y) ((x) > (y) ? (x) : (y)) -#endif -#ifndef MIN -#define MIN(x, y) ((x) < (y) ? (x) : (y)) -#endif -#ifndef UNUSED -#define UNUSED(x) ((void)((x))) -#endif - -#define ATOW(arr, i) \ - ((uint32)(arr)[i + 3] + ((uint32)(arr)[i + 2] << 8) + \ - ((uint32)(arr)[i + 1] << 16) + ((uint32)(arr)[i] << 24)) - -#define ATOH(arr, i) ((uint32)(arr)[i + 1] + ((uint32)(arr)[i] << 8)) - -#define CSRBIT(bit, sc) \ - { \ - if (sc) { \ - csr_data |= (bit); \ - } else { \ - csr_data &= ~(bit); \ - } \ - } - -#define PCHAR(c) (((char) (c) >= 0x20 && (char) (c) < 0x7f) ? (char) (c) : '.') - -#define ROM_SIZE (128 * 1024) -#define POLL_WAIT 70000 - -#define UNIT_V_EXBRK (UNIT_V_UF + 0) -#define UNIT_V_OPBRK (UNIT_V_UF + 1) -#define UNIT_EXBRK (1u << UNIT_V_EXBRK) -#define UNIT_OPBRK (1u << UNIT_V_OPBRK) - -#define EX_V_FLAG 1 << 21 - -#define ROM_BASE 0 -#define PHYS_MEM_BASE 0x2000000 - -#define MSIZ_512K 0x80000 -#define MSIZ_1M 0x100000 -#define MSIZ_2M 0x200000 -#define MSIZ_4M 0x400000 -#define MSIZ_8M 0x800000 -#define MSIZ_16M 0x1000000 -#define MSIZ_32M 0x2000000 -#define MSIZ_64M 0x4000000 - -/* Simulator stop codes */ -#define STOP_RSRV 1 -#define STOP_IBKPT 2 /* Breakpoint encountered */ -#define STOP_OPCODE 3 /* Invalid opcode */ -#define STOP_IRQ 4 /* Interrupt */ -#define STOP_EX 5 /* Exception */ -#define STOP_ESTK 6 /* Exception stack too deep */ -#define STOP_MMU 7 /* Unimplemented MMU Feature */ -#define STOP_POWER 8 /* System power-off */ -#define STOP_LOOP 9 /* Infinite loop stop */ -#define STOP_ERR 10 /* Other error */ - -/* Debug flags */ -#define READ_MSG 0x0001 -#define WRITE_MSG 0x0002 -#define DECODE_MSG 0x0004 -#define EXECUTE_MSG 0x0008 -#define INIT_MSG 0x0010 -#define IRQ_MSG 0x0020 -#define IO_DBG 0x0040 -#define CIO_DBG 0x0080 -#define TRACE_DBG 0x0100 -#define CALL_DBG 0x0200 -#define PKT_DBG 0x0400 -#define ERR_MSG 0x0800 -#define CACHE_DBG 0x1000 -#define DECODE_DBG 0x2000 - -#define TIMER_SANITY 0 -#define TIMER_INTERVAL 1 -#define TIMER_BUS 2 - -/* Timers */ -#define TMR_CLK 0 /* Calibrated 100Hz timer */ - -/* Global symbols */ - -extern DEBTAB sys_deb_tab[]; -extern DEVICE contty_dev; -extern DEVICE cpu_dev; -extern DEVICE csr_dev; -extern DEVICE ctc_dev; -extern DEVICE dmac_dev; -extern DEVICE id_dev; -extern DEVICE if_dev; -extern DEVICE iu_timer_dev; -extern DEVICE mau_dev; -extern DEVICE mmu_dev; -extern DEVICE ni_dev; -extern DEVICE nvram_dev; -extern DEVICE ports_dev; -extern DEVICE timer_dev; -extern DEVICE tod_dev; -extern DEVICE tti_dev; -extern DEVICE tto_dev; -#if defined(REV3) -extern DEVICE flt_dev; -extern DEVICE ha_dev; -#endif /* defined(REV3) */ - -#endif /* _3B2_DEFS_H_ */ +/* 3b2_defs.h: AT&T 3B2 Shared Simulator Definitions + + Copyright (c) 2017-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#ifndef _3B2_DEFS_H_ +#define _3B2_DEFS_H_ + +#include + +#include "sim_defs.h" + +#if defined(REV3) +#include "3b2_rev3_defs.h" +#else +#include "3b2_rev2_defs.h" +#endif + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +#if defined(__GNUC__) +#define noret void __attribute__((noreturn)) +#else +#define noret void +#endif + +#ifndef MAX +#define MAX(x, y) ((x) > (y) ? (x) : (y)) +#endif +#ifndef MIN +#define MIN(x, y) ((x) < (y) ? (x) : (y)) +#endif +#ifndef UNUSED +#define UNUSED(x) ((void)((x))) +#endif + +#define ATOW(arr, i) \ + ((uint32)(arr)[i + 3] + ((uint32)(arr)[i + 2] << 8) + \ + ((uint32)(arr)[i + 1] << 16) + ((uint32)(arr)[i] << 24)) + +#define ATOH(arr, i) ((uint32)(arr)[i + 1] + ((uint32)(arr)[i] << 8)) + +#define CSRBIT(bit, sc) \ + { \ + if (sc) { \ + csr_data |= (bit); \ + } else { \ + csr_data &= ~(bit); \ + } \ + } + +#define PCHAR(c) (((char) (c) >= 0x20 && (char) (c) < 0x7f) ? (char) (c) : '.') + +#define ROM_SIZE (128 * 1024) +#define POLL_WAIT 70000 + +#define UNIT_V_EXBRK (UNIT_V_UF + 0) +#define UNIT_V_OPBRK (UNIT_V_UF + 1) +#define UNIT_EXBRK (1u << UNIT_V_EXBRK) +#define UNIT_OPBRK (1u << UNIT_V_OPBRK) + +#define EX_V_FLAG 1 << 21 + +#define ROM_BASE 0 +#define PHYS_MEM_BASE 0x2000000 + +#define MSIZ_512K 0x80000 +#define MSIZ_1M 0x100000 +#define MSIZ_2M 0x200000 +#define MSIZ_4M 0x400000 +#define MSIZ_8M 0x800000 +#define MSIZ_16M 0x1000000 +#define MSIZ_32M 0x2000000 +#define MSIZ_64M 0x4000000 + +/* Simulator stop codes */ +#define STOP_RSRV 1 +#define STOP_IBKPT 2 /* Breakpoint encountered */ +#define STOP_OPCODE 3 /* Invalid opcode */ +#define STOP_IRQ 4 /* Interrupt */ +#define STOP_EX 5 /* Exception */ +#define STOP_ESTK 6 /* Exception stack too deep */ +#define STOP_MMU 7 /* Unimplemented MMU Feature */ +#define STOP_POWER 8 /* System power-off */ +#define STOP_LOOP 9 /* Infinite loop stop */ +#define STOP_ERR 10 /* Other error */ + +/* Debug flags */ +#define READ_MSG 0x0001 +#define WRITE_MSG 0x0002 +#define DECODE_MSG 0x0004 +#define EXECUTE_MSG 0x0008 +#define INIT_MSG 0x0010 +#define IRQ_MSG 0x0020 +#define IO_DBG 0x0040 +#define CIO_DBG 0x0080 +#define TRACE_DBG 0x0100 +#define CALL_DBG 0x0200 +#define PKT_DBG 0x0400 +#define ERR_MSG 0x0800 +#define CACHE_DBG 0x1000 +#define DECODE_DBG 0x2000 + +#define TIMER_SANITY 0 +#define TIMER_INTERVAL 1 +#define TIMER_BUS 2 + +/* Timers */ +#define TMR_CLK 0 /* Calibrated 100Hz timer */ + +/* Global symbols */ + +extern DEBTAB sys_deb_tab[]; +extern DEVICE contty_dev; +extern DEVICE cpu_dev; +extern DEVICE csr_dev; +extern DEVICE ctc_dev; +extern DEVICE dmac_dev; +extern DEVICE id_dev; +extern DEVICE if_dev; +extern DEVICE iu_timer_dev; +extern DEVICE mau_dev; +extern DEVICE mmu_dev; +extern DEVICE ni_dev; +extern DEVICE nvram_dev; +extern DEVICE ports_dev; +extern DEVICE timer_dev; +extern DEVICE tod_dev; +extern DEVICE tti_dev; +extern DEVICE tto_dev; +#if defined(REV3) +extern DEVICE flt_dev; +extern DEVICE ha_dev; +#endif /* defined(REV3) */ + +#endif /* _3B2_DEFS_H_ */ diff --git a/3B2/3b2_dmac.c b/3B2/3b2_dmac.c index 5a630d4f..85a44aa5 100644 --- a/3B2/3b2_dmac.c +++ b/3B2/3b2_dmac.c @@ -1,481 +1,481 @@ -/* 3b2_dmac.c: AM9517 DMA Controller - - Copyright (c) 2021-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#include "3b2_dmac.h" - -#if defined(REV2) -#include "3b2_id.h" -#endif - -#include "3b2_cpu.h" -#include "3b2_if.h" -#include "3b2_iu.h" -#include "3b2_mem.h" -#include "3b2_stddev.h" -#include "3b2_csr.h" - -DMA_STATE dma_state; - -UNIT dmac_unit[] = { - { UDATA (NULL, 0, 0), 0, 0 }, - { UDATA (NULL, 0, 0), 0, 1 }, - { UDATA (NULL, 0, 0), 0, 2 }, - { UDATA (NULL, 0, 0), 0, 3 }, - { NULL } -}; - -REG dmac_reg[] = { - { NULL } -}; - -DEVICE dmac_dev = { - "DMAC", dmac_unit, dmac_reg, NULL, - 1, 16, 8, 4, 16, 32, - NULL, NULL, &dmac_reset, - NULL, NULL, NULL, NULL, - DEV_DEBUG, 0, sys_deb_tab -}; - -dmac_dma_handler device_dma_handlers[] = { -#if defined(REV2) - {DMA_ID_CHAN, IDBASE+ID_DATA_REG, &id_drq, dmac_generic_dma, id_after_dma}, -#endif - {DMA_IF_CHAN, IFBASE+IF_DATA_REG, &if_state.drq, dmac_generic_dma, if_after_dma}, - {DMA_IUA_CHAN, IUBASE+IUA_DATA_REG, &iu_console.drq, iu_dma_console, NULL}, - {DMA_IUB_CHAN, IUBASE+IUB_DATA_REG, &iu_contty.drq, iu_dma_contty, NULL}, - {0, 0, NULL, NULL, NULL } -}; - -uint32 dma_address(uint8 channel, uint32 offset) { - uint32 addr, page; - if (DMA_DECR(channel)) { - addr = (PHYS_MEM_BASE + (uint32)(dma_state.channels[channel].addr) - offset); - } else { - addr = (PHYS_MEM_BASE + (uint32)(dma_state.channels[channel].addr) + offset); - } -#if defined (REV3) - page = (uint32)dma_state.channels[channel].page; -#else - /* In Rev 2, the top bit of the page address is a R/W bit, so - we mask it here */ - page = (uint32)dma_state.channels[channel].page & 0x7f; -#endif - addr |= page << 16; - return addr; -} - -t_stat dmac_reset(DEVICE *dptr) -{ - int i; - - memset(&dma_state, 0, sizeof(dma_state)); - - for (i = 0; i < 4; i++) { - dma_state.channels[i].page = 0; - dma_state.channels[i].addr = 0; - dma_state.channels[i].mode = 0; - dma_state.channels[i].wcount = 0; - dma_state.channels[i].addr_c = 0; - dma_state.channels[i].wcount_c = -1; - dma_state.channels[i].ptr = 0; - } - - return SCPE_OK; -} - -uint32 dmac_read(uint32 pa, size_t size) -{ - uint8 reg, base, data; - - base = (uint8) (pa >> 12); - reg = pa & 0xff; - - switch (base) { - case DMA_C: - switch (reg) { - case 0: /* channel 0 current address reg */ - data = ((dma_state.channels[0].addr_c) >> (dma_state.bff * 8)) & 0xff; - sim_debug(READ_MSG, &dmac_dev, - "Reading Channel 0 Addr Reg: %08x\n", - data); - dma_state.bff ^= 1; - break; - case 1: /* channel 0 current address reg */ - data = ((dma_state.channels[0].wcount_c) >> (dma_state.bff * 8)) & 0xff; - sim_debug(READ_MSG, &dmac_dev, - "Reading Channel 0 Addr Count Reg: %08x\n", - data); - dma_state.bff ^= 1; - break; - case 2: /* channel 1 current address reg */ - data = ((dma_state.channels[1].addr_c) >> (dma_state.bff * 8)) & 0xff; - sim_debug(READ_MSG, &dmac_dev, - "Reading Channel 1 Addr Reg: %08x\n", - data); - dma_state.bff ^= 1; - break; - case 3: /* channel 1 current address reg */ - data = ((dma_state.channels[1].wcount_c) >> (dma_state.bff * 8)) & 0xff; - sim_debug(READ_MSG, &dmac_dev, - "Reading Channel 1 Addr Count Reg: %08x\n", - data); - dma_state.bff ^= 1; - break; - case 4: /* channel 2 current address reg */ - data = ((dma_state.channels[2].addr_c) >> (dma_state.bff * 8)) & 0xff; - sim_debug(READ_MSG, &dmac_dev, - "Reading Channel 2 Addr Reg: %08x\n", - data); - dma_state.bff ^= 1; - break; - case 5: /* channel 2 current address reg */ - data = ((dma_state.channels[2].wcount_c) >> (dma_state.bff * 8)) & 0xff; - sim_debug(READ_MSG, &dmac_dev, - "Reading Channel 2 Addr Count Reg: %08x\n", - data); - dma_state.bff ^= 1; - break; - case 6: /* channel 3 current address reg */ - data = ((dma_state.channels[3].addr_c) >> (dma_state.bff * 8)) & 0xff; - sim_debug(READ_MSG, &dmac_dev, - "Reading Channel 3 Addr Reg: %08x\n", - data); - dma_state.bff ^= 1; - break; - case 7: /* channel 3 current address reg */ - data = ((dma_state.channels[3].wcount_c) >> (dma_state.bff * 8)) & 0xff; - sim_debug(READ_MSG, &dmac_dev, - "Reading Channel 3 Addr Count Reg: %08x\n", - data); - dma_state.bff ^= 1; - break; - case 8: - data = dma_state.status; - sim_debug(READ_MSG, &dmac_dev, - "Reading DMAC Status %08x\n", - data); - dma_state.status = 0; - break; - default: - sim_debug(READ_MSG, &dmac_dev, - "DMAC READ %lu B @ %08x\n", - size, pa); - data = 0; - } - - return data; - default: - sim_debug(READ_MSG, &dmac_dev, - "[BASE: %08x] DMAC READ %lu B @ %08x\n", - base, size, pa); - return 0; - } -} - -/* - * Program the DMAC - */ -void dmac_program(uint8 reg, uint8 val) -{ - uint8 channel_id, i, chan_num; - dma_channel *channel; - -#if defined(REV3) - /* TODO: More general DMA interrupt clearing */ - CPU_CLR_INT(INT_UART_DMA); - CLR_CSR(CSRDMA); -#endif - - if (reg < 8) { - switch (reg) { - case 0: - case 1: - chan_num = 0; - break; - case 2: - case 3: - chan_num = 1; - break; - case 4: - case 5: - chan_num = 2; - break; - case 6: - case 7: - chan_num = 3; - break; - default: - chan_num = 0; - break; - } - - channel = &dma_state.channels[chan_num]; - - switch (reg & 1) { - case 0: /* Address */ - channel->addr &= ~(0xff << dma_state.bff * 8); - channel->addr |= (val & 0xff) << (dma_state.bff * 8); - channel->addr_c = channel->addr; - sim_debug(WRITE_MSG, &dmac_dev, - "Set address channel %d byte %d = %08x\n", - chan_num, dma_state.bff, channel->addr); - break; - case 1: /* Word Count */ - channel->wcount &= ~(0xff << dma_state.bff * 8); - channel->wcount |= (val & 0xff) << (dma_state.bff * 8); - channel->wcount_c = channel->wcount; - channel->ptr = 0; - sim_debug(WRITE_MSG, &dmac_dev, - "Set word count channel %d byte %d = %08x\n", - chan_num, dma_state.bff, channel->wcount); - break; - } - - /* Toggle the byte flip-flop */ - dma_state.bff ^= 1; - - /* Handled. */ - return; - } - - /* If it hasn't been handled, it must be one of the following - registers. */ - - switch (reg) { - case 8: /* Command */ - dma_state.command = val; - sim_debug(WRITE_MSG, &dmac_dev, - "Command: val=%02x\n", - val); - break; - case 9: /* Request */ - sim_debug(WRITE_MSG, &dmac_dev, - "Request set: val=%02x\n", - val); - dma_state.request = val; - break; - case 10: /* Write Single Mask Register Bit */ - channel_id = val & 3; - - /* "Clear or Set" is bit 2 */ - if ((val >> 2) & 1) { - dma_state.mask |= (1 << channel_id); - } else { - dma_state.mask &= ~(1 << channel_id); - /* Set the appropriate DRQ */ - /* *dmac_drq_handlers[channel_id].drq = TRUE; */ - } - - sim_debug(WRITE_MSG, &dmac_dev, - "Write Single Mask Register Bit. channel=%d set/clear=%02x\n", - channel_id, (val >> 2) & 1); - break; - case 11: /* Mode */ - channel_id = val & 3; - sim_debug(WRITE_MSG, &dmac_dev, - "Mode Set. channel=%d val=%02x\n", - channel_id, val); - dma_state.channels[channel_id].mode = val; - break; - case 12: /* Clear Byte Pointer Flip/Flop */ - dma_state.bff = 0; - break; - case 13: /* Master Clear */ - dma_state.bff = 0; - dma_state.command = 0; - dma_state.status = 0; - for (i = 0; i < 4; i++) { - dma_state.channels[i].page = 0; - dma_state.channels[i].addr = 0; - dma_state.channels[i].wcount = 0; - dma_state.channels[i].addr_c = 0; - dma_state.channels[i].wcount_c = -1; - dma_state.channels[i].ptr = 0; - } - break; - case 15: /* Write All Mask Register Bits */ - sim_debug(WRITE_MSG, &dmac_dev, - "Write DMAC mask (all bits). Val=%02x\n", - val); - dma_state.mask = val & 0xf; - break; - case 16: /* Clear DMAC Interrupt */ - sim_debug(WRITE_MSG, &dmac_dev, - "Clear DMA Interrupt in DMAC. val=%02x\n", - val); - break; - default: - sim_debug(WRITE_MSG, &dmac_dev, - "Unhandled DMAC write. reg=%x val=%02x\n", - reg, val); - break; - } -} - -void dmac_page_update(uint8 base, uint8 reg, uint8 val) -{ - uint8 shift = 0; - - /* Sanity check */ - if (reg > 3) { - return; - } - -#if defined(REV2) - /* In Rev2 systems, the actual register is a 32-bit, - byte-addressed register, so that address 4x000 is the highest - byte, 4x003 is the lowest byte. */ - shift = -(reg - 3) * 8; -#endif - - switch (base) { -#if defined (REV2) - case DMA_ID: - sim_debug(WRITE_MSG, &dmac_dev, "Set page channel 0 = %x\n", val); - dma_state.channels[DMA_ID_CHAN].page &= ~(0xff << shift); - dma_state.channels[DMA_ID_CHAN].page |= ((uint16)val << shift); - break; -#endif - case DMA_IF: - sim_debug(WRITE_MSG, &dmac_dev, "Set page channel 1 = %x\n", val); - dma_state.channels[DMA_IF_CHAN].page &= ~(0xff << shift); - dma_state.channels[DMA_IF_CHAN].page |= ((uint16)val << shift); - break; - case DMA_IUA: - sim_debug(WRITE_MSG, &dmac_dev, "Set page channel 2 = %x\n", val); - dma_state.channels[DMA_IUA_CHAN].page &= ~(0xff << shift); - dma_state.channels[DMA_IUA_CHAN].page |= ((uint16)val << shift); - break; - case DMA_IUB: - sim_debug(WRITE_MSG, &dmac_dev, "Set page channel 3 = %x\n", val); - dma_state.channels[DMA_IUB_CHAN].page &= ~(0xff << shift); - dma_state.channels[DMA_IUB_CHAN].page |= ((uint16)val << shift); - break; - } -} - -void dmac_write(uint32 pa, uint32 val, size_t size) -{ - uint8 reg, base; - - base = (uint8) (pa >> 12); - reg = pa & 0xff; - - switch (base) { - case DMA_C: - dmac_program(reg, (uint8) val); - break; -#if defined (REV2) - case DMA_ID: -#endif - case DMA_IUA: - case DMA_IUB: - case DMA_IF: - dmac_page_update(base, reg, (uint8) val); - break; - } -} - -void dmac_generic_dma(uint8 channel, uint32 service_address) -{ - uint8 data; - int32 i; - uint32 addr; - dma_channel *chan = &dma_state.channels[channel]; - - i = chan->wcount_c; - - /* TODO: This assumes every transfer is a block mode, which is not - guaranteed to be valid, but is likely safe? */ - - switch (DMA_XFER(channel)) { - case DMA_XFER_VERIFY: - sim_debug(EXECUTE_MSG, &dmac_dev, - "[dmac_generic_dma channel=%d] unhandled VERIFY request.\n", - channel); - break; - case DMA_XFER_WRITE: - sim_debug(EXECUTE_MSG, &dmac_dev, - "[dmac_generic_dma channel=%d] write: %d bytes to %08x from %08x (page=%04x addr=%08x)\n", - channel, - chan->wcount + 1, - dma_address(channel, 0), - service_address, - dma_state.channels[channel].page, - dma_state.channels[channel].addr); - for (; i >= 0; i--) { - chan->wcount_c--; - addr = dma_address(channel, chan->ptr++); - chan->addr_c = addr; - data = pread_b(service_address, BUS_PER); - write_b(addr, data, BUS_PER); - } - break; - case DMA_XFER_READ: - sim_debug(EXECUTE_MSG, &dmac_dev, - "[dmac_generic_dma channel=%d] read: %d bytes from %08x to %08x\n", - channel, - chan->wcount + 1, - dma_address(channel, 0), - service_address); - for (; i >= 0; i--) { - chan->wcount_c = i; - addr = dma_address(channel, chan->ptr++); - chan->addr_c = addr; - data = pread_b(addr, BUS_PER); - write_b(service_address, data, BUS_PER); - } - break; - } - - /* End of Process must set the channel's mask bit */ - dma_state.mask |= (1 << channel); - dma_state.status |= (1 << channel); -} - -/* - * Service pending DRQs - */ -void dmac_service_drqs() -{ - volatile dmac_dma_handler *h; - - for (h = &device_dma_handlers[0]; h->drq != NULL; h++) { - /* Only trigger if the channel has a DRQ set and its channel's - mask bit is 0 */ - if (*h->drq && ((dma_state.mask >> h->channel) & 0x1) == 0) { - h->dma_handler(h->channel, h->service_address); - /* Each handler is responsible for clearing its own DRQ line! */ - if (h->after_dma_callback != NULL) { - h->after_dma_callback(); - } - } - } -} +/* 3b2_dmac.c: AM9517 DMA Controller + + Copyright (c) 2021-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#include "3b2_dmac.h" + +#if defined(REV2) +#include "3b2_id.h" +#endif + +#include "3b2_cpu.h" +#include "3b2_if.h" +#include "3b2_iu.h" +#include "3b2_mem.h" +#include "3b2_stddev.h" +#include "3b2_csr.h" + +DMA_STATE dma_state; + +UNIT dmac_unit[] = { + { UDATA (NULL, 0, 0), 0, 0 }, + { UDATA (NULL, 0, 0), 0, 1 }, + { UDATA (NULL, 0, 0), 0, 2 }, + { UDATA (NULL, 0, 0), 0, 3 }, + { NULL } +}; + +REG dmac_reg[] = { + { NULL } +}; + +DEVICE dmac_dev = { + "DMAC", dmac_unit, dmac_reg, NULL, + 1, 16, 8, 4, 16, 32, + NULL, NULL, &dmac_reset, + NULL, NULL, NULL, NULL, + DEV_DEBUG, 0, sys_deb_tab +}; + +dmac_dma_handler device_dma_handlers[] = { +#if defined(REV2) + {DMA_ID_CHAN, IDBASE+ID_DATA_REG, &id_drq, dmac_generic_dma, id_after_dma}, +#endif + {DMA_IF_CHAN, IFBASE+IF_DATA_REG, &if_state.drq, dmac_generic_dma, if_after_dma}, + {DMA_IUA_CHAN, IUBASE+IUA_DATA_REG, &iu_console.drq, iu_dma_console, NULL}, + {DMA_IUB_CHAN, IUBASE+IUB_DATA_REG, &iu_contty.drq, iu_dma_contty, NULL}, + {0, 0, NULL, NULL, NULL } +}; + +uint32 dma_address(uint8 channel, uint32 offset) { + uint32 addr, page; + if (DMA_DECR(channel)) { + addr = (PHYS_MEM_BASE + (uint32)(dma_state.channels[channel].addr) - offset); + } else { + addr = (PHYS_MEM_BASE + (uint32)(dma_state.channels[channel].addr) + offset); + } +#if defined (REV3) + page = (uint32)dma_state.channels[channel].page; +#else + /* In Rev 2, the top bit of the page address is a R/W bit, so + we mask it here */ + page = (uint32)dma_state.channels[channel].page & 0x7f; +#endif + addr |= page << 16; + return addr; +} + +t_stat dmac_reset(DEVICE *dptr) +{ + int i; + + memset(&dma_state, 0, sizeof(dma_state)); + + for (i = 0; i < 4; i++) { + dma_state.channels[i].page = 0; + dma_state.channels[i].addr = 0; + dma_state.channels[i].mode = 0; + dma_state.channels[i].wcount = 0; + dma_state.channels[i].addr_c = 0; + dma_state.channels[i].wcount_c = -1; + dma_state.channels[i].ptr = 0; + } + + return SCPE_OK; +} + +uint32 dmac_read(uint32 pa, size_t size) +{ + uint8 reg, base, data; + + base = (uint8) (pa >> 12); + reg = pa & 0xff; + + switch (base) { + case DMA_C: + switch (reg) { + case 0: /* channel 0 current address reg */ + data = ((dma_state.channels[0].addr_c) >> (dma_state.bff * 8)) & 0xff; + sim_debug(READ_MSG, &dmac_dev, + "Reading Channel 0 Addr Reg: %08x\n", + data); + dma_state.bff ^= 1; + break; + case 1: /* channel 0 current address reg */ + data = ((dma_state.channels[0].wcount_c) >> (dma_state.bff * 8)) & 0xff; + sim_debug(READ_MSG, &dmac_dev, + "Reading Channel 0 Addr Count Reg: %08x\n", + data); + dma_state.bff ^= 1; + break; + case 2: /* channel 1 current address reg */ + data = ((dma_state.channels[1].addr_c) >> (dma_state.bff * 8)) & 0xff; + sim_debug(READ_MSG, &dmac_dev, + "Reading Channel 1 Addr Reg: %08x\n", + data); + dma_state.bff ^= 1; + break; + case 3: /* channel 1 current address reg */ + data = ((dma_state.channels[1].wcount_c) >> (dma_state.bff * 8)) & 0xff; + sim_debug(READ_MSG, &dmac_dev, + "Reading Channel 1 Addr Count Reg: %08x\n", + data); + dma_state.bff ^= 1; + break; + case 4: /* channel 2 current address reg */ + data = ((dma_state.channels[2].addr_c) >> (dma_state.bff * 8)) & 0xff; + sim_debug(READ_MSG, &dmac_dev, + "Reading Channel 2 Addr Reg: %08x\n", + data); + dma_state.bff ^= 1; + break; + case 5: /* channel 2 current address reg */ + data = ((dma_state.channels[2].wcount_c) >> (dma_state.bff * 8)) & 0xff; + sim_debug(READ_MSG, &dmac_dev, + "Reading Channel 2 Addr Count Reg: %08x\n", + data); + dma_state.bff ^= 1; + break; + case 6: /* channel 3 current address reg */ + data = ((dma_state.channels[3].addr_c) >> (dma_state.bff * 8)) & 0xff; + sim_debug(READ_MSG, &dmac_dev, + "Reading Channel 3 Addr Reg: %08x\n", + data); + dma_state.bff ^= 1; + break; + case 7: /* channel 3 current address reg */ + data = ((dma_state.channels[3].wcount_c) >> (dma_state.bff * 8)) & 0xff; + sim_debug(READ_MSG, &dmac_dev, + "Reading Channel 3 Addr Count Reg: %08x\n", + data); + dma_state.bff ^= 1; + break; + case 8: + data = dma_state.status; + sim_debug(READ_MSG, &dmac_dev, + "Reading DMAC Status %08x\n", + data); + dma_state.status = 0; + break; + default: + sim_debug(READ_MSG, &dmac_dev, + "DMAC READ %lu B @ %08x\n", + size, pa); + data = 0; + } + + return data; + default: + sim_debug(READ_MSG, &dmac_dev, + "[BASE: %08x] DMAC READ %lu B @ %08x\n", + base, size, pa); + return 0; + } +} + +/* + * Program the DMAC + */ +void dmac_program(uint8 reg, uint8 val) +{ + uint8 channel_id, i, chan_num; + dma_channel *channel; + +#if defined(REV3) + /* TODO: More general DMA interrupt clearing */ + CPU_CLR_INT(INT_UART_DMA); + CLR_CSR(CSRDMA); +#endif + + if (reg < 8) { + switch (reg) { + case 0: + case 1: + chan_num = 0; + break; + case 2: + case 3: + chan_num = 1; + break; + case 4: + case 5: + chan_num = 2; + break; + case 6: + case 7: + chan_num = 3; + break; + default: + chan_num = 0; + break; + } + + channel = &dma_state.channels[chan_num]; + + switch (reg & 1) { + case 0: /* Address */ + channel->addr &= ~(0xff << dma_state.bff * 8); + channel->addr |= (val & 0xff) << (dma_state.bff * 8); + channel->addr_c = channel->addr; + sim_debug(WRITE_MSG, &dmac_dev, + "Set address channel %d byte %d = %08x\n", + chan_num, dma_state.bff, channel->addr); + break; + case 1: /* Word Count */ + channel->wcount &= ~(0xff << dma_state.bff * 8); + channel->wcount |= (val & 0xff) << (dma_state.bff * 8); + channel->wcount_c = channel->wcount; + channel->ptr = 0; + sim_debug(WRITE_MSG, &dmac_dev, + "Set word count channel %d byte %d = %08x\n", + chan_num, dma_state.bff, channel->wcount); + break; + } + + /* Toggle the byte flip-flop */ + dma_state.bff ^= 1; + + /* Handled. */ + return; + } + + /* If it hasn't been handled, it must be one of the following + registers. */ + + switch (reg) { + case 8: /* Command */ + dma_state.command = val; + sim_debug(WRITE_MSG, &dmac_dev, + "Command: val=%02x\n", + val); + break; + case 9: /* Request */ + sim_debug(WRITE_MSG, &dmac_dev, + "Request set: val=%02x\n", + val); + dma_state.request = val; + break; + case 10: /* Write Single Mask Register Bit */ + channel_id = val & 3; + + /* "Clear or Set" is bit 2 */ + if ((val >> 2) & 1) { + dma_state.mask |= (1 << channel_id); + } else { + dma_state.mask &= ~(1 << channel_id); + /* Set the appropriate DRQ */ + /* *dmac_drq_handlers[channel_id].drq = TRUE; */ + } + + sim_debug(WRITE_MSG, &dmac_dev, + "Write Single Mask Register Bit. channel=%d set/clear=%02x\n", + channel_id, (val >> 2) & 1); + break; + case 11: /* Mode */ + channel_id = val & 3; + sim_debug(WRITE_MSG, &dmac_dev, + "Mode Set. channel=%d val=%02x\n", + channel_id, val); + dma_state.channels[channel_id].mode = val; + break; + case 12: /* Clear Byte Pointer Flip/Flop */ + dma_state.bff = 0; + break; + case 13: /* Master Clear */ + dma_state.bff = 0; + dma_state.command = 0; + dma_state.status = 0; + for (i = 0; i < 4; i++) { + dma_state.channels[i].page = 0; + dma_state.channels[i].addr = 0; + dma_state.channels[i].wcount = 0; + dma_state.channels[i].addr_c = 0; + dma_state.channels[i].wcount_c = -1; + dma_state.channels[i].ptr = 0; + } + break; + case 15: /* Write All Mask Register Bits */ + sim_debug(WRITE_MSG, &dmac_dev, + "Write DMAC mask (all bits). Val=%02x\n", + val); + dma_state.mask = val & 0xf; + break; + case 16: /* Clear DMAC Interrupt */ + sim_debug(WRITE_MSG, &dmac_dev, + "Clear DMA Interrupt in DMAC. val=%02x\n", + val); + break; + default: + sim_debug(WRITE_MSG, &dmac_dev, + "Unhandled DMAC write. reg=%x val=%02x\n", + reg, val); + break; + } +} + +void dmac_page_update(uint8 base, uint8 reg, uint8 val) +{ + uint8 shift = 0; + + /* Sanity check */ + if (reg > 3) { + return; + } + +#if defined(REV2) + /* In Rev2 systems, the actual register is a 32-bit, + byte-addressed register, so that address 4x000 is the highest + byte, 4x003 is the lowest byte. */ + shift = -(reg - 3) * 8; +#endif + + switch (base) { +#if defined (REV2) + case DMA_ID: + sim_debug(WRITE_MSG, &dmac_dev, "Set page channel 0 = %x\n", val); + dma_state.channels[DMA_ID_CHAN].page &= ~(0xff << shift); + dma_state.channels[DMA_ID_CHAN].page |= ((uint16)val << shift); + break; +#endif + case DMA_IF: + sim_debug(WRITE_MSG, &dmac_dev, "Set page channel 1 = %x\n", val); + dma_state.channels[DMA_IF_CHAN].page &= ~(0xff << shift); + dma_state.channels[DMA_IF_CHAN].page |= ((uint16)val << shift); + break; + case DMA_IUA: + sim_debug(WRITE_MSG, &dmac_dev, "Set page channel 2 = %x\n", val); + dma_state.channels[DMA_IUA_CHAN].page &= ~(0xff << shift); + dma_state.channels[DMA_IUA_CHAN].page |= ((uint16)val << shift); + break; + case DMA_IUB: + sim_debug(WRITE_MSG, &dmac_dev, "Set page channel 3 = %x\n", val); + dma_state.channels[DMA_IUB_CHAN].page &= ~(0xff << shift); + dma_state.channels[DMA_IUB_CHAN].page |= ((uint16)val << shift); + break; + } +} + +void dmac_write(uint32 pa, uint32 val, size_t size) +{ + uint8 reg, base; + + base = (uint8) (pa >> 12); + reg = pa & 0xff; + + switch (base) { + case DMA_C: + dmac_program(reg, (uint8) val); + break; +#if defined (REV2) + case DMA_ID: +#endif + case DMA_IUA: + case DMA_IUB: + case DMA_IF: + dmac_page_update(base, reg, (uint8) val); + break; + } +} + +void dmac_generic_dma(uint8 channel, uint32 service_address) +{ + uint8 data; + int32 i; + uint32 addr; + dma_channel *chan = &dma_state.channels[channel]; + + i = chan->wcount_c; + + /* TODO: This assumes every transfer is a block mode, which is not + guaranteed to be valid, but is likely safe? */ + + switch (DMA_XFER(channel)) { + case DMA_XFER_VERIFY: + sim_debug(EXECUTE_MSG, &dmac_dev, + "[dmac_generic_dma channel=%d] unhandled VERIFY request.\n", + channel); + break; + case DMA_XFER_WRITE: + sim_debug(EXECUTE_MSG, &dmac_dev, + "[dmac_generic_dma channel=%d] write: %d bytes to %08x from %08x (page=%04x addr=%08x)\n", + channel, + chan->wcount + 1, + dma_address(channel, 0), + service_address, + dma_state.channels[channel].page, + dma_state.channels[channel].addr); + for (; i >= 0; i--) { + chan->wcount_c--; + addr = dma_address(channel, chan->ptr++); + chan->addr_c = addr; + data = pread_b(service_address, BUS_PER); + write_b(addr, data, BUS_PER); + } + break; + case DMA_XFER_READ: + sim_debug(EXECUTE_MSG, &dmac_dev, + "[dmac_generic_dma channel=%d] read: %d bytes from %08x to %08x\n", + channel, + chan->wcount + 1, + dma_address(channel, 0), + service_address); + for (; i >= 0; i--) { + chan->wcount_c = i; + addr = dma_address(channel, chan->ptr++); + chan->addr_c = addr; + data = pread_b(addr, BUS_PER); + write_b(service_address, data, BUS_PER); + } + break; + } + + /* End of Process must set the channel's mask bit */ + dma_state.mask |= (1 << channel); + dma_state.status |= (1 << channel); +} + +/* + * Service pending DRQs + */ +void dmac_service_drqs() +{ + volatile dmac_dma_handler *h; + + for (h = &device_dma_handlers[0]; h->drq != NULL; h++) { + /* Only trigger if the channel has a DRQ set and its channel's + mask bit is 0 */ + if (*h->drq && ((dma_state.mask >> h->channel) & 0x1) == 0) { + h->dma_handler(h->channel, h->service_address); + /* Each handler is responsible for clearing its own DRQ line! */ + if (h->after_dma_callback != NULL) { + h->after_dma_callback(); + } + } + } +} diff --git a/3B2/3b2_dmac.h b/3B2/3b2_dmac.h index 644b7734..cda90371 100644 --- a/3B2/3b2_dmac.h +++ b/3B2/3b2_dmac.h @@ -1,89 +1,89 @@ -/* 3b2_dmac.h: AM9517 DMA Controller - - Copyright (c) 2021-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#ifndef _3B2_DMAC_H_ -#define _3B2_DMAC_H_ - -#include "3b2_defs.h" - -#define DMA_XFER_VERIFY 0 -#define DMA_XFER_WRITE 1 /* Write to memory from device */ -#define DMA_XFER_READ 2 /* Read from memory to device */ - -#define DMA_IF_READ (IFBASE + IF_DATA_REG) - -#define DMA_MODE(C) ((dma_state.channels[(C)].mode >> 6) & 3) -#define DMA_DECR(C) ((dma_state.channels[(C)].mode >> 5) & 1) -#define DMA_AUTOINIT(C) ((dma_state.channels[(C)].mode >> 4) & 1) -#define DMA_XFER(C) ((dma_state.channels[(C)].mode >> 2) & 3) - -typedef struct { - uint8 mode; /* Channel mode */ - uint16 page; /* Memory page */ - uint16 addr; /* Original addr */ - uint16 wcount; /* Original wcount */ - uint16 addr_c; /* Current addr */ - int32 wcount_c; /* Current word-count */ - uint16 ptr; /* Pointer into memory */ -} dma_channel; - -typedef struct { - /* Byte (high/low) flip-flop */ - uint8 bff; - - /* Address and count registers for channels 0-3 */ - dma_channel channels[4]; - - /* DMAC programmable registers */ - uint8 command; - uint8 request; - uint8 mask; - uint8 status; -} DMA_STATE; - -typedef struct { - uint8 channel; - uint32 service_address; - t_bool *drq; - void (*dma_handler)(uint8 channel, uint32 service_address); - void (*after_dma_callback)(); -} dmac_dma_handler; - -/* DMAC */ -t_stat dmac_reset(DEVICE *dptr); -uint32 dmac_read(uint32 pa, size_t size); -void dmac_write(uint32 pa, uint32 val, size_t size); -void dmac_service_drqs(); -void dmac_generic_dma(uint8 channel, uint32 service_address); -uint32 dma_address(uint8 channel, uint32 offset); - -extern DMA_STATE dma_state; - -#endif +/* 3b2_dmac.h: AM9517 DMA Controller + + Copyright (c) 2021-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#ifndef _3B2_DMAC_H_ +#define _3B2_DMAC_H_ + +#include "3b2_defs.h" + +#define DMA_XFER_VERIFY 0 +#define DMA_XFER_WRITE 1 /* Write to memory from device */ +#define DMA_XFER_READ 2 /* Read from memory to device */ + +#define DMA_IF_READ (IFBASE + IF_DATA_REG) + +#define DMA_MODE(C) ((dma_state.channels[(C)].mode >> 6) & 3) +#define DMA_DECR(C) ((dma_state.channels[(C)].mode >> 5) & 1) +#define DMA_AUTOINIT(C) ((dma_state.channels[(C)].mode >> 4) & 1) +#define DMA_XFER(C) ((dma_state.channels[(C)].mode >> 2) & 3) + +typedef struct { + uint8 mode; /* Channel mode */ + uint16 page; /* Memory page */ + uint16 addr; /* Original addr */ + uint16 wcount; /* Original wcount */ + uint16 addr_c; /* Current addr */ + int32 wcount_c; /* Current word-count */ + uint16 ptr; /* Pointer into memory */ +} dma_channel; + +typedef struct { + /* Byte (high/low) flip-flop */ + uint8 bff; + + /* Address and count registers for channels 0-3 */ + dma_channel channels[4]; + + /* DMAC programmable registers */ + uint8 command; + uint8 request; + uint8 mask; + uint8 status; +} DMA_STATE; + +typedef struct { + uint8 channel; + uint32 service_address; + t_bool *drq; + void (*dma_handler)(uint8 channel, uint32 service_address); + void (*after_dma_callback)(); +} dmac_dma_handler; + +/* DMAC */ +t_stat dmac_reset(DEVICE *dptr); +uint32 dmac_read(uint32 pa, size_t size); +void dmac_write(uint32 pa, uint32 val, size_t size); +void dmac_service_drqs(); +void dmac_generic_dma(uint8 channel, uint32 service_address); +uint32 dma_address(uint8 channel, uint32 offset); + +extern DMA_STATE dma_state; + +#endif diff --git a/3B2/3b2_id.c b/3B2/3b2_id.c index d24e03f0..3b83d96a 100644 --- a/3B2/3b2_id.c +++ b/3B2/3b2_id.c @@ -1,996 +1,996 @@ -/* 3b2_d.c: uPD7261 Integrated Disk Controller - - Copyright (c) 2017-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -/* - * This file contains the code for the Integrated Disk (ID) controller - * (based on the uPD7261) and up to two winchester hard disks. - * - * Supported winchester drives are: - * - * SIMH Name ID Cyl Head Sec Byte/Sec Note - * --------- -- ---- ---- --- -------- ---------------------- - * HD30 3 697 5 18 512 CDC Wren 94155-36 - * HD72 5 925 9 18 512 CDC Wren II 94156-86 - * HD72C 8 754 11 18 512 Fujitsu M2243AS - * HD135 11 1224 15 18 512 Maxtor XT1190 - */ - -#include "3b2_id.h" - -#include "sim_disk.h" - -#include "3b2_cpu.h" - -#define ID_SEEK_WAIT 50 -#define ID_SEEK_BASE 700 -#define ID_RECAL_WAIT 6000 -#define ID_RW_WAIT 1000 -#define ID_SUS_WAIT 200 -#define ID_SPEC_WAIT 1250 -#define ID_SIS_WAIT 142 -#define ID_CMD_WAIT 140 - -/* Static function declarations */ -static SIM_INLINE t_lba id_lba(uint16 cyl, uint8 head, uint8 sec); - -/* Data FIFO pointer - Read */ -uint8 id_dpr = 0; -/* Data FIFO pointer - Write */ -uint8 id_dpw = 0; -/* Controller Status Register */ -uint8 id_status = 0; -/* Unit Interrupt Status */ -uint8 id_int_status = 0; -/* Last command received */ -uint8 id_cmd = 0; -/* DMAC request */ -t_bool id_drq = FALSE; -/* 8-byte FIFO */ -uint8 id_data[ID_FIFO_LEN] = {0}; -/* SRQM bit */ -t_bool id_srqm = FALSE; -/* The logical unit number (0-1) */ -uint8 id_unit_num = 0; -/* The physical unit number (0-3) */ -uint8 id_ua = 0; -/* Cylinder the drive is positioned on */ -uint16 id_cyl[ID_NUM_UNITS] = {0}; -/* Ending Track Number (from Specify) */ -uint8 id_etn = 0; -/* Ending Sector Number (from Specify) */ -uint8 id_esn = 0; -/* DTLH word (from Specify) */ -uint8 id_dtlh = 0; -/* Physical sector number */ -uint8 id_psn = 0; -/* Physical head number */ -uint8 id_phn = 0; -/* Logical cylinder number, high byte */ -uint8 id_lcnh = 0; -/* Logical cylinder number, low byte */ -uint8 id_lcnl = 0; -/* Logical head number */ -uint8 id_lhn = 0; -/* Logical sector number */ -uint8 id_lsn = 0; -/* Number of sectors to transfer, decremented after each sector */ -uint8 id_scnt = 0; -/* Whether we are using polling mode or not */ -t_bool id_polling = FALSE; -/* Sector buffer */ -uint8 id_buf[ID_SEC_SIZE]; -/* Buffer pointer */ -size_t id_buf_ptr = 0; - -uint8 id_idfield[ID_IDFIELD_LEN]; -uint8 id_idfield_ptr = 0; - -int8 id_seek_state[ID_NUM_UNITS] = {ID_SEEK_NONE}; - -struct id_dtype { - uint8 hd; /* Number of heads */ - uint32 capac; /* Capacity (in sectors) */ - const char *name; -}; - -static struct id_dtype id_dtab[] = { - ID_DRV(HD30), - ID_DRV(HD72), - ID_DRV(HD72C), - ID_DRV(HD135), - ID_DRV(HD161), - { 0 } -}; - -UNIT id_unit[] = { - { UDATA (&id_unit_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BINK+ID_AUTOSIZE+ - (ID_HD72_DTYPE << ID_V_DTYPE), ID_DSK_SIZE(HD72)), 0, ID0, 0 }, - { UDATA (&id_unit_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BINK+ID_AUTOSIZE+ - (ID_HD72_DTYPE << ID_V_DTYPE), ID_DSK_SIZE(HD72)), 0, ID1, 0 }, - { UDATA (&id_ctlr_svc, 0, 0) }, - { NULL } -}; - -UNIT *id_ctlr_unit = &id_unit[ID_CTLR]; - -/* The currently selected drive number */ -UNIT *id_sel_unit = &id_unit[ID0]; - -REG id_reg[] = { - { HRDATAD(CMD, id_cmd, 8, "Command") }, - { HRDATAD(STAT, id_status, 8, "Status") }, - { BRDATAD(CYL, id_cyl, 8, 8, ID_NUM_UNITS, "Track") }, - { NULL } -}; - -/* HD161 and HD135 are identical; the difference is only in the - * software being run on the emulator. SVR 2.0 will support a maximum - * of 1024 cylinders, so can only format the first 1024 cylinders of - * the HD135. SVR 3.0+ can support all 1224 cylinders of the HD161. */ -MTAB id_mod[] = { - { MTAB_XTD|MTAB_VUN, ID_HD30_DTYPE, NULL, "HD30", - &id_set_type, NULL, NULL, "Set HD30 Disk Type" }, - { MTAB_XTD|MTAB_VUN, ID_HD72_DTYPE, NULL, "HD72", - &id_set_type, NULL, NULL, "Set HD72 Disk Type" }, - { MTAB_XTD|MTAB_VUN, ID_HD72C_DTYPE, NULL, "HD72C", - &id_set_type, NULL, NULL, "Set HD72C Disk Type" }, - { MTAB_XTD|MTAB_VUN, ID_HD135_DTYPE, NULL, "HD135", - &id_set_type, NULL, NULL, "Set HD135 Disk Type" }, - { MTAB_XTD|MTAB_VUN, ID_HD161_DTYPE, NULL, "HD161", - &id_set_type, NULL, NULL, "Set HD161 Disk Type" }, - { MTAB_XTD|MTAB_VUN, 0, "TYPE", NULL, - NULL, &id_show_type, NULL, "Display device type" }, - { ID_AUTOSIZE, ID_AUTOSIZE, "autosize", "AUTOSIZE", - NULL, NULL, NULL, "Set type based on file size at attach" }, - { ID_AUTOSIZE, 0, "noautosize", "NOAUTOSIZE", - NULL, NULL, NULL, "Disable disk autosize on attach" }, - { 0 } -}; - -DEVICE id_dev = { - "IDISK", id_unit, id_reg, id_mod, - ID_NUM_UNITS, 16, 32, 1, 16, 8, - NULL, NULL, &id_reset, - NULL, &id_attach, &id_detach, NULL, - DEV_DEBUG|DEV_DISK|DEV_SECTORS, 0, sys_deb_tab, - NULL, NULL, &id_help, NULL, NULL, - &id_description -}; - -/* Function implementation */ - -#define UPDATE_INT { \ - if ((id_status & (ID_STAT_CEL|ID_STAT_CEH)) || \ - ((id_status & ID_STAT_SRQ) && !id_srqm)) { \ - CPU_SET_INT(INT_DISK); \ - } else { \ - CPU_CLR_INT(INT_DISK); \ - } \ - } - -static SIM_INLINE void id_set_status(uint8 flags) -{ - id_status |= flags; - UPDATE_INT; -} - -static SIM_INLINE void id_clr_status(uint8 flags) -{ - id_status &= ~(flags); - UPDATE_INT; -} - -static SIM_INLINE void id_set_srqm(t_bool state) -{ - id_srqm = state; - UPDATE_INT; -} - -static SIM_INLINE void id_clear_fifo() -{ - id_dpr = 0; - id_dpw = 0; -} - -static SIM_INLINE void id_activate(UNIT *uptr, int32 delay) -{ - sim_activate_abs(uptr, delay); -} - -/* - * Service routine for ID controller. - * - * The simulated HD controller must service Sense Interrupt Status, - * Specify, and Detect Error independent of the operation of either ID - * unit, which may be in the middle of a seek or other operation. - */ -t_stat id_ctlr_svc(UNIT *uptr) -{ - uint8 cmd; - - cmd = uptr->u4; /* The command that caused the activity */ - - id_set_srqm(FALSE); - id_clr_status(ID_STAT_CB); - id_set_status(ID_STAT_CEH); - uptr->u4 = 0; - - switch (cmd) { - case ID_CMD_SIS: - sim_debug(EXECUTE_MSG, &id_dev, - "INTR\t\tCOMPLETING Sense Interrupt Status.\n"); - id_data[0] = id_int_status; - id_int_status = 0; - break; - default: - sim_debug(EXECUTE_MSG, &id_dev, - "INTR\t\tCOMPLETING OTHER COMMAND 0x%x (CONTROLLER)\n", - cmd); - break; - } - - return SCPE_OK; -} - -/* - * Service routine for ID0 and ID1 units. - */ -t_stat id_unit_svc(UNIT *uptr) -{ - uint8 unit, other, cmd; - - unit = uptr->u3; /* The unit number that needs an interrupt */ - cmd = uptr->u4; /* The command that caused the activity */ - other = unit ^ 1; /* The number of the other unit */ - - /* If the other unit is active, we cannot interrupt, so we delay - * here */ - if (id_unit[other].u4 == ID_CMD_RDATA || - id_unit[other].u4 == ID_CMD_WDATA) { - id_activate(uptr, 1000); - return SCPE_OK; - } - - id_set_srqm(FALSE); - id_clr_status(ID_STAT_CB); - /* Note that we don't set CEH, in case this is a SEEK/RECAL ID_SEEK_1 */ - - switch (cmd) { - case ID_CMD_SEEK: /* fall-through */ - case ID_CMD_RECAL: - /* In POLLING mode, SEEK and RECAL actually interrupt twice. - * - * 1. Immediately after the correct number of stepping pulses - * have been issued (SRQ is not set) - * - * 2. After the drive has completed seeking and is ready - * for a new command (SRQ is set) - */ - if (id_polling) { - switch (id_seek_state[unit]) { - case ID_SEEK_0: - id_set_status(ID_STAT_CEH); - sim_debug(EXECUTE_MSG, &id_dev, - "INTR\t\tCOMPLETING Recal/Seek SEEK_0 UNIT %d\n", - unit); - id_seek_state[unit] = ID_SEEK_1; - id_activate(uptr, 8000); /* TODO: Correct Delay based on steps */ - break; - case ID_SEEK_1: - sim_debug(EXECUTE_MSG, &id_dev, - "INTR\t\tCOMPLETING Recal/Seek SEEK_1 UNIT %d\n", - unit); - id_seek_state[unit] = ID_SEEK_NONE; - id_set_status(ID_STAT_SRQ); - uptr->u4 = 0; /* Only clear out the command on a SEEK_1, never a SEEK_0 */ - if (uptr->flags & UNIT_ATT) { - id_int_status |= (ID_IST_SEN|unit); - } else { - id_int_status |= (ID_IST_NR|unit); - } - break; - default: - sim_debug(EXECUTE_MSG, &id_dev, - "INTR\t\tERROR, NOT SEEK_0 OR SEEK_1, UNIT %d\n", - unit); - break; - } - } else { - sim_debug(EXECUTE_MSG, &id_dev, - "INTR\t\tCOMPLETING NON-POLLING Recal/Seek UNIT %d\n", - unit); - id_set_status(ID_STAT_CEH); - uptr->u4 = 0; - if (uptr->flags & UNIT_ATT) { - id_int_status |= (ID_IST_SEN|unit); - } else { - id_int_status |= (ID_IST_NR|unit); - } - } - - break; - case ID_CMD_SUS: - sim_debug(EXECUTE_MSG, &id_dev, - "INTR\t\tCOMPLETING Sense Unit Status UNIT %d\n", - unit); - id_set_status(ID_STAT_CEH); - uptr->u4 = 0; - if ((uptr->flags & UNIT_ATT) == 0) { - /* If no HD is attached, SUS puts 0x00 into the data - buffer */ - id_data[0] = 0; - } else { - /* Put Unit Status into byte 0 */ - id_data[0] = (ID_UST_DSEL|ID_UST_SCL|ID_UST_RDY); - if (id_cyl[unit] == 0) { - id_data[0] |= ID_UST_TK0; - } - } - break; - default: - sim_debug(EXECUTE_MSG, &id_dev, - "INTR\t\tCOMPLETING OTHER COMMAND 0x%x UNIT %d\n", - cmd, unit); - id_set_status(ID_STAT_CEH); - uptr->u4 = 0; - break; - } - - return SCPE_OK; -} - -t_stat id_set_type(UNIT *uptr, int32 val, CONST char *cptr, void *desc) -{ - if (val < 0 || val > ID_MAX_DTYPE) { - return SCPE_ARG; - } - - if (uptr->flags & UNIT_ATT) { - return SCPE_ALATT; - } - - uptr->flags = (uptr->flags & ~ID_DTYPE) | (val << ID_V_DTYPE); - uptr->capac = (t_addr)id_dtab[val].capac; - - return SCPE_OK; -} - -t_stat id_show_type (FILE *st, UNIT *uptr, int32 val, CONST void *desc) -{ - fprintf (st, "%s", id_dtab[ID_GET_DTYPE(uptr->flags)].name); - return SCPE_OK; -} - -t_stat id_reset(DEVICE *dptr) -{ - id_clear_fifo(); - return SCPE_OK; -} - -t_stat id_attach(UNIT *uptr, CONST char *cptr) -{ - static const char *drives[] = {"HD30", "HD72", "HD72C", "HD135", "HD161", NULL}; - - return sim_disk_attach_ex(uptr, cptr, 512, 1, TRUE, 0, id_dtab[ID_GET_DTYPE(uptr->flags)].name, - 0, 0, (uptr->flags & ID_AUTOSIZE) ? drives : NULL); -} - -t_stat id_detach(UNIT *uptr) -{ - return sim_disk_detach(uptr); -} - -/* Return the logical block address of the given sector */ -static t_lba id_lba(uint16 cyl, uint8 head, uint8 sec) -{ - uint8 dtype; - - dtype = ID_GET_DTYPE(id_sel_unit->flags); - - return((ID_SEC_CNT * id_dtab[dtype].hd * cyl) + - (ID_SEC_CNT * head) + - sec); -} - -/* At the end of each sector read or write, we update the FIFO - * with the correct return parameters. */ -static void SIM_INLINE id_end_rw(uint8 est) -{ - id_clear_fifo(); - id_data[0] = est; - id_data[1] = id_phn; - id_data[2] = ~(id_lcnh); - id_data[3] = id_lcnl; - id_data[4] = id_lhn; - id_data[5] = id_lsn; - id_data[6] = id_scnt; -} - -/* The controller wraps id_lsn, id_lhn, and id_lcnl on each sector - * read, so that they point to the next C/H/S */ -static void SIM_INLINE id_update_chs() -{ - if (id_lsn++ >= id_esn) { - id_lsn = 0; - if (id_lhn++ >= id_etn) { - id_lhn = 0; - if (id_lcnl == 0xff) { - id_lcnl = 0; - id_lcnh++; - } else { - id_lcnl++; - } - } - } -} - -uint32 id_read(uint32 pa, size_t size) -{ - uint8 reg; - uint16 cyl; - t_lba lba; - uint32 data; - t_seccnt sectsread; - - reg = (uint8) (pa - IDBASE); - - switch(reg) { - case ID_DATA_REG: /* Data Buffer Register */ - /* If we're in a DMA transfer, we need to be reading data from - * the disk buffer. Otherwise, we're reading from the FIFO. */ - - if (id_drq) { - /* If the drive isn't attached, there's really nothing we - can do. */ - if ((id_sel_unit->flags & UNIT_ATT) == 0) { - id_end_rw(ID_EST_NR); - return 0; - } - - /* We could be in one of these commands: - * - Read Data - * - Read ID - */ - - if (CMD_NUM == ID_CMD_RDATA) { - /* If we're still in DRQ but we've read all our sectors, - * that's an error state. */ - if (id_scnt == 0) { - sim_debug(READ_MSG, &id_dev, - "ERROR\tid_scnt = 0 but still in dma\n"); - id_end_rw(ID_EST_OVR); - return 0; - } - - /* If the disk buffer is empty, fill it. */ - if (id_buf_ptr == 0 || id_buf_ptr >= ID_SEC_SIZE) { - /* It's time to read a new sector into our sector buf */ - id_buf_ptr = 0; - cyl = (uint16) (((uint16)id_lcnh << 8)|(uint16)id_lcnl); - id_cyl[id_unit_num] = cyl; - lba = id_lba(cyl, id_lhn, id_lsn); - if (sim_disk_rdsect(id_sel_unit, lba, id_buf, §sread, 1) == SCPE_OK) { - if (sectsread !=1) { - sim_debug(READ_MSG, &id_dev, - "ERROR: ASKED TO READ ONE SECTOR, READ: %d\n", - sectsread); - } - id_update_chs(); - } else { - /* Uh-oh! */ - sim_debug(READ_MSG, &id_dev, - "RDATA READ ERROR. Failure from sim_disk_rdsect!\n"); - id_end_rw(ID_EST_DER); - return 0; - } - } - - data = id_buf[id_buf_ptr++]; - sim_debug(READ_MSG, &id_dev, "DATA\t%02x\n", data); - - /* Done with this current sector, update id_scnt */ - if (id_buf_ptr >= ID_SEC_SIZE) { - if (--id_scnt == 0) { - id_end_rw(0); - } - } - } else if (CMD_NUM == ID_CMD_RID) { - /* We have to return the ID bytes for the current C/H/S */ - if (id_idfield_ptr == 0 || id_idfield_ptr >= ID_IDFIELD_LEN) { - id_idfield[0] = ~(id_lcnh); - id_idfield[1] = id_lcnl; - id_idfield[2] = id_lhn; - id_idfield[3] = id_lsn; - id_idfield_ptr = 0; - } - - data = id_idfield[id_idfield_ptr++]; - sim_debug(READ_MSG, &id_dev, - "ID DATA\t%02x\n", - data); - - if (id_idfield_ptr >= ID_IDFIELD_LEN) { - if (id_scnt-- > 0) { - /* Another sector to ID */ - id_idfield_ptr = 0; - } else { - /* All done, set return codes */ - id_clear_fifo(); - id_data[0] = 0; - id_data[1] = id_scnt; - } - } - } else { - /* cmd not Read Data or Read ID */ - stop_reason = STOP_ERR; - return 0; - } - - return data; - } else { - if (id_dpr < ID_FIFO_LEN) { - sim_debug(READ_MSG, &id_dev, - "DATA\t%02x\n", - id_data[id_dpr]); - return id_data[id_dpr++]; - } else { - sim_debug(READ_MSG, &id_dev, - "ERROR\tFIFO OVERRUN\n"); - return 0; - } - } - - break; - case ID_CMD_STAT_REG: /* Status Register */ - sim_debug(READ_MSG, &id_dev, - "STATUS\t%02x\n", - id_status|id_drq); - return id_status|(id_drq ? 1u : 0); - } - - sim_debug(READ_MSG, &id_dev, - "Read of unsuported register %x\n", - id_status); - - return 0; -} - -void id_write(uint32 pa, uint32 val, size_t size) -{ - uint8 reg; - uint16 cyl; - t_lba lba; - t_seccnt sectswritten; - - reg = (uint8) (pa - IDBASE); - - switch(reg) { - case ID_DATA_REG: - /* If we're in a DMA transfer, we need to be writing data to - * the disk buffer. Otherwise, we're writing to the FIFO. */ - - if (id_drq) { - /* If we're still in DRQ but we've written all our sectors, - * that's an error state. */ - if (id_scnt == 0) { - sim_debug(WRITE_MSG, &id_dev, - "ERROR\tid_scnt = 0 but still in dma\n"); - id_end_rw(ID_EST_OVR); - return; - } - - /* Write to the disk buffer */ - if (id_buf_ptr < ID_SEC_SIZE) { - id_buf[id_buf_ptr++] = (uint8)(val & 0xff); - sim_debug(WRITE_MSG, &id_dev, - "DATA\t%02x\n", - (uint8)(val & 0xff)); - } else { - sim_debug(WRITE_MSG, &id_dev, - "ERROR\tWDATA OVERRUN\n"); - id_end_rw(ID_EST_OVR); - return; - } - - /* If we've hit the end of a sector, flush it */ - if (id_buf_ptr >= ID_SEC_SIZE) { - /* It's time to start the next sector, and flush the old. */ - id_buf_ptr = 0; - cyl = (uint16) (((uint16) id_lcnh << 8)|(uint16)id_lcnl); - id_cyl[id_unit_num] = cyl; - lba = id_lba(cyl, id_lhn, id_lsn); - if (sim_disk_wrsect(id_sel_unit, lba, id_buf, §swritten, 1) == SCPE_OK) { - if (sectswritten !=1) { - sim_debug(WRITE_MSG, &id_dev, - "ERROR: ASKED TO WRITE ONE SECTOR, WROTE: %d\n", - sectswritten); - } - id_update_chs(); - if (--id_scnt == 0) { - id_end_rw(0); - } - } else { - /* Uh-oh! */ - sim_debug(WRITE_MSG, &id_dev, - "ERROR\tWDATA WRITE ERROR. lba=%04x\n", - lba); - id_end_rw(ID_EST_DER); - return; - } - } - return; - } else { - sim_debug(WRITE_MSG, &id_dev, - "DATA\t%02x\n", - val); - if (id_dpw < ID_FIFO_LEN) { - id_data[id_dpw++] = (uint8) val; - } else { - sim_debug(WRITE_MSG, &id_dev, - "ERROR\tFIFO OVERRUN\n"); - } - } - return; - case ID_CMD_STAT_REG: - id_handle_command((uint8) val); - return; - default: - return; - } -} - -void id_handle_command(uint8 val) -{ - uint8 cmd, aux_cmd, sec, pattern; - uint16 cyl; - uint32 time; - t_lba lba; - - /* Reset the FIFO pointer */ - id_clear_fifo(); - - /* Is this an aux command or a full command? */ - if ((val & 0xf0) == 0) { - aux_cmd = val & 0x0f; - - if (aux_cmd & ID_AUX_CLCE) { - sim_debug(WRITE_MSG, &id_dev, - "COMMAND\t%02x\tAUX:CLCE\n", - val); - id_clr_status(ID_STAT_CEH|ID_STAT_CEL); - } - - if (aux_cmd & ID_AUX_HSRQ) { - sim_debug(WRITE_MSG, &id_dev, - "COMMAND\t%02x\tAUX:HSRQ\n", - val); - id_set_srqm(TRUE); - } - - if (aux_cmd & ID_AUX_CLB) { - sim_debug(WRITE_MSG, &id_dev, - "COMMAND\t%02x\tAUX:CLBUF\n", - val); - id_clear_fifo(); - } - - if (aux_cmd & ID_AUX_RST) { - sim_debug(WRITE_MSG, &id_dev, - "COMMAND\t%02x\tAUX:RESET\n", - val); - id_clear_fifo(); - sim_cancel(id_sel_unit); - sim_cancel(id_ctlr_unit); - id_status = 0; - id_srqm = FALSE; - UPDATE_INT; - } - - /* Just return early */ - return; - } - - /* If the controller is busy and this isn't an AUX command, do - * nothing */ - if (id_status & ID_STAT_CB) { - sim_debug(EXECUTE_MSG, &id_dev, - "!!! Controller Busy. Skipping command byte %02x\n", - val); - return; - } - - /* A full command always resets CEH and CEL */ - id_clr_status(ID_STAT_CEH|ID_STAT_CEL); - - /* Save the full command byte */ - id_cmd = val; - cmd = (id_cmd >> 4) & 0xf; - - /* Now that we know it's not an aux command, we can get the unit - * number. Note that we don't update the unit in the case of three - * special commands. */ - if (cmd != ID_CMD_SIS && cmd != ID_CMD_SPEC && cmd != ID_CMD_DERR) { - if ((id_cmd & 3) != id_ua) { - id_unit_num = id_cmd & 1; - id_ua = id_cmd & 3; - id_sel_unit = &id_unit[id_unit_num]; - } - } - - /* TODO: Fix this hack */ - if (cmd == ID_CMD_SIS || cmd == ID_CMD_SPEC || cmd == ID_CMD_DERR) { - id_ctlr_unit->u4 = cmd; - } else { - id_sel_unit->u4 = cmd; - } - - id_set_status(ID_STAT_CB); - - switch(cmd) { - case ID_CMD_SIS: - sim_debug(WRITE_MSG, &id_dev, - "COMMAND\t%02x\tSense Int. Status\n", - val); - id_clr_status(ID_STAT_SRQ); /* SIS immediately de-asserts SRQ */ - id_activate(id_ctlr_unit, ID_SIS_WAIT); - break; - case ID_CMD_SPEC: - sim_debug(WRITE_MSG, &id_dev, - "COMMAND\t%02x\tSpecify - ETN=%02x ESN=%02x\n", - val, id_data[3], id_data[4]); - id_dtlh = id_data[1]; - id_etn = id_data[3]; - id_esn = id_data[4]; - id_polling = (id_dtlh & ID_DTLH_POLL) == 0; - id_activate(id_ctlr_unit, ID_SPEC_WAIT); - break; - case ID_CMD_SUS: - sim_debug(WRITE_MSG, &id_dev, - "COMMAND\t%02x\tSense Unit Status - %d\n", - val, id_ua); - id_activate(id_sel_unit, ID_SUS_WAIT); - break; - case ID_CMD_DERR: - sim_debug(WRITE_MSG, &id_dev, - "COMMAND\t%02x\tDetect Error\n", - val); - id_activate(id_ctlr_unit, ID_CMD_WAIT); - break; - case ID_CMD_RECAL: - time = id_cyl[id_unit_num]; - id_cyl[id_unit_num] = 0; - id_seek_state[id_unit_num] = ID_SEEK_0; - if (id_polling) { - sim_debug(WRITE_MSG, &id_dev, - "COMMAND\t%02x\tRecalibrate - %d - POLLING\n", - val, id_ua); - id_activate(id_sel_unit, 1000); - } else { - sim_debug(WRITE_MSG, &id_dev, - "COMMAND\t%02x\tRecalibrate - %d - NORMAL\n", - val, id_ua); - id_activate(id_sel_unit, (ID_RECAL_WAIT + (time * ID_SEEK_WAIT))); - } - break; - case ID_CMD_SEEK: - id_lcnh = id_data[0]; - id_lcnl = id_data[1]; - cyl = id_lcnh << 8 | id_lcnl; - time = (uint32) abs(id_cyl[id_unit_num] - cyl); - id_cyl[id_unit_num] = cyl; - id_seek_state[id_unit_num] = ID_SEEK_0; - - if (id_polling) { - sim_debug(WRITE_MSG, &id_dev, - "COMMAND\t%02x\tSeek - %d - POLLING\n", - val, id_ua); - id_activate(id_sel_unit, 4000); - } else { - sim_debug(WRITE_MSG, &id_dev, - "COMMAND\t%02x\tSeek - %d - NORMAL\n", - val, id_ua); - id_activate(id_sel_unit, ID_SEEK_BASE + (time * ID_SEEK_WAIT)); - } - break; - case ID_CMD_FMT: - sim_debug(WRITE_MSG, &id_dev, - "COMMAND\t%02x\tFormat - %d\n", - val, id_ua); - - id_phn = id_data[0]; - id_scnt = id_data[1]; - pattern = id_data[2]; - - /* Format scnt sectors with the given pattern, if attached */ - if (id_sel_unit->flags & UNIT_ATT) { - /* Formatting soft-sectored disks always begins at sector 0 */ - sec = 0; - - while (id_scnt-- > 0) { - /* Write one sector of pattern */ - for (id_buf_ptr = 0; id_buf_ptr < ID_SEC_SIZE; id_buf_ptr++) { - id_buf[id_buf_ptr] = pattern; - } - lba = id_lba(id_cyl[id_unit_num], id_phn, sec++); - if (sim_disk_wrsect(id_sel_unit, lba, id_buf, NULL, 1) == SCPE_OK) { - sim_debug(EXECUTE_MSG, &id_dev, - "FORMAT: PHN=%d SCNT=%d PAT=%02x LBA=%04x\n", - id_phn, id_scnt, pattern, lba); - } else { - sim_debug(EXECUTE_MSG, &id_dev, - "FORMAT FAILED! PHN=%d SCNT=%d PAT=%02x LBA=%04x\n", - id_phn, id_scnt, pattern, lba); - break; - } - } - - id_data[0] = 0; - } else { - /* Not attached */ - id_data[0] = ID_EST_NR; - } - - id_data[1] = id_scnt; - - id_activate(id_sel_unit, ID_CMD_WAIT); - break; - case ID_CMD_VID: - sim_debug(WRITE_MSG, &id_dev, - "COMMAND\t%02x\tVerify ID - %d\n", - val, id_ua); - id_data[0] = 0; - id_data[1] = 0x05; /* What do we put here? */ - id_activate(id_sel_unit, ID_CMD_WAIT); - break; - case ID_CMD_RID: - sim_debug(WRITE_MSG, &id_dev, - "COMMAND\t%02x\tRead ID - %d\n", - val, id_ua); - if (id_sel_unit->flags & UNIT_ATT) { - id_drq = TRUE; - - /* Grab our arguments */ - id_phn = id_data[0]; - id_scnt = id_data[1]; - - /* Compute logical values used by ID verification */ - id_lhn = id_phn; - id_lsn = 0; - } else { - sim_debug(EXECUTE_MSG, &id_dev, - "UNIT %d NOT ATTACHED, CANNOT READ ID.\n", - id_ua); - } - id_activate(id_sel_unit, ID_CMD_WAIT); - break; - case ID_CMD_RDIAG: - sim_debug(WRITE_MSG, &id_dev, - "COMMAND\t%02x\tRead Diag - %d\n", - val, id_ua); - id_activate(id_sel_unit, ID_CMD_WAIT); - break; - case ID_CMD_RDATA: - sim_debug(WRITE_MSG, &id_dev, - "COMMAND\t%02x\tRead Data - %d\n", - val, id_ua); - if (id_sel_unit->flags & UNIT_ATT) { - id_drq = TRUE; - id_buf_ptr = 0; - - /* Grab our arguments */ - id_phn = id_data[0]; - id_lcnh = ~(id_data[1]); - id_lcnl = id_data[2]; - id_lhn = id_data[3]; - id_lsn = id_data[4]; - id_scnt = id_data[5]; - } else { - sim_debug(EXECUTE_MSG, &id_dev, - "UNIT %d NOT ATTACHED, CANNOT READ DATA.\n", - id_ua); - } - id_activate(id_sel_unit, ID_RW_WAIT); - break; - case ID_CMD_CHECK: - sim_debug(WRITE_MSG, &id_dev, - "COMMAND\t%02x\tCheck - %d\n", - val, id_ua); - id_activate(id_sel_unit, ID_CMD_WAIT); - break; - case ID_CMD_SCAN: - sim_debug(WRITE_MSG, &id_dev, - "COMMAND\t%02x\tScan - %d\n", - val, id_ua); - id_activate(id_sel_unit, ID_CMD_WAIT); - break; - case ID_CMD_VDATA: - sim_debug(WRITE_MSG, &id_dev, - "COMMAND\t%02x\tVerify Data - %d\n", - val, id_ua); - id_activate(id_sel_unit, ID_CMD_WAIT); - break; - case ID_CMD_WDATA: - sim_debug(WRITE_MSG, &id_dev, - "COMMAND\t%02x\tWrite Data - %d\n", - val, id_ua); - if (id_sel_unit->flags & UNIT_ATT) { - id_drq = TRUE; - id_buf_ptr = 0; - - /* Grab our arguments */ - id_phn = id_data[0]; - id_lcnh = ~(id_data[1]); - id_lcnl = id_data[2]; - id_lhn = id_data[3]; - id_lsn = id_data[4]; - id_scnt = id_data[5]; - } else { - sim_debug(EXECUTE_MSG, &id_dev, - "UNIT %d NOT ATTACHED, CANNOT WRITE.\n", - id_ua); - } - id_activate(id_sel_unit, ID_RW_WAIT); - break; - } -} - -void id_after_dma() -{ - id_clr_status(ID_STAT_DRQ); - id_drq = FALSE; -} - -CONST char *id_description(DEVICE *dptr) -{ - return "Integrated Hard Disk"; -} - -t_stat id_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) -{ - fprintf(st, "Integrated Hard Disk (IDISK)\n\n"); - fprintf(st, "The IDISK device implements the integrated MFM hard disk of the\n"); - fprintf(st, "3B2/400. Up to two drives are supported on a single controller.\n\n"); - fprintf(st, "Supported device types are:\n\n"); - fprintf(st, " Name Size ID Cyl Head Sec Byte/Sec Description\n"); - fprintf(st, " ---- -------- -- ---- ---- --- -------- ----------------------\n"); - fprintf(st, " HD30 30.6 MB 3 697 5 18 512 CDC Wren 94155-36\n"); - fprintf(st, " HD72 73.2 MB 5 925 9 18 512 CDC Wren II 94156-86\n"); - fprintf(st, " HD72C 72.9 MB 8 754 11 18 512 Fujitsu M2243AS\n"); - fprintf(st, " HD135 135.0 MB 11 1024 15 18 512 Maxtor XT1190 (SVR2)\n"); - fprintf(st, " HD161 161.4 MB 11 1224 15 18 512 Maxtor XT1190 (SVR3+)\n\n"); - fprintf(st, "The drive ID and geometry values are used when low-level formatting a\n"); - fprintf(st, "drive using the AT&T 'idtools' utility.\n"); - - fprint_set_help(st, dptr); - fprint_show_help(st, dptr); - fprint_reg_help(st, dptr); - - return SCPE_OK; -} +/* 3b2_d.c: uPD7261 Integrated Disk Controller + + Copyright (c) 2017-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +/* + * This file contains the code for the Integrated Disk (ID) controller + * (based on the uPD7261) and up to two winchester hard disks. + * + * Supported winchester drives are: + * + * SIMH Name ID Cyl Head Sec Byte/Sec Note + * --------- -- ---- ---- --- -------- ---------------------- + * HD30 3 697 5 18 512 CDC Wren 94155-36 + * HD72 5 925 9 18 512 CDC Wren II 94156-86 + * HD72C 8 754 11 18 512 Fujitsu M2243AS + * HD135 11 1224 15 18 512 Maxtor XT1190 + */ + +#include "3b2_id.h" + +#include "sim_disk.h" + +#include "3b2_cpu.h" + +#define ID_SEEK_WAIT 50 +#define ID_SEEK_BASE 700 +#define ID_RECAL_WAIT 6000 +#define ID_RW_WAIT 1000 +#define ID_SUS_WAIT 200 +#define ID_SPEC_WAIT 1250 +#define ID_SIS_WAIT 142 +#define ID_CMD_WAIT 140 + +/* Static function declarations */ +static SIM_INLINE t_lba id_lba(uint16 cyl, uint8 head, uint8 sec); + +/* Data FIFO pointer - Read */ +uint8 id_dpr = 0; +/* Data FIFO pointer - Write */ +uint8 id_dpw = 0; +/* Controller Status Register */ +uint8 id_status = 0; +/* Unit Interrupt Status */ +uint8 id_int_status = 0; +/* Last command received */ +uint8 id_cmd = 0; +/* DMAC request */ +t_bool id_drq = FALSE; +/* 8-byte FIFO */ +uint8 id_data[ID_FIFO_LEN] = {0}; +/* SRQM bit */ +t_bool id_srqm = FALSE; +/* The logical unit number (0-1) */ +uint8 id_unit_num = 0; +/* The physical unit number (0-3) */ +uint8 id_ua = 0; +/* Cylinder the drive is positioned on */ +uint16 id_cyl[ID_NUM_UNITS] = {0}; +/* Ending Track Number (from Specify) */ +uint8 id_etn = 0; +/* Ending Sector Number (from Specify) */ +uint8 id_esn = 0; +/* DTLH word (from Specify) */ +uint8 id_dtlh = 0; +/* Physical sector number */ +uint8 id_psn = 0; +/* Physical head number */ +uint8 id_phn = 0; +/* Logical cylinder number, high byte */ +uint8 id_lcnh = 0; +/* Logical cylinder number, low byte */ +uint8 id_lcnl = 0; +/* Logical head number */ +uint8 id_lhn = 0; +/* Logical sector number */ +uint8 id_lsn = 0; +/* Number of sectors to transfer, decremented after each sector */ +uint8 id_scnt = 0; +/* Whether we are using polling mode or not */ +t_bool id_polling = FALSE; +/* Sector buffer */ +uint8 id_buf[ID_SEC_SIZE]; +/* Buffer pointer */ +size_t id_buf_ptr = 0; + +uint8 id_idfield[ID_IDFIELD_LEN]; +uint8 id_idfield_ptr = 0; + +int8 id_seek_state[ID_NUM_UNITS] = {ID_SEEK_NONE}; + +struct id_dtype { + uint8 hd; /* Number of heads */ + uint32 capac; /* Capacity (in sectors) */ + const char *name; +}; + +static struct id_dtype id_dtab[] = { + ID_DRV(HD30), + ID_DRV(HD72), + ID_DRV(HD72C), + ID_DRV(HD135), + ID_DRV(HD161), + { 0 } +}; + +UNIT id_unit[] = { + { UDATA (&id_unit_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BINK+ID_AUTOSIZE+ + (ID_HD72_DTYPE << ID_V_DTYPE), ID_DSK_SIZE(HD72)), 0, ID0, 0 }, + { UDATA (&id_unit_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BINK+ID_AUTOSIZE+ + (ID_HD72_DTYPE << ID_V_DTYPE), ID_DSK_SIZE(HD72)), 0, ID1, 0 }, + { UDATA (&id_ctlr_svc, 0, 0) }, + { NULL } +}; + +UNIT *id_ctlr_unit = &id_unit[ID_CTLR]; + +/* The currently selected drive number */ +UNIT *id_sel_unit = &id_unit[ID0]; + +REG id_reg[] = { + { HRDATAD(CMD, id_cmd, 8, "Command") }, + { HRDATAD(STAT, id_status, 8, "Status") }, + { BRDATAD(CYL, id_cyl, 8, 8, ID_NUM_UNITS, "Track") }, + { NULL } +}; + +/* HD161 and HD135 are identical; the difference is only in the + * software being run on the emulator. SVR 2.0 will support a maximum + * of 1024 cylinders, so can only format the first 1024 cylinders of + * the HD135. SVR 3.0+ can support all 1224 cylinders of the HD161. */ +MTAB id_mod[] = { + { MTAB_XTD|MTAB_VUN, ID_HD30_DTYPE, NULL, "HD30", + &id_set_type, NULL, NULL, "Set HD30 Disk Type" }, + { MTAB_XTD|MTAB_VUN, ID_HD72_DTYPE, NULL, "HD72", + &id_set_type, NULL, NULL, "Set HD72 Disk Type" }, + { MTAB_XTD|MTAB_VUN, ID_HD72C_DTYPE, NULL, "HD72C", + &id_set_type, NULL, NULL, "Set HD72C Disk Type" }, + { MTAB_XTD|MTAB_VUN, ID_HD135_DTYPE, NULL, "HD135", + &id_set_type, NULL, NULL, "Set HD135 Disk Type" }, + { MTAB_XTD|MTAB_VUN, ID_HD161_DTYPE, NULL, "HD161", + &id_set_type, NULL, NULL, "Set HD161 Disk Type" }, + { MTAB_XTD|MTAB_VUN, 0, "TYPE", NULL, + NULL, &id_show_type, NULL, "Display device type" }, + { ID_AUTOSIZE, ID_AUTOSIZE, "autosize", "AUTOSIZE", + NULL, NULL, NULL, "Set type based on file size at attach" }, + { ID_AUTOSIZE, 0, "noautosize", "NOAUTOSIZE", + NULL, NULL, NULL, "Disable disk autosize on attach" }, + { 0 } +}; + +DEVICE id_dev = { + "IDISK", id_unit, id_reg, id_mod, + ID_NUM_UNITS, 16, 32, 1, 16, 8, + NULL, NULL, &id_reset, + NULL, &id_attach, &id_detach, NULL, + DEV_DEBUG|DEV_DISK|DEV_SECTORS, 0, sys_deb_tab, + NULL, NULL, &id_help, NULL, NULL, + &id_description +}; + +/* Function implementation */ + +#define UPDATE_INT { \ + if ((id_status & (ID_STAT_CEL|ID_STAT_CEH)) || \ + ((id_status & ID_STAT_SRQ) && !id_srqm)) { \ + CPU_SET_INT(INT_DISK); \ + } else { \ + CPU_CLR_INT(INT_DISK); \ + } \ + } + +static SIM_INLINE void id_set_status(uint8 flags) +{ + id_status |= flags; + UPDATE_INT; +} + +static SIM_INLINE void id_clr_status(uint8 flags) +{ + id_status &= ~(flags); + UPDATE_INT; +} + +static SIM_INLINE void id_set_srqm(t_bool state) +{ + id_srqm = state; + UPDATE_INT; +} + +static SIM_INLINE void id_clear_fifo() +{ + id_dpr = 0; + id_dpw = 0; +} + +static SIM_INLINE void id_activate(UNIT *uptr, int32 delay) +{ + sim_activate_abs(uptr, delay); +} + +/* + * Service routine for ID controller. + * + * The simulated HD controller must service Sense Interrupt Status, + * Specify, and Detect Error independent of the operation of either ID + * unit, which may be in the middle of a seek or other operation. + */ +t_stat id_ctlr_svc(UNIT *uptr) +{ + uint8 cmd; + + cmd = uptr->u4; /* The command that caused the activity */ + + id_set_srqm(FALSE); + id_clr_status(ID_STAT_CB); + id_set_status(ID_STAT_CEH); + uptr->u4 = 0; + + switch (cmd) { + case ID_CMD_SIS: + sim_debug(EXECUTE_MSG, &id_dev, + "INTR\t\tCOMPLETING Sense Interrupt Status.\n"); + id_data[0] = id_int_status; + id_int_status = 0; + break; + default: + sim_debug(EXECUTE_MSG, &id_dev, + "INTR\t\tCOMPLETING OTHER COMMAND 0x%x (CONTROLLER)\n", + cmd); + break; + } + + return SCPE_OK; +} + +/* + * Service routine for ID0 and ID1 units. + */ +t_stat id_unit_svc(UNIT *uptr) +{ + uint8 unit, other, cmd; + + unit = uptr->u3; /* The unit number that needs an interrupt */ + cmd = uptr->u4; /* The command that caused the activity */ + other = unit ^ 1; /* The number of the other unit */ + + /* If the other unit is active, we cannot interrupt, so we delay + * here */ + if (id_unit[other].u4 == ID_CMD_RDATA || + id_unit[other].u4 == ID_CMD_WDATA) { + id_activate(uptr, 1000); + return SCPE_OK; + } + + id_set_srqm(FALSE); + id_clr_status(ID_STAT_CB); + /* Note that we don't set CEH, in case this is a SEEK/RECAL ID_SEEK_1 */ + + switch (cmd) { + case ID_CMD_SEEK: /* fall-through */ + case ID_CMD_RECAL: + /* In POLLING mode, SEEK and RECAL actually interrupt twice. + * + * 1. Immediately after the correct number of stepping pulses + * have been issued (SRQ is not set) + * + * 2. After the drive has completed seeking and is ready + * for a new command (SRQ is set) + */ + if (id_polling) { + switch (id_seek_state[unit]) { + case ID_SEEK_0: + id_set_status(ID_STAT_CEH); + sim_debug(EXECUTE_MSG, &id_dev, + "INTR\t\tCOMPLETING Recal/Seek SEEK_0 UNIT %d\n", + unit); + id_seek_state[unit] = ID_SEEK_1; + id_activate(uptr, 8000); /* TODO: Correct Delay based on steps */ + break; + case ID_SEEK_1: + sim_debug(EXECUTE_MSG, &id_dev, + "INTR\t\tCOMPLETING Recal/Seek SEEK_1 UNIT %d\n", + unit); + id_seek_state[unit] = ID_SEEK_NONE; + id_set_status(ID_STAT_SRQ); + uptr->u4 = 0; /* Only clear out the command on a SEEK_1, never a SEEK_0 */ + if (uptr->flags & UNIT_ATT) { + id_int_status |= (ID_IST_SEN|unit); + } else { + id_int_status |= (ID_IST_NR|unit); + } + break; + default: + sim_debug(EXECUTE_MSG, &id_dev, + "INTR\t\tERROR, NOT SEEK_0 OR SEEK_1, UNIT %d\n", + unit); + break; + } + } else { + sim_debug(EXECUTE_MSG, &id_dev, + "INTR\t\tCOMPLETING NON-POLLING Recal/Seek UNIT %d\n", + unit); + id_set_status(ID_STAT_CEH); + uptr->u4 = 0; + if (uptr->flags & UNIT_ATT) { + id_int_status |= (ID_IST_SEN|unit); + } else { + id_int_status |= (ID_IST_NR|unit); + } + } + + break; + case ID_CMD_SUS: + sim_debug(EXECUTE_MSG, &id_dev, + "INTR\t\tCOMPLETING Sense Unit Status UNIT %d\n", + unit); + id_set_status(ID_STAT_CEH); + uptr->u4 = 0; + if ((uptr->flags & UNIT_ATT) == 0) { + /* If no HD is attached, SUS puts 0x00 into the data + buffer */ + id_data[0] = 0; + } else { + /* Put Unit Status into byte 0 */ + id_data[0] = (ID_UST_DSEL|ID_UST_SCL|ID_UST_RDY); + if (id_cyl[unit] == 0) { + id_data[0] |= ID_UST_TK0; + } + } + break; + default: + sim_debug(EXECUTE_MSG, &id_dev, + "INTR\t\tCOMPLETING OTHER COMMAND 0x%x UNIT %d\n", + cmd, unit); + id_set_status(ID_STAT_CEH); + uptr->u4 = 0; + break; + } + + return SCPE_OK; +} + +t_stat id_set_type(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + if (val < 0 || val > ID_MAX_DTYPE) { + return SCPE_ARG; + } + + if (uptr->flags & UNIT_ATT) { + return SCPE_ALATT; + } + + uptr->flags = (uptr->flags & ~ID_DTYPE) | (val << ID_V_DTYPE); + uptr->capac = (t_addr)id_dtab[val].capac; + + return SCPE_OK; +} + +t_stat id_show_type (FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + fprintf (st, "%s", id_dtab[ID_GET_DTYPE(uptr->flags)].name); + return SCPE_OK; +} + +t_stat id_reset(DEVICE *dptr) +{ + id_clear_fifo(); + return SCPE_OK; +} + +t_stat id_attach(UNIT *uptr, CONST char *cptr) +{ + static const char *drives[] = {"HD30", "HD72", "HD72C", "HD135", "HD161", NULL}; + + return sim_disk_attach_ex(uptr, cptr, 512, 1, TRUE, 0, id_dtab[ID_GET_DTYPE(uptr->flags)].name, + 0, 0, (uptr->flags & ID_AUTOSIZE) ? drives : NULL); +} + +t_stat id_detach(UNIT *uptr) +{ + return sim_disk_detach(uptr); +} + +/* Return the logical block address of the given sector */ +static t_lba id_lba(uint16 cyl, uint8 head, uint8 sec) +{ + uint8 dtype; + + dtype = ID_GET_DTYPE(id_sel_unit->flags); + + return((ID_SEC_CNT * id_dtab[dtype].hd * cyl) + + (ID_SEC_CNT * head) + + sec); +} + +/* At the end of each sector read or write, we update the FIFO + * with the correct return parameters. */ +static void SIM_INLINE id_end_rw(uint8 est) +{ + id_clear_fifo(); + id_data[0] = est; + id_data[1] = id_phn; + id_data[2] = ~(id_lcnh); + id_data[3] = id_lcnl; + id_data[4] = id_lhn; + id_data[5] = id_lsn; + id_data[6] = id_scnt; +} + +/* The controller wraps id_lsn, id_lhn, and id_lcnl on each sector + * read, so that they point to the next C/H/S */ +static void SIM_INLINE id_update_chs() +{ + if (id_lsn++ >= id_esn) { + id_lsn = 0; + if (id_lhn++ >= id_etn) { + id_lhn = 0; + if (id_lcnl == 0xff) { + id_lcnl = 0; + id_lcnh++; + } else { + id_lcnl++; + } + } + } +} + +uint32 id_read(uint32 pa, size_t size) +{ + uint8 reg; + uint16 cyl; + t_lba lba; + uint32 data; + t_seccnt sectsread; + + reg = (uint8) (pa - IDBASE); + + switch(reg) { + case ID_DATA_REG: /* Data Buffer Register */ + /* If we're in a DMA transfer, we need to be reading data from + * the disk buffer. Otherwise, we're reading from the FIFO. */ + + if (id_drq) { + /* If the drive isn't attached, there's really nothing we + can do. */ + if ((id_sel_unit->flags & UNIT_ATT) == 0) { + id_end_rw(ID_EST_NR); + return 0; + } + + /* We could be in one of these commands: + * - Read Data + * - Read ID + */ + + if (CMD_NUM == ID_CMD_RDATA) { + /* If we're still in DRQ but we've read all our sectors, + * that's an error state. */ + if (id_scnt == 0) { + sim_debug(READ_MSG, &id_dev, + "ERROR\tid_scnt = 0 but still in dma\n"); + id_end_rw(ID_EST_OVR); + return 0; + } + + /* If the disk buffer is empty, fill it. */ + if (id_buf_ptr == 0 || id_buf_ptr >= ID_SEC_SIZE) { + /* It's time to read a new sector into our sector buf */ + id_buf_ptr = 0; + cyl = (uint16) (((uint16)id_lcnh << 8)|(uint16)id_lcnl); + id_cyl[id_unit_num] = cyl; + lba = id_lba(cyl, id_lhn, id_lsn); + if (sim_disk_rdsect(id_sel_unit, lba, id_buf, §sread, 1) == SCPE_OK) { + if (sectsread !=1) { + sim_debug(READ_MSG, &id_dev, + "ERROR: ASKED TO READ ONE SECTOR, READ: %d\n", + sectsread); + } + id_update_chs(); + } else { + /* Uh-oh! */ + sim_debug(READ_MSG, &id_dev, + "RDATA READ ERROR. Failure from sim_disk_rdsect!\n"); + id_end_rw(ID_EST_DER); + return 0; + } + } + + data = id_buf[id_buf_ptr++]; + sim_debug(READ_MSG, &id_dev, "DATA\t%02x\n", data); + + /* Done with this current sector, update id_scnt */ + if (id_buf_ptr >= ID_SEC_SIZE) { + if (--id_scnt == 0) { + id_end_rw(0); + } + } + } else if (CMD_NUM == ID_CMD_RID) { + /* We have to return the ID bytes for the current C/H/S */ + if (id_idfield_ptr == 0 || id_idfield_ptr >= ID_IDFIELD_LEN) { + id_idfield[0] = ~(id_lcnh); + id_idfield[1] = id_lcnl; + id_idfield[2] = id_lhn; + id_idfield[3] = id_lsn; + id_idfield_ptr = 0; + } + + data = id_idfield[id_idfield_ptr++]; + sim_debug(READ_MSG, &id_dev, + "ID DATA\t%02x\n", + data); + + if (id_idfield_ptr >= ID_IDFIELD_LEN) { + if (id_scnt-- > 0) { + /* Another sector to ID */ + id_idfield_ptr = 0; + } else { + /* All done, set return codes */ + id_clear_fifo(); + id_data[0] = 0; + id_data[1] = id_scnt; + } + } + } else { + /* cmd not Read Data or Read ID */ + stop_reason = STOP_ERR; + return 0; + } + + return data; + } else { + if (id_dpr < ID_FIFO_LEN) { + sim_debug(READ_MSG, &id_dev, + "DATA\t%02x\n", + id_data[id_dpr]); + return id_data[id_dpr++]; + } else { + sim_debug(READ_MSG, &id_dev, + "ERROR\tFIFO OVERRUN\n"); + return 0; + } + } + + break; + case ID_CMD_STAT_REG: /* Status Register */ + sim_debug(READ_MSG, &id_dev, + "STATUS\t%02x\n", + id_status|id_drq); + return id_status|(id_drq ? 1u : 0); + } + + sim_debug(READ_MSG, &id_dev, + "Read of unsuported register %x\n", + id_status); + + return 0; +} + +void id_write(uint32 pa, uint32 val, size_t size) +{ + uint8 reg; + uint16 cyl; + t_lba lba; + t_seccnt sectswritten; + + reg = (uint8) (pa - IDBASE); + + switch(reg) { + case ID_DATA_REG: + /* If we're in a DMA transfer, we need to be writing data to + * the disk buffer. Otherwise, we're writing to the FIFO. */ + + if (id_drq) { + /* If we're still in DRQ but we've written all our sectors, + * that's an error state. */ + if (id_scnt == 0) { + sim_debug(WRITE_MSG, &id_dev, + "ERROR\tid_scnt = 0 but still in dma\n"); + id_end_rw(ID_EST_OVR); + return; + } + + /* Write to the disk buffer */ + if (id_buf_ptr < ID_SEC_SIZE) { + id_buf[id_buf_ptr++] = (uint8)(val & 0xff); + sim_debug(WRITE_MSG, &id_dev, + "DATA\t%02x\n", + (uint8)(val & 0xff)); + } else { + sim_debug(WRITE_MSG, &id_dev, + "ERROR\tWDATA OVERRUN\n"); + id_end_rw(ID_EST_OVR); + return; + } + + /* If we've hit the end of a sector, flush it */ + if (id_buf_ptr >= ID_SEC_SIZE) { + /* It's time to start the next sector, and flush the old. */ + id_buf_ptr = 0; + cyl = (uint16) (((uint16) id_lcnh << 8)|(uint16)id_lcnl); + id_cyl[id_unit_num] = cyl; + lba = id_lba(cyl, id_lhn, id_lsn); + if (sim_disk_wrsect(id_sel_unit, lba, id_buf, §swritten, 1) == SCPE_OK) { + if (sectswritten !=1) { + sim_debug(WRITE_MSG, &id_dev, + "ERROR: ASKED TO WRITE ONE SECTOR, WROTE: %d\n", + sectswritten); + } + id_update_chs(); + if (--id_scnt == 0) { + id_end_rw(0); + } + } else { + /* Uh-oh! */ + sim_debug(WRITE_MSG, &id_dev, + "ERROR\tWDATA WRITE ERROR. lba=%04x\n", + lba); + id_end_rw(ID_EST_DER); + return; + } + } + return; + } else { + sim_debug(WRITE_MSG, &id_dev, + "DATA\t%02x\n", + val); + if (id_dpw < ID_FIFO_LEN) { + id_data[id_dpw++] = (uint8) val; + } else { + sim_debug(WRITE_MSG, &id_dev, + "ERROR\tFIFO OVERRUN\n"); + } + } + return; + case ID_CMD_STAT_REG: + id_handle_command((uint8) val); + return; + default: + return; + } +} + +void id_handle_command(uint8 val) +{ + uint8 cmd, aux_cmd, sec, pattern; + uint16 cyl; + uint32 time; + t_lba lba; + + /* Reset the FIFO pointer */ + id_clear_fifo(); + + /* Is this an aux command or a full command? */ + if ((val & 0xf0) == 0) { + aux_cmd = val & 0x0f; + + if (aux_cmd & ID_AUX_CLCE) { + sim_debug(WRITE_MSG, &id_dev, + "COMMAND\t%02x\tAUX:CLCE\n", + val); + id_clr_status(ID_STAT_CEH|ID_STAT_CEL); + } + + if (aux_cmd & ID_AUX_HSRQ) { + sim_debug(WRITE_MSG, &id_dev, + "COMMAND\t%02x\tAUX:HSRQ\n", + val); + id_set_srqm(TRUE); + } + + if (aux_cmd & ID_AUX_CLB) { + sim_debug(WRITE_MSG, &id_dev, + "COMMAND\t%02x\tAUX:CLBUF\n", + val); + id_clear_fifo(); + } + + if (aux_cmd & ID_AUX_RST) { + sim_debug(WRITE_MSG, &id_dev, + "COMMAND\t%02x\tAUX:RESET\n", + val); + id_clear_fifo(); + sim_cancel(id_sel_unit); + sim_cancel(id_ctlr_unit); + id_status = 0; + id_srqm = FALSE; + UPDATE_INT; + } + + /* Just return early */ + return; + } + + /* If the controller is busy and this isn't an AUX command, do + * nothing */ + if (id_status & ID_STAT_CB) { + sim_debug(EXECUTE_MSG, &id_dev, + "!!! Controller Busy. Skipping command byte %02x\n", + val); + return; + } + + /* A full command always resets CEH and CEL */ + id_clr_status(ID_STAT_CEH|ID_STAT_CEL); + + /* Save the full command byte */ + id_cmd = val; + cmd = (id_cmd >> 4) & 0xf; + + /* Now that we know it's not an aux command, we can get the unit + * number. Note that we don't update the unit in the case of three + * special commands. */ + if (cmd != ID_CMD_SIS && cmd != ID_CMD_SPEC && cmd != ID_CMD_DERR) { + if ((id_cmd & 3) != id_ua) { + id_unit_num = id_cmd & 1; + id_ua = id_cmd & 3; + id_sel_unit = &id_unit[id_unit_num]; + } + } + + /* TODO: Fix this hack */ + if (cmd == ID_CMD_SIS || cmd == ID_CMD_SPEC || cmd == ID_CMD_DERR) { + id_ctlr_unit->u4 = cmd; + } else { + id_sel_unit->u4 = cmd; + } + + id_set_status(ID_STAT_CB); + + switch(cmd) { + case ID_CMD_SIS: + sim_debug(WRITE_MSG, &id_dev, + "COMMAND\t%02x\tSense Int. Status\n", + val); + id_clr_status(ID_STAT_SRQ); /* SIS immediately de-asserts SRQ */ + id_activate(id_ctlr_unit, ID_SIS_WAIT); + break; + case ID_CMD_SPEC: + sim_debug(WRITE_MSG, &id_dev, + "COMMAND\t%02x\tSpecify - ETN=%02x ESN=%02x\n", + val, id_data[3], id_data[4]); + id_dtlh = id_data[1]; + id_etn = id_data[3]; + id_esn = id_data[4]; + id_polling = (id_dtlh & ID_DTLH_POLL) == 0; + id_activate(id_ctlr_unit, ID_SPEC_WAIT); + break; + case ID_CMD_SUS: + sim_debug(WRITE_MSG, &id_dev, + "COMMAND\t%02x\tSense Unit Status - %d\n", + val, id_ua); + id_activate(id_sel_unit, ID_SUS_WAIT); + break; + case ID_CMD_DERR: + sim_debug(WRITE_MSG, &id_dev, + "COMMAND\t%02x\tDetect Error\n", + val); + id_activate(id_ctlr_unit, ID_CMD_WAIT); + break; + case ID_CMD_RECAL: + time = id_cyl[id_unit_num]; + id_cyl[id_unit_num] = 0; + id_seek_state[id_unit_num] = ID_SEEK_0; + if (id_polling) { + sim_debug(WRITE_MSG, &id_dev, + "COMMAND\t%02x\tRecalibrate - %d - POLLING\n", + val, id_ua); + id_activate(id_sel_unit, 1000); + } else { + sim_debug(WRITE_MSG, &id_dev, + "COMMAND\t%02x\tRecalibrate - %d - NORMAL\n", + val, id_ua); + id_activate(id_sel_unit, (ID_RECAL_WAIT + (time * ID_SEEK_WAIT))); + } + break; + case ID_CMD_SEEK: + id_lcnh = id_data[0]; + id_lcnl = id_data[1]; + cyl = id_lcnh << 8 | id_lcnl; + time = (uint32) abs(id_cyl[id_unit_num] - cyl); + id_cyl[id_unit_num] = cyl; + id_seek_state[id_unit_num] = ID_SEEK_0; + + if (id_polling) { + sim_debug(WRITE_MSG, &id_dev, + "COMMAND\t%02x\tSeek - %d - POLLING\n", + val, id_ua); + id_activate(id_sel_unit, 4000); + } else { + sim_debug(WRITE_MSG, &id_dev, + "COMMAND\t%02x\tSeek - %d - NORMAL\n", + val, id_ua); + id_activate(id_sel_unit, ID_SEEK_BASE + (time * ID_SEEK_WAIT)); + } + break; + case ID_CMD_FMT: + sim_debug(WRITE_MSG, &id_dev, + "COMMAND\t%02x\tFormat - %d\n", + val, id_ua); + + id_phn = id_data[0]; + id_scnt = id_data[1]; + pattern = id_data[2]; + + /* Format scnt sectors with the given pattern, if attached */ + if (id_sel_unit->flags & UNIT_ATT) { + /* Formatting soft-sectored disks always begins at sector 0 */ + sec = 0; + + while (id_scnt-- > 0) { + /* Write one sector of pattern */ + for (id_buf_ptr = 0; id_buf_ptr < ID_SEC_SIZE; id_buf_ptr++) { + id_buf[id_buf_ptr] = pattern; + } + lba = id_lba(id_cyl[id_unit_num], id_phn, sec++); + if (sim_disk_wrsect(id_sel_unit, lba, id_buf, NULL, 1) == SCPE_OK) { + sim_debug(EXECUTE_MSG, &id_dev, + "FORMAT: PHN=%d SCNT=%d PAT=%02x LBA=%04x\n", + id_phn, id_scnt, pattern, lba); + } else { + sim_debug(EXECUTE_MSG, &id_dev, + "FORMAT FAILED! PHN=%d SCNT=%d PAT=%02x LBA=%04x\n", + id_phn, id_scnt, pattern, lba); + break; + } + } + + id_data[0] = 0; + } else { + /* Not attached */ + id_data[0] = ID_EST_NR; + } + + id_data[1] = id_scnt; + + id_activate(id_sel_unit, ID_CMD_WAIT); + break; + case ID_CMD_VID: + sim_debug(WRITE_MSG, &id_dev, + "COMMAND\t%02x\tVerify ID - %d\n", + val, id_ua); + id_data[0] = 0; + id_data[1] = 0x05; /* What do we put here? */ + id_activate(id_sel_unit, ID_CMD_WAIT); + break; + case ID_CMD_RID: + sim_debug(WRITE_MSG, &id_dev, + "COMMAND\t%02x\tRead ID - %d\n", + val, id_ua); + if (id_sel_unit->flags & UNIT_ATT) { + id_drq = TRUE; + + /* Grab our arguments */ + id_phn = id_data[0]; + id_scnt = id_data[1]; + + /* Compute logical values used by ID verification */ + id_lhn = id_phn; + id_lsn = 0; + } else { + sim_debug(EXECUTE_MSG, &id_dev, + "UNIT %d NOT ATTACHED, CANNOT READ ID.\n", + id_ua); + } + id_activate(id_sel_unit, ID_CMD_WAIT); + break; + case ID_CMD_RDIAG: + sim_debug(WRITE_MSG, &id_dev, + "COMMAND\t%02x\tRead Diag - %d\n", + val, id_ua); + id_activate(id_sel_unit, ID_CMD_WAIT); + break; + case ID_CMD_RDATA: + sim_debug(WRITE_MSG, &id_dev, + "COMMAND\t%02x\tRead Data - %d\n", + val, id_ua); + if (id_sel_unit->flags & UNIT_ATT) { + id_drq = TRUE; + id_buf_ptr = 0; + + /* Grab our arguments */ + id_phn = id_data[0]; + id_lcnh = ~(id_data[1]); + id_lcnl = id_data[2]; + id_lhn = id_data[3]; + id_lsn = id_data[4]; + id_scnt = id_data[5]; + } else { + sim_debug(EXECUTE_MSG, &id_dev, + "UNIT %d NOT ATTACHED, CANNOT READ DATA.\n", + id_ua); + } + id_activate(id_sel_unit, ID_RW_WAIT); + break; + case ID_CMD_CHECK: + sim_debug(WRITE_MSG, &id_dev, + "COMMAND\t%02x\tCheck - %d\n", + val, id_ua); + id_activate(id_sel_unit, ID_CMD_WAIT); + break; + case ID_CMD_SCAN: + sim_debug(WRITE_MSG, &id_dev, + "COMMAND\t%02x\tScan - %d\n", + val, id_ua); + id_activate(id_sel_unit, ID_CMD_WAIT); + break; + case ID_CMD_VDATA: + sim_debug(WRITE_MSG, &id_dev, + "COMMAND\t%02x\tVerify Data - %d\n", + val, id_ua); + id_activate(id_sel_unit, ID_CMD_WAIT); + break; + case ID_CMD_WDATA: + sim_debug(WRITE_MSG, &id_dev, + "COMMAND\t%02x\tWrite Data - %d\n", + val, id_ua); + if (id_sel_unit->flags & UNIT_ATT) { + id_drq = TRUE; + id_buf_ptr = 0; + + /* Grab our arguments */ + id_phn = id_data[0]; + id_lcnh = ~(id_data[1]); + id_lcnl = id_data[2]; + id_lhn = id_data[3]; + id_lsn = id_data[4]; + id_scnt = id_data[5]; + } else { + sim_debug(EXECUTE_MSG, &id_dev, + "UNIT %d NOT ATTACHED, CANNOT WRITE.\n", + id_ua); + } + id_activate(id_sel_unit, ID_RW_WAIT); + break; + } +} + +void id_after_dma() +{ + id_clr_status(ID_STAT_DRQ); + id_drq = FALSE; +} + +CONST char *id_description(DEVICE *dptr) +{ + return "Integrated Hard Disk"; +} + +t_stat id_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf(st, "Integrated Hard Disk (IDISK)\n\n"); + fprintf(st, "The IDISK device implements the integrated MFM hard disk of the\n"); + fprintf(st, "3B2/400. Up to two drives are supported on a single controller.\n\n"); + fprintf(st, "Supported device types are:\n\n"); + fprintf(st, " Name Size ID Cyl Head Sec Byte/Sec Description\n"); + fprintf(st, " ---- -------- -- ---- ---- --- -------- ----------------------\n"); + fprintf(st, " HD30 30.6 MB 3 697 5 18 512 CDC Wren 94155-36\n"); + fprintf(st, " HD72 73.2 MB 5 925 9 18 512 CDC Wren II 94156-86\n"); + fprintf(st, " HD72C 72.9 MB 8 754 11 18 512 Fujitsu M2243AS\n"); + fprintf(st, " HD135 135.0 MB 11 1024 15 18 512 Maxtor XT1190 (SVR2)\n"); + fprintf(st, " HD161 161.4 MB 11 1224 15 18 512 Maxtor XT1190 (SVR3+)\n\n"); + fprintf(st, "The drive ID and geometry values are used when low-level formatting a\n"); + fprintf(st, "drive using the AT&T 'idtools' utility.\n"); + + fprint_set_help(st, dptr); + fprint_show_help(st, dptr); + fprint_reg_help(st, dptr); + + return SCPE_OK; +} diff --git a/3B2/3b2_id.h b/3B2/3b2_id.h index 08ecf98e..81f78e31 100644 --- a/3B2/3b2_id.h +++ b/3B2/3b2_id.h @@ -1,178 +1,178 @@ -/* 3b2_id.h: uPD7261 Integrated Disk Controller - - Copyright (c) 2017-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#ifndef __3B2_ID_H__ -#define __3B2_ID_H__ - -#include "3b2_defs.h" - -#define ID0 0 -#define ID1 1 -#define ID_CTLR 2 - -/* Command Codes (bits 3-7 of command byte) */ - -#define ID_CMD_AUX 0x00 /* Auxiliary Command */ -#define ID_CMD_SIS 0x01 /* Sense int. status */ -#define ID_CMD_SPEC 0x02 /* Specify */ -#define ID_CMD_SUS 0x03 /* Sense unit status */ -#define ID_CMD_DERR 0x04 /* Detect Error */ -#define ID_CMD_RECAL 0x05 /* Recalibrate */ -#define ID_CMD_SEEK 0x06 /* Seek */ -#define ID_CMD_FMT 0x07 /* Format */ -#define ID_CMD_VID 0x08 /* Verify ID */ -#define ID_CMD_RID 0x09 /* Read ID */ -#define ID_CMD_RDIAG 0x0A /* Read Diagnostic */ -#define ID_CMD_RDATA 0x0B /* Read Data */ -#define ID_CMD_CHECK 0x0C /* Check */ -#define ID_CMD_SCAN 0x0D /* Scan */ -#define ID_CMD_VDATA 0x0E /* Verify Data */ -#define ID_CMD_WDATA 0x0F /* Write Data */ - -#define ID_AUX_RST 0x01 -#define ID_AUX_CLB 0x02 -#define ID_AUX_HSRQ 0x04 -#define ID_AUX_CLCE 0x08 - -#define ID_STAT_DRQ 0x01 -#define ID_STAT_NCI 0x02 -#define ID_STAT_IER 0x04 -#define ID_STAT_RRQ 0x08 -#define ID_STAT_SRQ 0x10 -#define ID_STAT_CEL 0x20 -#define ID_STAT_CEH 0x40 -#define ID_STAT_CB 0x80 - -#define ID_IST_SEN 0x80 /* Seek End */ -#define ID_IST_RC 0x40 /* Ready Change */ -#define ID_IST_SER 0x20 /* Seek Error */ -#define ID_IST_EQC 0x10 /* Equipment Check */ -#define ID_IST_NR 0x08 /* Not Ready */ - -#define ID_UST_DSEL 0x10 /* Drive Selected */ -#define ID_UST_SCL 0x08 /* Seek Complete */ -#define ID_UST_TK0 0x04 /* Track 0 */ -#define ID_UST_RDY 0x02 /* Ready */ -#define ID_UST_WFL 0x01 /* Write Fault */ - -#define ID_EST_ENC 0x80 -#define ID_EST_OVR 0x40 -#define ID_EST_DER 0x20 -#define ID_EST_EQC 0x10 -#define ID_EST_NR 0x08 -#define ID_EST_ND 0x04 -#define ID_EST_NWR 0x02 -#define ID_EST_MAM 0x01 - -#define ID_DTLH_POLL 0x10 - -#define ID_SEEK_NONE -1 -#define ID_SEEK_0 0 -#define ID_SEEK_1 1 - -/* Drive Geometries */ - -/* Common across all drive types */ -#define ID_SEC_SIZE 512 -#define ID_SEC_CNT 18 -#define ID_CYL_SIZE ID_SEC_SIZE * ID_SEC_CNT - -/* Specific to each drive type */ -#define ID_MAX_DTYPE 3 - -#define ID_HD30_DTYPE 0 -#define ID_HD30_CYL 697 -#define ID_HD30_HEADS 5 -#define ID_HD30_LBN 62730 - -#define ID_HD72_DTYPE 1 -#define ID_HD72_CYL 925 -#define ID_HD72_HEADS 9 -#define ID_HD72_LBN 149850 - -#define ID_HD72C_DTYPE 2 -#define ID_HD72C_CYL 754 -#define ID_HD72C_HEADS 11 -#define ID_HD72C_LBN 149292 - -/* The HD135 is actually just an HD161 with only 1024 cylinders - * formatted. This is a software limitation, not hardware. */ - -#define ID_HD135_DTYPE 3 -#define ID_HD135_CYL 1224 -#define ID_HD135_HEADS 15 -#define ID_HD135_LBN 330480 - -#define ID_HD161_DTYPE 3 -#define ID_HD161_CYL 1224 -#define ID_HD161_HEADS 15 -#define ID_HD161_LBN 330480 - -#define ID_V_DTYPE (DKUF_V_UF + 0) -#define ID_M_DTYPE 3 -#define ID_DTYPE (ID_M_DTYPE << ID_V_DTYPE) -#define ID_V_AUTOSIZE (ID_V_DTYPE + 2) -#define ID_AUTOSIZE (1 << ID_V_AUTOSIZE) -#define ID_GET_DTYPE(x) (((x) >> ID_V_DTYPE) & ID_M_DTYPE) -#define ID_DRV(d) { ID_##d##_HEADS, ID_##d##_LBN, #d } - -#define ID_DSK_SIZE(d) ID_##d##_LBN - -/* Unit, Register, Device descriptions */ - -#define ID_FIFO_LEN 8 -#define ID_IDFIELD_LEN 4 - -#define ID_NUM_UNITS 2 - -#define DMA_ID_SVC IDBASE+ID_DATA_REG - -#define CMD_NUM ((id_cmd >> 4) & 0xf) - -/* Function prototypes */ - -t_stat id_ctlr_svc(UNIT *uptr); -t_stat id_unit_svc(UNIT *uptr); -t_stat id_reset(DEVICE *dptr); -t_stat id_set_type(UNIT *uptr, int32 val, CONST char *cptr, void *desc); -t_stat id_show_type (FILE *st, UNIT *uptr, int32 val, CONST void *desc); -t_stat id_attach(UNIT *uptr, CONST char *cptr); -t_stat id_detach(UNIT *uptr); -uint32 id_read(uint32 pa, size_t size); -void id_write(uint32 pa, uint32 val, size_t size); -CONST char *id_description(DEVICE *dptr); -t_stat id_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); -void id_handle_data(uint8 val); -void id_handle_command(uint8 val); -void id_after_dma(); - -extern t_bool id_drq; - -#endif +/* 3b2_id.h: uPD7261 Integrated Disk Controller + + Copyright (c) 2017-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#ifndef __3B2_ID_H__ +#define __3B2_ID_H__ + +#include "3b2_defs.h" + +#define ID0 0 +#define ID1 1 +#define ID_CTLR 2 + +/* Command Codes (bits 3-7 of command byte) */ + +#define ID_CMD_AUX 0x00 /* Auxiliary Command */ +#define ID_CMD_SIS 0x01 /* Sense int. status */ +#define ID_CMD_SPEC 0x02 /* Specify */ +#define ID_CMD_SUS 0x03 /* Sense unit status */ +#define ID_CMD_DERR 0x04 /* Detect Error */ +#define ID_CMD_RECAL 0x05 /* Recalibrate */ +#define ID_CMD_SEEK 0x06 /* Seek */ +#define ID_CMD_FMT 0x07 /* Format */ +#define ID_CMD_VID 0x08 /* Verify ID */ +#define ID_CMD_RID 0x09 /* Read ID */ +#define ID_CMD_RDIAG 0x0A /* Read Diagnostic */ +#define ID_CMD_RDATA 0x0B /* Read Data */ +#define ID_CMD_CHECK 0x0C /* Check */ +#define ID_CMD_SCAN 0x0D /* Scan */ +#define ID_CMD_VDATA 0x0E /* Verify Data */ +#define ID_CMD_WDATA 0x0F /* Write Data */ + +#define ID_AUX_RST 0x01 +#define ID_AUX_CLB 0x02 +#define ID_AUX_HSRQ 0x04 +#define ID_AUX_CLCE 0x08 + +#define ID_STAT_DRQ 0x01 +#define ID_STAT_NCI 0x02 +#define ID_STAT_IER 0x04 +#define ID_STAT_RRQ 0x08 +#define ID_STAT_SRQ 0x10 +#define ID_STAT_CEL 0x20 +#define ID_STAT_CEH 0x40 +#define ID_STAT_CB 0x80 + +#define ID_IST_SEN 0x80 /* Seek End */ +#define ID_IST_RC 0x40 /* Ready Change */ +#define ID_IST_SER 0x20 /* Seek Error */ +#define ID_IST_EQC 0x10 /* Equipment Check */ +#define ID_IST_NR 0x08 /* Not Ready */ + +#define ID_UST_DSEL 0x10 /* Drive Selected */ +#define ID_UST_SCL 0x08 /* Seek Complete */ +#define ID_UST_TK0 0x04 /* Track 0 */ +#define ID_UST_RDY 0x02 /* Ready */ +#define ID_UST_WFL 0x01 /* Write Fault */ + +#define ID_EST_ENC 0x80 +#define ID_EST_OVR 0x40 +#define ID_EST_DER 0x20 +#define ID_EST_EQC 0x10 +#define ID_EST_NR 0x08 +#define ID_EST_ND 0x04 +#define ID_EST_NWR 0x02 +#define ID_EST_MAM 0x01 + +#define ID_DTLH_POLL 0x10 + +#define ID_SEEK_NONE -1 +#define ID_SEEK_0 0 +#define ID_SEEK_1 1 + +/* Drive Geometries */ + +/* Common across all drive types */ +#define ID_SEC_SIZE 512 +#define ID_SEC_CNT 18 +#define ID_CYL_SIZE ID_SEC_SIZE * ID_SEC_CNT + +/* Specific to each drive type */ +#define ID_MAX_DTYPE 3 + +#define ID_HD30_DTYPE 0 +#define ID_HD30_CYL 697 +#define ID_HD30_HEADS 5 +#define ID_HD30_LBN 62730 + +#define ID_HD72_DTYPE 1 +#define ID_HD72_CYL 925 +#define ID_HD72_HEADS 9 +#define ID_HD72_LBN 149850 + +#define ID_HD72C_DTYPE 2 +#define ID_HD72C_CYL 754 +#define ID_HD72C_HEADS 11 +#define ID_HD72C_LBN 149292 + +/* The HD135 is actually just an HD161 with only 1024 cylinders + * formatted. This is a software limitation, not hardware. */ + +#define ID_HD135_DTYPE 3 +#define ID_HD135_CYL 1224 +#define ID_HD135_HEADS 15 +#define ID_HD135_LBN 330480 + +#define ID_HD161_DTYPE 3 +#define ID_HD161_CYL 1224 +#define ID_HD161_HEADS 15 +#define ID_HD161_LBN 330480 + +#define ID_V_DTYPE (DKUF_V_UF + 0) +#define ID_M_DTYPE 3 +#define ID_DTYPE (ID_M_DTYPE << ID_V_DTYPE) +#define ID_V_AUTOSIZE (ID_V_DTYPE + 2) +#define ID_AUTOSIZE (1 << ID_V_AUTOSIZE) +#define ID_GET_DTYPE(x) (((x) >> ID_V_DTYPE) & ID_M_DTYPE) +#define ID_DRV(d) { ID_##d##_HEADS, ID_##d##_LBN, #d } + +#define ID_DSK_SIZE(d) ID_##d##_LBN + +/* Unit, Register, Device descriptions */ + +#define ID_FIFO_LEN 8 +#define ID_IDFIELD_LEN 4 + +#define ID_NUM_UNITS 2 + +#define DMA_ID_SVC IDBASE+ID_DATA_REG + +#define CMD_NUM ((id_cmd >> 4) & 0xf) + +/* Function prototypes */ + +t_stat id_ctlr_svc(UNIT *uptr); +t_stat id_unit_svc(UNIT *uptr); +t_stat id_reset(DEVICE *dptr); +t_stat id_set_type(UNIT *uptr, int32 val, CONST char *cptr, void *desc); +t_stat id_show_type (FILE *st, UNIT *uptr, int32 val, CONST void *desc); +t_stat id_attach(UNIT *uptr, CONST char *cptr); +t_stat id_detach(UNIT *uptr); +uint32 id_read(uint32 pa, size_t size); +void id_write(uint32 pa, uint32 val, size_t size); +CONST char *id_description(DEVICE *dptr); +t_stat id_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); +void id_handle_data(uint8 val); +void id_handle_command(uint8 val); +void id_after_dma(); + +extern t_bool id_drq; + +#endif diff --git a/3B2/3b2_if.c b/3B2/3b2_if.c index a97cb94d..cad717e1 100644 --- a/3B2/3b2_if.c +++ b/3B2/3b2_if.c @@ -1,645 +1,645 @@ -/* 3b2_if.c: TMS2797 Integrated Floppy Controller - - Copyright (c) 2017-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#include "3b2_if.h" - -#include "sim_disk.h" - -#include "3b2_cpu.h" -#include "3b2_csr.h" - -/* Static function declarations */ -static SIM_INLINE uint32 if_lba(); - -/* - * Disk Format: - * ------------ - * - * - 80 Tracks - * - 9 Sectors per track - * - 2 heads - * - 512 bytes per sector - * - * 80 * 9 * 2 * 512 = 720KB - * - */ - -#define IF_STEP_DELAY 300 /* us */ -#define IF_R_DELAY 6500 /* us */ -#define IF_W_DELAY 7000 /* us */ -#define IF_VERIFY_DELAY 2000 /* us */ -#define IF_HLD_DELAY 6000 /* us */ -#define IF_HSW_DELAY 4000 /* us */ - -#if defined(REV3) -#define SET_INT CPU_SET_INT(INT_FLOPPY) -#define CLR_INT CPU_CLR_INT(INT_FLOPPY) -#else -#define SET_INT do { \ - CPU_SET_INT(INT_FLOPPY); \ - SET_CSR(CSRDISK); \ - } while(0) -#define CLR_INT do { \ - CPU_CLR_INT(INT_FLOPPY); \ - CLR_CSR(CSRDISK); \ - } while(0) -#endif - -UNIT if_unit = { - UDATA (&if_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BINK+UNIT_ROABLE, - IF_DSK_SIZE_SECS) -}; - -REG if_reg[] = { - { NULL } -}; - -DEVICE if_dev = { - "IFLOPPY", &if_unit, if_reg, NULL, - 1, 16, 8, 1, 16, 8, - NULL, NULL, &if_reset, - NULL, &if_attach, &if_detach, NULL, - DEV_DEBUG|DEV_DISK|DEV_SECTORS, 0, sys_deb_tab, - NULL, NULL, &if_help, NULL, NULL, - &if_description -}; - -IF_STATE if_state; -uint8 if_buf[IF_SEC_SIZE]; -uint32 if_sec_ptr = 0; - -/* Function implementation */ - -static SIM_INLINE void if_activate(uint32 delay_us) -{ - sim_activate_after(&if_unit, delay_us); -} - -t_stat if_svc(UNIT *uptr) -{ - uint32 lba; /* Logical block address for write */ - t_seccnt sectswritten; - - if_state.status &= ~(IF_BUSY); - - switch(if_state.cmd & 0xf0) { - case IF_RESTORE: - if_state.status = (IF_TK_0|IF_HEAD_LOADED); - break; - case IF_SEEK: - if_state.status = IF_HEAD_LOADED; - if (if_state.track == 0) { - if_state.status |= IF_TK_0; - } - break; - case IF_WRITE_SEC: - lba = if_lba(); - - /* If we're read-only, don't actually do anything. */ - if (if_unit.flags & UNIT_RO) { - break; - } - - if (sim_disk_wrsect(&if_unit, lba, if_buf, §swritten, 1) == SCPE_OK) { - if (sectswritten != 1) { - sim_debug(EXECUTE_MSG, &if_dev, - "ERROR: ASKED TO wRITE ONE SECTOR, WROTE %d\n", - sectswritten); - } - } - - break; - } - - if_state.cmd = 0; - - /* Request an interrupt */ - sim_debug(IRQ_MSG, &if_dev, "\tINTR\n"); - SET_INT; - - return SCPE_OK; -} - -t_stat if_reset(DEVICE *dptr) -{ - if_state.status = IF_TK_0; - if_state.track = 0; - if_state.sector = 1; - if_sec_ptr = 0; - - return SCPE_OK; -} - -t_stat if_attach(UNIT *uptr, CONST char *cptr) -{ - return sim_disk_attach(uptr, cptr, 512, 1, TRUE, 0, NULL, 0, 0); -} - -t_stat if_detach(UNIT *uptr) -{ - return sim_disk_detach(uptr); -} - -uint32 if_read(uint32 pa, size_t size) { - uint8 reg, data; - UNIT *uptr; - - uptr = &(if_dev.units[0]); - reg = (uint8)(pa - IFBASE); - - switch (reg) { - case IF_STATUS_REG: - data = if_state.status; - /* If there's no image attached, we're not ready */ - if ((uptr->flags & (UNIT_ATT|UNIT_BUF)) == 0) { - data |= IF_NRDY; - } - /* Reading the status register always de-asserts the IRQ line */ - CLR_INT; - sim_debug(READ_MSG, &if_dev, "\tSTATUS\t%02x\n", data); - break; - case IF_TRACK_REG: - data = if_state.track; - sim_debug(READ_MSG, &if_dev, "\tTRACK\t%02x\n", data); - break; - case IF_SECTOR_REG: - data = if_state.sector; - sim_debug(READ_MSG, &if_dev, "\tSECTOR\t%02x\n", data); - break; - case IF_DATA_REG: - if_state.status &= ~IF_DRQ; - - if (((uptr->flags & (UNIT_ATT|UNIT_BUF)) == 0) || - ((if_state.cmd & 0xf0) != IF_READ_SEC && - (if_state.cmd & 0xf0) != IF_READ_SEC_M)) { - /* Not attached, or not a read command */ - - switch (if_state.cmd & 0xf0) { - case IF_READ_ADDR: - /* Special state machine. */ - switch (if_state.read_addr_ptr++) { - case 0: - if_state.data = if_state.track; - break; - case 1: - if_state.data = if_state.side; - break; - case 2: - if_state.data = if_state.sector; - break; - case 3: - if_state.data = 2; /* 512 byte */ - break; - case 4: - /* TODO: Checksum */ - if_state.data = 0; - break; - case 5: - /* TODO: Checksum */ - if_state.data = 0; - if_state.read_addr_ptr = 0; - break; - } - } - - sim_debug(READ_MSG, &if_dev, "\tDATA\t%02x\n", if_state.data); - return if_state.data; - } - - data = if_buf[if_sec_ptr++]; - sim_debug(READ_MSG, &if_dev, "\tDATA\t%02x\n", data); - - if (if_sec_ptr >= IF_SEC_SIZE) { - if_sec_ptr = 0; - } - - break; - default: - data = 0xffu; // Compiler warning - break; - } - - return data; -} - -/* Handle the most recently received command */ -void if_handle_command() -{ - uint32 delay_ms = 0; - uint32 head_switch_delay = 0; - uint32 head_load_delay = 0; - uint32 lba; /* Logical block address */ - t_seccnt sectsread; - - if_sec_ptr = 0; - - /* We're starting a new command. */ - if_state.status = IF_BUSY; - - /* Clear read addr state */ - if_state.read_addr_ptr = 0; - - switch(if_state.cmd & 0xf0) { - case IF_RESTORE: - case IF_SEEK: - case IF_STEP: - case IF_STEP_T: - case IF_STEP_IN: - case IF_STEP_IN_T: - case IF_STEP_OUT: - case IF_STEP_OUT_T: - if_state.cmd_type = 1; - if (if_state.cmd & IF_H_FLAG) { - head_load_delay = IF_HLD_DELAY; - } - break; - - case IF_READ_SEC: - case IF_READ_SEC_M: - case IF_WRITE_SEC: - case IF_WRITE_SEC_M: - if_state.cmd_type = 2; -#if defined(REV2) - if (((if_state.cmd & IF_U_FLAG) >> 1) != if_state.side) { - head_switch_delay = IF_HSW_DELAY; - if_state.side = (if_state.cmd & IF_U_FLAG) >> 1; - } -#endif - break; - - case IF_READ_ADDR: - case IF_READ_TRACK: - case IF_WRITE_TRACK: - if_state.cmd_type = 3; -#if defined(REV2) - if (((if_state.cmd & IF_U_FLAG) >> 1) != if_state.side) { - head_switch_delay = IF_HSW_DELAY; - if_state.side = (if_state.cmd & IF_U_FLAG) >> 1; - } -#endif - break; - } - - switch(if_state.cmd & 0xf0) { - case IF_RESTORE: - sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tRestore\n", if_state.cmd); - - /* Reset HLT */ - if_state.status &= ~IF_HEAD_LOADED; - - if (if_unit.flags & UNIT_RO) { - if_state.status |= IF_WP; - } - - /* If head should be loaded immediately, do so now */ - if (if_state.cmd & IF_H_FLAG) { - if_state.status |= IF_HEAD_LOADED; - } - - if (if_state.track == 0) { - if_state.status |= IF_TK_0; - if_state.track = 1; /* Kind of a gross hack */ - } - - if (if_state.cmd & IF_V_FLAG) { - delay_ms = (IF_STEP_DELAY * if_state.track) + IF_VERIFY_DELAY; - } else { - delay_ms = IF_STEP_DELAY * if_state.track; - } - - if_activate(delay_ms); - - if_state.data = 0; - if_state.track = 0; - break; - - case IF_STEP: - case IF_STEP_T: - sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tStep\n", if_state.cmd); - if (if_unit.flags & UNIT_RO) { - if_state.status |= IF_WP; - } - if_activate(IF_STEP_DELAY); - if_state.track = (uint8) MIN(MAX((int) if_state.track + if_state.step_dir, 0), 0x4f); - break; - case IF_STEP_IN: - case IF_STEP_IN_T: - sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tStep In\n", if_state.cmd); - if (if_unit.flags & UNIT_RO) { - if_state.status |= IF_WP; - } - if_state.step_dir = IF_STEP_IN_DIR; - if_state.track = (uint8) MAX((int) if_state.track + if_state.step_dir, 0); - if_activate(IF_STEP_DELAY); - break; - case IF_STEP_OUT: - case IF_STEP_OUT_T: - sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tStep Out\n", if_state.cmd); - if (if_unit.flags & UNIT_RO) { - if_state.status |= IF_WP; - } - if_state.step_dir = IF_STEP_OUT_DIR; - if_state.track = (uint8) MIN((int) if_state.track + if_state.step_dir, 0x4f); - if_activate(IF_STEP_DELAY); - break; - case IF_SEEK: - sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tSeek\n", if_state.cmd); - - /* Reset HLT */ - if_state.status &= ~IF_HEAD_LOADED; - - if (if_unit.flags & UNIT_RO) { - if_state.status |= IF_WP; - } - - /* If head should be loaded immediately, do so now */ - if (if_state.cmd & IF_H_FLAG) { - if_state.status |= IF_HEAD_LOADED; - } - - /* Save the direction for stepping */ - if (if_state.data > if_state.track) { - if_state.step_dir = IF_STEP_IN_DIR; - } else if (if_state.data < if_state.track) { - if_state.step_dir = IF_STEP_OUT_DIR; - } - - /* The new track is in the data register */ - - if (if_state.data > IF_TRACK_COUNT-1) { - if_state.data = IF_TRACK_COUNT-1; - } - - if (if_state.data == 0) { - if_state.status |= IF_TK_0; - } else { - if_state.status &= ~(IF_TK_0); - } - - delay_ms = (uint32) abs(if_state.data - if_state.track); - - if (delay_ms == 0) { - delay_ms++; - } - - if (if_state.cmd & IF_V_FLAG) { - if_activate((IF_STEP_DELAY * delay_ms) + IF_VERIFY_DELAY + head_load_delay); - } else { - if_activate((IF_STEP_DELAY * delay_ms) + head_load_delay); - } - - if_state.track = if_state.data; - break; - - case IF_READ_SEC: - lba = if_lba(); - - sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tRead Sector %d/%d/%d (lba=%d)\n", - if_state.cmd, if_state.track, if_state.side, if_state.sector, lba); - - if (sim_disk_rdsect(&if_unit, lba, if_buf, §sread, 1) == SCPE_OK) { - if (sectsread != 1) { - sim_debug(EXECUTE_MSG, &if_dev, - "ERROR: ASKED TO READ ONE SECTOR, READ %d\n", - sectsread); - } - /* We set DRQ right away to request the transfer. */ - if_state.drq = TRUE; - if_state.status |= IF_DRQ; - if (if_state.cmd & IF_E_FLAG) { - if_activate(IF_R_DELAY + IF_VERIFY_DELAY + head_switch_delay); - } else { - if_activate(IF_R_DELAY + head_switch_delay); - } - } - - break; - case IF_READ_SEC_M: - /* Not yet implemented. Halt the emulator. */ - sim_debug(EXECUTE_MSG, &if_dev, - "\tCOMMAND\t%02x\tRead Sector (Multi) - NOT IMPLEMENTED\n", - if_state.cmd); - stop_reason = STOP_ERR; - break; - case IF_WRITE_SEC: - lba = if_lba(); - - sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tWrite Sector %d/%d/%d (lba=%d)\n", - if_state.cmd, if_state.track, if_state.side, if_state.sector, lba); - - if (if_unit.flags & UNIT_RO) { - if_state.status |= IF_WP; - sim_debug(EXECUTE_MSG, &if_dev, "\tWON'T WRITE: WRITE PROTECTED.\n"); - /* Still cause an interrupt... */ - if_activate(IF_W_DELAY + head_switch_delay); - /* But don't set DRQ and ask for a transfer. */ - break; - } - - /* We set DRQ right away to request the transfer. Data will - * be written by the host into our buffer by 512 writes to the - * data register. When the IF device later activates, the data - * will actually be written. */ - if_state.drq = TRUE; - if_state.status |= IF_DRQ; - if (if_state.cmd & IF_E_FLAG) { - if_activate(IF_W_DELAY + IF_VERIFY_DELAY + head_switch_delay); - } else { - if_activate(IF_W_DELAY + head_switch_delay); - } - break; - case IF_WRITE_SEC_M: - /* Not yet implemented. Halt the emulator. */ - sim_debug(EXECUTE_MSG, &if_dev, - "\tCOMMAND\t%02x\tWrite Sector (Multi) - NOT IMPLEMENTED\n", - if_state.cmd); - stop_reason = STOP_ERR; - break; - case IF_READ_ADDR: - sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tRead Address\n", if_state.cmd); - if_state.drq = TRUE; - if_state.status |= IF_DRQ; - if_activate(IF_R_DELAY); - break; - case IF_READ_TRACK: - sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tRead Track\n", if_state.cmd); - /* Not yet implemented. Halt the emulator. */ - stop_reason = STOP_ERR; - break; - case IF_WRITE_TRACK: - sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tWrite Track\n", if_state.cmd); - /* Set DRQ */ - if_state.drq = TRUE; - if_state.status |= IF_DRQ; - if (if_state.cmd & IF_E_FLAG) { - if_activate(IF_W_DELAY + IF_VERIFY_DELAY + head_switch_delay); - } else { - if_activate(IF_W_DELAY + head_switch_delay); - } - break; - } -} - -void if_write(uint32 pa, uint32 val, size_t size) -{ - UNIT *uptr; - uint8 reg; - - val = val & 0xff; - - uptr = &(if_dev.units[0]); - reg = (uint8) (pa - IFBASE); - - switch (reg) { - case IF_CMD_REG: - if_state.cmd = (uint8) val; - /* Writing to the command register always de-asserts the IRQ line */ - CLR_INT; - - /* If this is a FORCE INTERRUPT, handle it immediately. All - * other commands require that the unit be attached and a - * diskette loaded. This one does not. */ - if ((if_state.cmd & 0xf0) == IF_FORCE_INT) { - sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tForce Interrupt\n", if_state.cmd); - if_state.status = 0; - - if ((uptr->flags & UNIT_ATT) && if_state.track == 0) { - if_state.status |= (IF_TK_0|IF_HEAD_LOADED); - } - - if ((if_state.cmd & 0xf) == 0) { - sim_cancel(&if_unit); -#if defined(REV2) - CLR_INT; /* TODO: Confirm this is right */ -#endif - } else if ((if_state.cmd & 0x8) == 0x8) { - if_state.status |= IF_DRQ; - SET_INT; - } - break; - } - - if ((uptr->flags & UNIT_ATT) == 0) { - /* If not attached, do nothing */ - break; - } - - if_handle_command(); - break; - case IF_TRACK_REG: - if_state.track = (uint8) val; - sim_debug(WRITE_MSG, &if_dev, "\tTRACK\t%02x\n", val); - break; - case IF_SECTOR_REG: - if_state.sector = (uint8) val; - sim_debug(WRITE_MSG, &if_dev, "\tSECTOR\t%02x\n", val); - break; - case IF_DATA_REG: - if_state.data = (uint8) val; - - sim_debug(WRITE_MSG, &if_dev, "\tDATA\t%02x\n", val); - - if ((uptr->flags & UNIT_ATT) == 0) { - /* Not attached */ - break; - } - - if ((if_state.cmd & 0xf0) == IF_WRITE_TRACK) { - /* We intentionally ignore WRITE TRACK data, because - * This is only used for low-level MFM formatting, - * which we do not emulate. */ - } else if ((if_state.cmd & 0xf0) == IF_WRITE_SEC || - (if_state.cmd & 0xf0) == IF_WRITE_SEC_M) { - - if_buf[if_sec_ptr++] = (uint8) val; - - if (if_sec_ptr >= IF_SEC_SIZE) { - if_sec_ptr = 0; - } - } - - break; - default: - break; - } -} - -#if defined(REV3) -uint32 if_csr_read(uint32 pa, size_t size) -{ - return (uint32)(if_state.csr); -} - -void if_csr_write(uint32 pa, uint32 val, size_t size) -{ - if_state.csr = val & 0xff; -} -#endif - -CONST char *if_description(DEVICE *dptr) -{ - return "Integrated Floppy Disk"; -} - -t_stat if_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) -{ - fprintf(st, "Integrated Floppy Disk (IFLOPPY)\n\n"); - fprintf(st, "The IFLOPPY device implements the integrated 720 KB floppy disk\n"); - fprintf(st, "of the 3B2/400. A single floppy disk is supported on the controller.\n\n"); - fprintf(st, "The format of the diskette media is as follows:\n\n"); - fprintf(st, " Size Sides Tracks/Side Sectors/Track Bytes/Track\n"); - fprintf(st, " ------ ----- ----------- ------------- -----------\n"); - fprintf(st, " 720 KB 2 80 9 512\n\n"); - fprintf(st, "Physical media is Double Sided/Quad Density, 96 tpi, 250kbps MFM encoding.\n"); - - fprint_set_help(st, dptr); - fprint_show_help(st, dptr); - fprint_reg_help(st, dptr); - - return SCPE_OK; -} - -/* - * Compute the offset of the currently selected C/H/S (in # of sectors) - */ -static SIM_INLINE uint32 if_lba() -{ - /* Reminder that sectors are numbered 1-9 instead - * of being numbered 0-8 */ - return((if_state.track * IF_SEC_COUNT * 2) + - (if_state.side * IF_SEC_COUNT) + - (if_state.sector - 1)); -} - -void if_after_dma() -{ - if_state.drq = FALSE; - if_state.status &= ~IF_DRQ; -} +/* 3b2_if.c: TMS2797 Integrated Floppy Controller + + Copyright (c) 2017-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#include "3b2_if.h" + +#include "sim_disk.h" + +#include "3b2_cpu.h" +#include "3b2_csr.h" + +/* Static function declarations */ +static SIM_INLINE uint32 if_lba(); + +/* + * Disk Format: + * ------------ + * + * - 80 Tracks + * - 9 Sectors per track + * - 2 heads + * - 512 bytes per sector + * + * 80 * 9 * 2 * 512 = 720KB + * + */ + +#define IF_STEP_DELAY 300 /* us */ +#define IF_R_DELAY 6500 /* us */ +#define IF_W_DELAY 7000 /* us */ +#define IF_VERIFY_DELAY 2000 /* us */ +#define IF_HLD_DELAY 6000 /* us */ +#define IF_HSW_DELAY 4000 /* us */ + +#if defined(REV3) +#define SET_INT CPU_SET_INT(INT_FLOPPY) +#define CLR_INT CPU_CLR_INT(INT_FLOPPY) +#else +#define SET_INT do { \ + CPU_SET_INT(INT_FLOPPY); \ + SET_CSR(CSRDISK); \ + } while(0) +#define CLR_INT do { \ + CPU_CLR_INT(INT_FLOPPY); \ + CLR_CSR(CSRDISK); \ + } while(0) +#endif + +UNIT if_unit = { + UDATA (&if_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BINK+UNIT_ROABLE, + IF_DSK_SIZE_SECS) +}; + +REG if_reg[] = { + { NULL } +}; + +DEVICE if_dev = { + "IFLOPPY", &if_unit, if_reg, NULL, + 1, 16, 8, 1, 16, 8, + NULL, NULL, &if_reset, + NULL, &if_attach, &if_detach, NULL, + DEV_DEBUG|DEV_DISK|DEV_SECTORS, 0, sys_deb_tab, + NULL, NULL, &if_help, NULL, NULL, + &if_description +}; + +IF_STATE if_state; +uint8 if_buf[IF_SEC_SIZE]; +uint32 if_sec_ptr = 0; + +/* Function implementation */ + +static SIM_INLINE void if_activate(uint32 delay_us) +{ + sim_activate_after(&if_unit, delay_us); +} + +t_stat if_svc(UNIT *uptr) +{ + uint32 lba; /* Logical block address for write */ + t_seccnt sectswritten; + + if_state.status &= ~(IF_BUSY); + + switch(if_state.cmd & 0xf0) { + case IF_RESTORE: + if_state.status = (IF_TK_0|IF_HEAD_LOADED); + break; + case IF_SEEK: + if_state.status = IF_HEAD_LOADED; + if (if_state.track == 0) { + if_state.status |= IF_TK_0; + } + break; + case IF_WRITE_SEC: + lba = if_lba(); + + /* If we're read-only, don't actually do anything. */ + if (if_unit.flags & UNIT_RO) { + break; + } + + if (sim_disk_wrsect(&if_unit, lba, if_buf, §swritten, 1) == SCPE_OK) { + if (sectswritten != 1) { + sim_debug(EXECUTE_MSG, &if_dev, + "ERROR: ASKED TO wRITE ONE SECTOR, WROTE %d\n", + sectswritten); + } + } + + break; + } + + if_state.cmd = 0; + + /* Request an interrupt */ + sim_debug(IRQ_MSG, &if_dev, "\tINTR\n"); + SET_INT; + + return SCPE_OK; +} + +t_stat if_reset(DEVICE *dptr) +{ + if_state.status = IF_TK_0; + if_state.track = 0; + if_state.sector = 1; + if_sec_ptr = 0; + + return SCPE_OK; +} + +t_stat if_attach(UNIT *uptr, CONST char *cptr) +{ + return sim_disk_attach(uptr, cptr, 512, 1, TRUE, 0, NULL, 0, 0); +} + +t_stat if_detach(UNIT *uptr) +{ + return sim_disk_detach(uptr); +} + +uint32 if_read(uint32 pa, size_t size) { + uint8 reg, data; + UNIT *uptr; + + uptr = &(if_dev.units[0]); + reg = (uint8)(pa - IFBASE); + + switch (reg) { + case IF_STATUS_REG: + data = if_state.status; + /* If there's no image attached, we're not ready */ + if ((uptr->flags & (UNIT_ATT|UNIT_BUF)) == 0) { + data |= IF_NRDY; + } + /* Reading the status register always de-asserts the IRQ line */ + CLR_INT; + sim_debug(READ_MSG, &if_dev, "\tSTATUS\t%02x\n", data); + break; + case IF_TRACK_REG: + data = if_state.track; + sim_debug(READ_MSG, &if_dev, "\tTRACK\t%02x\n", data); + break; + case IF_SECTOR_REG: + data = if_state.sector; + sim_debug(READ_MSG, &if_dev, "\tSECTOR\t%02x\n", data); + break; + case IF_DATA_REG: + if_state.status &= ~IF_DRQ; + + if (((uptr->flags & (UNIT_ATT|UNIT_BUF)) == 0) || + ((if_state.cmd & 0xf0) != IF_READ_SEC && + (if_state.cmd & 0xf0) != IF_READ_SEC_M)) { + /* Not attached, or not a read command */ + + switch (if_state.cmd & 0xf0) { + case IF_READ_ADDR: + /* Special state machine. */ + switch (if_state.read_addr_ptr++) { + case 0: + if_state.data = if_state.track; + break; + case 1: + if_state.data = if_state.side; + break; + case 2: + if_state.data = if_state.sector; + break; + case 3: + if_state.data = 2; /* 512 byte */ + break; + case 4: + /* TODO: Checksum */ + if_state.data = 0; + break; + case 5: + /* TODO: Checksum */ + if_state.data = 0; + if_state.read_addr_ptr = 0; + break; + } + } + + sim_debug(READ_MSG, &if_dev, "\tDATA\t%02x\n", if_state.data); + return if_state.data; + } + + data = if_buf[if_sec_ptr++]; + sim_debug(READ_MSG, &if_dev, "\tDATA\t%02x\n", data); + + if (if_sec_ptr >= IF_SEC_SIZE) { + if_sec_ptr = 0; + } + + break; + default: + data = 0xffu; // Compiler warning + break; + } + + return data; +} + +/* Handle the most recently received command */ +void if_handle_command() +{ + uint32 delay_ms = 0; + uint32 head_switch_delay = 0; + uint32 head_load_delay = 0; + uint32 lba; /* Logical block address */ + t_seccnt sectsread; + + if_sec_ptr = 0; + + /* We're starting a new command. */ + if_state.status = IF_BUSY; + + /* Clear read addr state */ + if_state.read_addr_ptr = 0; + + switch(if_state.cmd & 0xf0) { + case IF_RESTORE: + case IF_SEEK: + case IF_STEP: + case IF_STEP_T: + case IF_STEP_IN: + case IF_STEP_IN_T: + case IF_STEP_OUT: + case IF_STEP_OUT_T: + if_state.cmd_type = 1; + if (if_state.cmd & IF_H_FLAG) { + head_load_delay = IF_HLD_DELAY; + } + break; + + case IF_READ_SEC: + case IF_READ_SEC_M: + case IF_WRITE_SEC: + case IF_WRITE_SEC_M: + if_state.cmd_type = 2; +#if defined(REV2) + if (((if_state.cmd & IF_U_FLAG) >> 1) != if_state.side) { + head_switch_delay = IF_HSW_DELAY; + if_state.side = (if_state.cmd & IF_U_FLAG) >> 1; + } +#endif + break; + + case IF_READ_ADDR: + case IF_READ_TRACK: + case IF_WRITE_TRACK: + if_state.cmd_type = 3; +#if defined(REV2) + if (((if_state.cmd & IF_U_FLAG) >> 1) != if_state.side) { + head_switch_delay = IF_HSW_DELAY; + if_state.side = (if_state.cmd & IF_U_FLAG) >> 1; + } +#endif + break; + } + + switch(if_state.cmd & 0xf0) { + case IF_RESTORE: + sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tRestore\n", if_state.cmd); + + /* Reset HLT */ + if_state.status &= ~IF_HEAD_LOADED; + + if (if_unit.flags & UNIT_RO) { + if_state.status |= IF_WP; + } + + /* If head should be loaded immediately, do so now */ + if (if_state.cmd & IF_H_FLAG) { + if_state.status |= IF_HEAD_LOADED; + } + + if (if_state.track == 0) { + if_state.status |= IF_TK_0; + if_state.track = 1; /* Kind of a gross hack */ + } + + if (if_state.cmd & IF_V_FLAG) { + delay_ms = (IF_STEP_DELAY * if_state.track) + IF_VERIFY_DELAY; + } else { + delay_ms = IF_STEP_DELAY * if_state.track; + } + + if_activate(delay_ms); + + if_state.data = 0; + if_state.track = 0; + break; + + case IF_STEP: + case IF_STEP_T: + sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tStep\n", if_state.cmd); + if (if_unit.flags & UNIT_RO) { + if_state.status |= IF_WP; + } + if_activate(IF_STEP_DELAY); + if_state.track = (uint8) MIN(MAX((int) if_state.track + if_state.step_dir, 0), 0x4f); + break; + case IF_STEP_IN: + case IF_STEP_IN_T: + sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tStep In\n", if_state.cmd); + if (if_unit.flags & UNIT_RO) { + if_state.status |= IF_WP; + } + if_state.step_dir = IF_STEP_IN_DIR; + if_state.track = (uint8) MAX((int) if_state.track + if_state.step_dir, 0); + if_activate(IF_STEP_DELAY); + break; + case IF_STEP_OUT: + case IF_STEP_OUT_T: + sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tStep Out\n", if_state.cmd); + if (if_unit.flags & UNIT_RO) { + if_state.status |= IF_WP; + } + if_state.step_dir = IF_STEP_OUT_DIR; + if_state.track = (uint8) MIN((int) if_state.track + if_state.step_dir, 0x4f); + if_activate(IF_STEP_DELAY); + break; + case IF_SEEK: + sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tSeek\n", if_state.cmd); + + /* Reset HLT */ + if_state.status &= ~IF_HEAD_LOADED; + + if (if_unit.flags & UNIT_RO) { + if_state.status |= IF_WP; + } + + /* If head should be loaded immediately, do so now */ + if (if_state.cmd & IF_H_FLAG) { + if_state.status |= IF_HEAD_LOADED; + } + + /* Save the direction for stepping */ + if (if_state.data > if_state.track) { + if_state.step_dir = IF_STEP_IN_DIR; + } else if (if_state.data < if_state.track) { + if_state.step_dir = IF_STEP_OUT_DIR; + } + + /* The new track is in the data register */ + + if (if_state.data > IF_TRACK_COUNT-1) { + if_state.data = IF_TRACK_COUNT-1; + } + + if (if_state.data == 0) { + if_state.status |= IF_TK_0; + } else { + if_state.status &= ~(IF_TK_0); + } + + delay_ms = (uint32) abs(if_state.data - if_state.track); + + if (delay_ms == 0) { + delay_ms++; + } + + if (if_state.cmd & IF_V_FLAG) { + if_activate((IF_STEP_DELAY * delay_ms) + IF_VERIFY_DELAY + head_load_delay); + } else { + if_activate((IF_STEP_DELAY * delay_ms) + head_load_delay); + } + + if_state.track = if_state.data; + break; + + case IF_READ_SEC: + lba = if_lba(); + + sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tRead Sector %d/%d/%d (lba=%d)\n", + if_state.cmd, if_state.track, if_state.side, if_state.sector, lba); + + if (sim_disk_rdsect(&if_unit, lba, if_buf, §sread, 1) == SCPE_OK) { + if (sectsread != 1) { + sim_debug(EXECUTE_MSG, &if_dev, + "ERROR: ASKED TO READ ONE SECTOR, READ %d\n", + sectsread); + } + /* We set DRQ right away to request the transfer. */ + if_state.drq = TRUE; + if_state.status |= IF_DRQ; + if (if_state.cmd & IF_E_FLAG) { + if_activate(IF_R_DELAY + IF_VERIFY_DELAY + head_switch_delay); + } else { + if_activate(IF_R_DELAY + head_switch_delay); + } + } + + break; + case IF_READ_SEC_M: + /* Not yet implemented. Halt the emulator. */ + sim_debug(EXECUTE_MSG, &if_dev, + "\tCOMMAND\t%02x\tRead Sector (Multi) - NOT IMPLEMENTED\n", + if_state.cmd); + stop_reason = STOP_ERR; + break; + case IF_WRITE_SEC: + lba = if_lba(); + + sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tWrite Sector %d/%d/%d (lba=%d)\n", + if_state.cmd, if_state.track, if_state.side, if_state.sector, lba); + + if (if_unit.flags & UNIT_RO) { + if_state.status |= IF_WP; + sim_debug(EXECUTE_MSG, &if_dev, "\tWON'T WRITE: WRITE PROTECTED.\n"); + /* Still cause an interrupt... */ + if_activate(IF_W_DELAY + head_switch_delay); + /* But don't set DRQ and ask for a transfer. */ + break; + } + + /* We set DRQ right away to request the transfer. Data will + * be written by the host into our buffer by 512 writes to the + * data register. When the IF device later activates, the data + * will actually be written. */ + if_state.drq = TRUE; + if_state.status |= IF_DRQ; + if (if_state.cmd & IF_E_FLAG) { + if_activate(IF_W_DELAY + IF_VERIFY_DELAY + head_switch_delay); + } else { + if_activate(IF_W_DELAY + head_switch_delay); + } + break; + case IF_WRITE_SEC_M: + /* Not yet implemented. Halt the emulator. */ + sim_debug(EXECUTE_MSG, &if_dev, + "\tCOMMAND\t%02x\tWrite Sector (Multi) - NOT IMPLEMENTED\n", + if_state.cmd); + stop_reason = STOP_ERR; + break; + case IF_READ_ADDR: + sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tRead Address\n", if_state.cmd); + if_state.drq = TRUE; + if_state.status |= IF_DRQ; + if_activate(IF_R_DELAY); + break; + case IF_READ_TRACK: + sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tRead Track\n", if_state.cmd); + /* Not yet implemented. Halt the emulator. */ + stop_reason = STOP_ERR; + break; + case IF_WRITE_TRACK: + sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tWrite Track\n", if_state.cmd); + /* Set DRQ */ + if_state.drq = TRUE; + if_state.status |= IF_DRQ; + if (if_state.cmd & IF_E_FLAG) { + if_activate(IF_W_DELAY + IF_VERIFY_DELAY + head_switch_delay); + } else { + if_activate(IF_W_DELAY + head_switch_delay); + } + break; + } +} + +void if_write(uint32 pa, uint32 val, size_t size) +{ + UNIT *uptr; + uint8 reg; + + val = val & 0xff; + + uptr = &(if_dev.units[0]); + reg = (uint8) (pa - IFBASE); + + switch (reg) { + case IF_CMD_REG: + if_state.cmd = (uint8) val; + /* Writing to the command register always de-asserts the IRQ line */ + CLR_INT; + + /* If this is a FORCE INTERRUPT, handle it immediately. All + * other commands require that the unit be attached and a + * diskette loaded. This one does not. */ + if ((if_state.cmd & 0xf0) == IF_FORCE_INT) { + sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tForce Interrupt\n", if_state.cmd); + if_state.status = 0; + + if ((uptr->flags & UNIT_ATT) && if_state.track == 0) { + if_state.status |= (IF_TK_0|IF_HEAD_LOADED); + } + + if ((if_state.cmd & 0xf) == 0) { + sim_cancel(&if_unit); +#if defined(REV2) + CLR_INT; /* TODO: Confirm this is right */ +#endif + } else if ((if_state.cmd & 0x8) == 0x8) { + if_state.status |= IF_DRQ; + SET_INT; + } + break; + } + + if ((uptr->flags & UNIT_ATT) == 0) { + /* If not attached, do nothing */ + break; + } + + if_handle_command(); + break; + case IF_TRACK_REG: + if_state.track = (uint8) val; + sim_debug(WRITE_MSG, &if_dev, "\tTRACK\t%02x\n", val); + break; + case IF_SECTOR_REG: + if_state.sector = (uint8) val; + sim_debug(WRITE_MSG, &if_dev, "\tSECTOR\t%02x\n", val); + break; + case IF_DATA_REG: + if_state.data = (uint8) val; + + sim_debug(WRITE_MSG, &if_dev, "\tDATA\t%02x\n", val); + + if ((uptr->flags & UNIT_ATT) == 0) { + /* Not attached */ + break; + } + + if ((if_state.cmd & 0xf0) == IF_WRITE_TRACK) { + /* We intentionally ignore WRITE TRACK data, because + * This is only used for low-level MFM formatting, + * which we do not emulate. */ + } else if ((if_state.cmd & 0xf0) == IF_WRITE_SEC || + (if_state.cmd & 0xf0) == IF_WRITE_SEC_M) { + + if_buf[if_sec_ptr++] = (uint8) val; + + if (if_sec_ptr >= IF_SEC_SIZE) { + if_sec_ptr = 0; + } + } + + break; + default: + break; + } +} + +#if defined(REV3) +uint32 if_csr_read(uint32 pa, size_t size) +{ + return (uint32)(if_state.csr); +} + +void if_csr_write(uint32 pa, uint32 val, size_t size) +{ + if_state.csr = val & 0xff; +} +#endif + +CONST char *if_description(DEVICE *dptr) +{ + return "Integrated Floppy Disk"; +} + +t_stat if_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf(st, "Integrated Floppy Disk (IFLOPPY)\n\n"); + fprintf(st, "The IFLOPPY device implements the integrated 720 KB floppy disk\n"); + fprintf(st, "of the 3B2/400. A single floppy disk is supported on the controller.\n\n"); + fprintf(st, "The format of the diskette media is as follows:\n\n"); + fprintf(st, " Size Sides Tracks/Side Sectors/Track Bytes/Track\n"); + fprintf(st, " ------ ----- ----------- ------------- -----------\n"); + fprintf(st, " 720 KB 2 80 9 512\n\n"); + fprintf(st, "Physical media is Double Sided/Quad Density, 96 tpi, 250kbps MFM encoding.\n"); + + fprint_set_help(st, dptr); + fprint_show_help(st, dptr); + fprint_reg_help(st, dptr); + + return SCPE_OK; +} + +/* + * Compute the offset of the currently selected C/H/S (in # of sectors) + */ +static SIM_INLINE uint32 if_lba() +{ + /* Reminder that sectors are numbered 1-9 instead + * of being numbered 0-8 */ + return((if_state.track * IF_SEC_COUNT * 2) + + (if_state.side * IF_SEC_COUNT) + + (if_state.sector - 1)); +} + +void if_after_dma() +{ + if_state.drq = FALSE; + if_state.status &= ~IF_DRQ; +} diff --git a/3B2/3b2_if.h b/3B2/3b2_if.h index d27640e4..256c084b 100644 --- a/3B2/3b2_if.h +++ b/3B2/3b2_if.h @@ -1,131 +1,131 @@ -/* 3b2_if.h: TMS2797 Integrated Floppy Controller - - Copyright (c) 2017-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#ifndef __3B2_IF_H__ -#define __3B2_IF_H__ - -#include "3b2_defs.h" - -typedef struct { - uint8 data; - uint8 cmd; - uint8 cmd_type; - uint8 status; - uint8 track; - uint8 sector; - uint8 side; - uint8 read_addr_ptr; - int8 step_dir; - t_bool drq; -#if defined(REV3) - uint8 csr; -#endif -} IF_STATE; - -/* Status Bits */ -#define IF_BUSY 0x01 -#define IF_DRQ 0x02 -#define IF_INDEX 0x02 -#define IF_TK_0 0x04 -#define IF_LOST_DATA 0x04 -#define IF_CRC_ERR 0x08 -#define IF_SEEK_ERR 0x10 -#define IF_RNF 0x10 -#define IF_HEAD_LOADED 0x20 -#define IF_RECORD_TYPE 0x20 -#define IF_WP 0x40 -#define IF_NRDY 0x80 - -/* Type I Commands */ -#define IF_RESTORE 0x00 -#define IF_SEEK 0x10 -#define IF_STEP 0x20 -#define IF_STEP_T 0x30 -#define IF_STEP_IN 0x40 -#define IF_STEP_IN_T 0x50 -#define IF_STEP_OUT 0x60 -#define IF_STEP_OUT_T 0x70 - -/* Type II Commands */ -#define IF_READ_SEC 0x80 -#define IF_READ_SEC_M 0x90 -#define IF_WRITE_SEC 0xA0 -#define IF_WRITE_SEC_M 0xB0 - -/* Type III Commands */ -#define IF_READ_ADDR 0xC0 -#define IF_READ_TRACK 0xE0 -#define IF_WRITE_TRACK 0xF0 - -/* Type IV Command */ -#define IF_FORCE_INT 0xD0 - -/* Command flags */ - -#define IF_C_FLAG 0x02 -#define IF_V_FLAG 0x04 -#define IF_E_FLAG 0x04 -#define IF_U_FLAG 0x02 -#define IF_H_FLAG 0x08 -#define IF_S_FLAG 0x10 - -/* Constants */ - -#define IF_SIDES 2 -#define IF_SEC_COUNT 9 -#define IF_SEC_SIZE 512 -#define IF_TRACK_SIZE 4608 -#define IF_TRACK_COUNT 80 - -#define IF_STEP_IN_DIR 1 -#define IF_STEP_OUT_DIR -1 - -#define IF_DSK_SIZE_SECS (IF_SIDES * IF_TRACK_COUNT * IF_SEC_COUNT) - -/* Function prototypes */ - -t_stat if_svc(UNIT *uptr); -t_stat if_reset(DEVICE *dptr); -t_stat if_attach(UNIT *uptr, CONST char *cptr); -t_stat if_detach(UNIT *uptr); -uint32 if_read(uint32 pa, size_t size); -void if_write(uint32 pa, uint32 val, size_t size); -#if defined(REV3) -uint32 if_csr_read(uint32 pa, size_t size); -void if_csr_write(uint32 pa, uint32 val, size_t size); -#endif -void if_handle_command(); -void if_after_dma(); -CONST char *if_description(DEVICE *dptr); -t_stat if_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); - -extern IF_STATE if_state; - -#endif +/* 3b2_if.h: TMS2797 Integrated Floppy Controller + + Copyright (c) 2017-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#ifndef __3B2_IF_H__ +#define __3B2_IF_H__ + +#include "3b2_defs.h" + +typedef struct { + uint8 data; + uint8 cmd; + uint8 cmd_type; + uint8 status; + uint8 track; + uint8 sector; + uint8 side; + uint8 read_addr_ptr; + int8 step_dir; + t_bool drq; +#if defined(REV3) + uint8 csr; +#endif +} IF_STATE; + +/* Status Bits */ +#define IF_BUSY 0x01 +#define IF_DRQ 0x02 +#define IF_INDEX 0x02 +#define IF_TK_0 0x04 +#define IF_LOST_DATA 0x04 +#define IF_CRC_ERR 0x08 +#define IF_SEEK_ERR 0x10 +#define IF_RNF 0x10 +#define IF_HEAD_LOADED 0x20 +#define IF_RECORD_TYPE 0x20 +#define IF_WP 0x40 +#define IF_NRDY 0x80 + +/* Type I Commands */ +#define IF_RESTORE 0x00 +#define IF_SEEK 0x10 +#define IF_STEP 0x20 +#define IF_STEP_T 0x30 +#define IF_STEP_IN 0x40 +#define IF_STEP_IN_T 0x50 +#define IF_STEP_OUT 0x60 +#define IF_STEP_OUT_T 0x70 + +/* Type II Commands */ +#define IF_READ_SEC 0x80 +#define IF_READ_SEC_M 0x90 +#define IF_WRITE_SEC 0xA0 +#define IF_WRITE_SEC_M 0xB0 + +/* Type III Commands */ +#define IF_READ_ADDR 0xC0 +#define IF_READ_TRACK 0xE0 +#define IF_WRITE_TRACK 0xF0 + +/* Type IV Command */ +#define IF_FORCE_INT 0xD0 + +/* Command flags */ + +#define IF_C_FLAG 0x02 +#define IF_V_FLAG 0x04 +#define IF_E_FLAG 0x04 +#define IF_U_FLAG 0x02 +#define IF_H_FLAG 0x08 +#define IF_S_FLAG 0x10 + +/* Constants */ + +#define IF_SIDES 2 +#define IF_SEC_COUNT 9 +#define IF_SEC_SIZE 512 +#define IF_TRACK_SIZE 4608 +#define IF_TRACK_COUNT 80 + +#define IF_STEP_IN_DIR 1 +#define IF_STEP_OUT_DIR -1 + +#define IF_DSK_SIZE_SECS (IF_SIDES * IF_TRACK_COUNT * IF_SEC_COUNT) + +/* Function prototypes */ + +t_stat if_svc(UNIT *uptr); +t_stat if_reset(DEVICE *dptr); +t_stat if_attach(UNIT *uptr, CONST char *cptr); +t_stat if_detach(UNIT *uptr); +uint32 if_read(uint32 pa, size_t size); +void if_write(uint32 pa, uint32 val, size_t size); +#if defined(REV3) +uint32 if_csr_read(uint32 pa, size_t size); +void if_csr_write(uint32 pa, uint32 val, size_t size); +#endif +void if_handle_command(); +void if_after_dma(); +CONST char *if_description(DEVICE *dptr); +t_stat if_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); + +extern IF_STATE if_state; + +#endif diff --git a/3B2/3b2_io.c b/3B2/3b2_io.c index 5883eecd..80d2df51 100644 --- a/3B2/3b2_io.c +++ b/3B2/3b2_io.c @@ -1,772 +1,772 @@ -/* 3b2_io.c: Common I/O (CIO) Feature Card Support - - Copyright (c) 2017-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#include "3b2_io.h" - -#include "3b2_cpu.h" -#include "3b2_csr.h" -#include "3b2_dmac.h" -#include "3b2_if.h" -#include "3b2_iu.h" -#include "3b2_mem.h" -#include "3b2_mmu.h" -#include "3b2_stddev.h" -#include "3b2_timer.h" - -#if defined(REV2) -#include "3b2_id.h" -#endif - -CIO_STATE cio[CIO_SLOTS] = {{0}}; -uint16 cio_int_req = 0; /* Bitset of card slots requesting interrupts */ - -#if defined(REV3) -iolink iotable[] = { - { MMUBASE, MMUBASE+MMUSIZE, &mmu_read, &mmu_write }, - { IFBASE, IFBASE+IFSIZE, &if_read, &if_write }, - { IFCSRBASE, IFCSRBASE+IFCSRSIZE, &if_csr_read, &if_csr_write }, - { FLTLBASE, FLTLBASE+FLTLSIZE, &flt_read, &flt_write }, - { FLTHBASE, FLTHBASE+FLTHSIZE, &flt_read, &flt_write }, - { NVRBASE, NVRBASE+NVRSIZE, &nvram_read, &nvram_write }, - { TIMERBASE, TIMERBASE+TIMERSIZE, &timer_read, &timer_write }, - { CSRBASE, CSRBASE+CSRSIZE, &csr_read, &csr_write }, - { IUBASE, IUBASE+IUSIZE, &iu_read, &iu_write }, - { DMAIUABASE, DMAIUABASE+DMAIUASIZE, &dmac_read, &dmac_write }, - { DMAIUBBASE, DMAIUBBASE+DMAIUBSIZE, &dmac_read, &dmac_write }, - { DMACBASE, DMACBASE+DMACSIZE, &dmac_read, &dmac_write }, - { DMAIFBASE, DMAIFBASE+DMAIFSIZE, &dmac_read, &dmac_write }, - { TODBASE, TODBASE+TODSIZE, &tod_read, &tod_write }, - { 0, 0, NULL, NULL} -}; -#else -iolink iotable[] = { - { MMUBASE, MMUBASE+MMUSIZE, &mmu_read, &mmu_write }, - { IFBASE, IFBASE+IFSIZE, &if_read, &if_write }, - { IDBASE, IDBASE+IDSIZE, &id_read, &id_write }, - { DMAIDBASE, DMAIDBASE+DMAIDSIZE, &dmac_read, &dmac_write }, - { NVRBASE, NVRBASE+NVRSIZE, &nvram_read, &nvram_write }, - { TIMERBASE, TIMERBASE+TIMERSIZE, &timer_read, &timer_write }, - { CSRBASE, CSRBASE+CSRSIZE, &csr_read, &csr_write }, - { IUBASE, IUBASE+IUSIZE, &iu_read, &iu_write }, - { DMAIUABASE, DMAIUABASE+DMAIUASIZE, &dmac_read, &dmac_write }, - { DMAIUBBASE, DMAIUBBASE+DMAIUBSIZE, &dmac_read, &dmac_write }, - { DMACBASE, DMACBASE+DMACSIZE, &dmac_read, &dmac_write }, - { DMAIFBASE, DMAIFBASE+DMAIFSIZE, &dmac_read, &dmac_write }, - { TODBASE, TODBASE+TODSIZE, &tod_read, &tod_write }, - { 0, 0, NULL, NULL} -}; -#endif - -/* - * Insert a CIO card into the backplane. - * - * If a space could be found, SPCE_OK is returned, and the slot the - * card was installed in is placed in `slot`. - * - * If no room is availalbe, return SCPE_NXM. - */ -t_stat cio_install(uint16 id, - CONST char *name, - uint8 ipl, - void (*exp_handler)(uint8 slot), - void (*full_handler)(uint8 slot), - void (*sysgen)(uint8 slot), - void (*reset_handler)(uint8 slot), - uint8 *slot) -{ - uint8 s; - - for (s = 0; s < CIO_SLOTS; s++) { - sim_debug(EXECUTE_MSG, &cpu_dev, - "[cio_install] cio[%d]: populated=%d, id=%d\n", - s, cio[s].populated, cio[s].id); - if (!cio[s].populated) { - sim_debug(EXECUTE_MSG, &cpu_dev, - "[cio_install] >>> I found a free slot! Slot #%d has nothing\n", s); - *slot = s; - /* Ensure the slot is in a clean state */ - cio_remove(s); - /* Populate the slot */ - cio[s].populated = TRUE; - cio[s].id = id; - cio[s].ipl = ipl; - strncpy(cio[s].name, name, CIO_NAME_LEN); - cio[s].exp_handler = exp_handler; - cio[s].full_handler = full_handler; - cio[s].sysgen = sysgen; - cio[s].reset_handler = reset_handler; - return SCPE_OK; - } - } - - return SCPE_NXM; -} - -/* - * Remove a CIO card from the specified backplane slot. - */ -void cio_remove(uint8 slot) -{ - memset(&cio[slot], 0, sizeof(CIO_STATE)); - /* cio[slot].populated = FALSE; */ - CIO_CLR_INT(slot); -} - -/* - * Remove all CIO cards of the matching type. - */ -void cio_remove_all(uint16 id) -{ - int i; - - for (i = 0; i < CIO_SLOTS; i++) { - if (cio[i].populated && cio[i].id == id) { - cio_remove(i); - } - } -} - -/* - * A braindead CRC32 calculator. - * - * This is overkill for what we need: A simple way to tag the contents - * of a block of memory uploaded to a CIO card (so we can - * differentiate between desired functions without actually having to - * disassemble and understand 80186 code!) - */ -uint32 cio_crc32_shift(uint32 crc, uint8 data) -{ - uint8 i; - - crc = ~crc; - crc ^= data; - for (i = 0; i < 8; i++) { - if (crc & 1) { - crc = (crc >> 1) ^ CRC_POLYNOMIAL; - } else { - crc = crc >> 1; - } - } - - return ~crc; -} - -void cio_sysgen(uint8 slot) -{ - uint32 sysgen_p; - - sysgen_p = pread_w(SYSGEN_PTR, BUS_PER); - - sim_debug(CIO_DBG, &cpu_dev, - "[SYSGEN] Starting sysgen for card %d (%s). sysgen_p=%08x\n", - slot, cio[slot].name, sysgen_p); - - /* seqbit is always reset to 0 on completion */ - cio[slot].seqbit = 0; - - cio[slot].rqp = pread_w(sysgen_p, BUS_PER); - cio[slot].cqp = pread_w(sysgen_p + 4, BUS_PER); - cio[slot].rqs = pread_b(sysgen_p + 8, BUS_PER); - cio[slot].cqs = pread_b(sysgen_p + 9, BUS_PER); - cio[slot].ivec = pread_b(sysgen_p + 10, BUS_PER); - cio[slot].no_rque = pread_b(sysgen_p + 11, BUS_PER); - - sim_debug(CIO_DBG, &cpu_dev, - "[SYSGEN] sysgen rqp = %08x\n", - cio[slot].rqp); - sim_debug(CIO_DBG, &cpu_dev, - "[SYSGEN] sysgen cqp = %08x\n", - cio[slot].cqp); - sim_debug(CIO_DBG, &cpu_dev, - "[SYSGEN] sysgen rqs = %02x\n", - cio[slot].rqs); - sim_debug(CIO_DBG, &cpu_dev, - "[SYSGEN] sysgen cqs = %02x\n", - cio[slot].cqs); - sim_debug(CIO_DBG, &cpu_dev, - "[SYSGEN] sysgen ivec = %02x\n", - cio[slot].ivec); - sim_debug(CIO_DBG, &cpu_dev, - "[SYSGEN] sysgen no_rque = %02x\n", - cio[slot].no_rque); - - /* If the card has a custom sysgen handler, run it */ - if (cio[slot].sysgen != NULL) { - cio[slot].sysgen(slot); - } else { - sim_debug(CIO_DBG, &cpu_dev, - "[cio_sysgen] Not running custom sysgen.\n"); - } -} - -void cio_cexpress(uint8 slot, uint32 esize, cio_entry *cqe, uint8 *app_data) -{ - uint32 i, cqp; - - cqp = cio[slot].cqp; - - sim_debug(CIO_DBG, &cpu_dev, - "[cio_cexpress] [%s] cqp = %08x seqbit = %d\n", - cio[slot].name, cqp, cio[slot].seqbit); - - cio[slot].seqbit ^= 1; - - cqe->subdevice |= (cio[slot].seqbit << 6); - - pwrite_h(cqp, cqe->byte_count, BUS_PER); - pwrite_b(cqp + 2, cqe->subdevice, BUS_PER); - pwrite_b(cqp + 3, cqe->opcode, BUS_PER); - pwrite_w(cqp + 4, cqe->address, BUS_PER); - - /* Write application-specific data. */ - for (i = 0; i < (esize - QESIZE); i++) { - pwrite_b(cqp + 8 + i, app_data[i], BUS_PER); - } -} - -void cio_cqueue(uint8 slot, uint8 cmd_stat, uint32 esize, - cio_entry *cqe, uint8 *app_data) -{ - uint32 i, cqp, top; - uint16 lp; - - /* Apply the CMD/STAT bit */ - cqe->subdevice |= (cmd_stat << 7); - - /* Get the physical address of the completion queue - * in main memory */ - cqp = cio[slot].cqp; - - /* Get the physical address of the first entry in - * the completion queue */ - top = cqp + esize + LUSIZE; - - /* Get the load pointer. This is a 16-bit absolute offset - * from the top of the queue to the start of the entry. */ - lp = pread_h(cqp + esize, BUS_PER); - - /* Load the entry at the supplied address */ - pwrite_h(top + lp, cqe->byte_count, BUS_PER); - pwrite_b(top + lp + 2, cqe->subdevice, BUS_PER); - pwrite_b(top + lp + 3, cqe->opcode, BUS_PER); - pwrite_w(top + lp + 4, cqe->address, BUS_PER); - - /* Write application-specific data. */ - for (i = 0; i < (esize - QESIZE); i++) { - pwrite_b(top + lp + 8 + i, app_data[i], BUS_PER); - } - - /* Increment the load pointer to the next queue location. - * If we go past the end of the queue, wrap around to the - * start of the queue */ - if (cio[slot].cqs > 0) { - lp = (lp + esize) % (esize * cio[slot].cqs); - /* Store it back to the correct location */ - pwrite_h(cqp + esize, lp, BUS_PER); - } -} - -/* - * Retrieve the Express Entry from the Request Queue - */ -void cio_rexpress(uint8 slot, uint32 esize, cio_entry *rqe, uint8 *app_data) -{ - uint32 i; - uint32 rqp; - - rqp = cio[slot].rqp; - - /* Unload the express entry from the request queue */ - rqe->byte_count = pread_h(rqp, BUS_PER); - rqe->subdevice = pread_b(rqp + 2, BUS_PER); - rqe->opcode = pread_b(rqp + 3, BUS_PER); - rqe->address = pread_w(rqp + 4, BUS_PER); - - for (i = 0; i < (esize - QESIZE); i++) { - app_data[i] = pread_b(rqp + 8 + i, BUS_PER); - } -} - -/* - * Retrieve an entry from the Request Queue. This function - * returns the load pointer that points to the NEXT available slot. - * This may be used by callers to determine which queue(s) need to - * be serviced. - * - * Returns SCPE_OK on success, or SCPE_NXM if no entry was found. - * - */ -t_stat cio_rqueue(uint8 slot, uint32 qnum, uint32 esize, - cio_entry *rqe, uint8 *app_data) -{ - uint32 i, rqp, top; - uint16 lp, ulp; - - /* Get the physical address of the request queue in main memory */ - rqp = cio[slot].rqp + - esize + - (qnum * (LUSIZE + (esize * cio[slot].rqs))); - - lp = pread_h(rqp, BUS_PER); - ulp = pread_h(rqp + 2, BUS_PER); - - /* Check to see if the request queue is empty. If it is, there's - * nothing to take. */ - if (lp == ulp) { - return SCPE_NXM; - } - - top = rqp + LUSIZE; - - /* Retrieve the entry at the supplied address */ - rqe->byte_count = pread_h(top + ulp, BUS_PER); - rqe->subdevice = pread_b(top + ulp + 2, BUS_PER); - rqe->opcode = pread_b(top + ulp + 3, BUS_PER); - rqe->address = pread_w(top + ulp + 4, BUS_PER); - - /* Read application-specific data. */ - for (i = 0; i < (esize - QESIZE); i++) { - app_data[i] = pread_b(top + ulp + 8 + i, BUS_PER); - } - - /* Increment the unload pointer to the next queue location. If we - * go past the end of the queue, wrap around to the start of the - * queue */ - if (cio[slot].rqs > 0) { - ulp = (ulp + esize) % (esize * cio[slot].rqs); - pwrite_h(rqp + 2, ulp, BUS_PER); - } - - return SCPE_OK; -} - -/* - * Return the Load Pointer for the given request queue - */ -uint16 cio_r_lp(uint8 slot, uint32 qnum, uint32 esize) -{ - uint32 rqp; - - rqp = cio[slot].rqp + - esize + - (qnum * (LUSIZE + (esize * cio[slot].rqs))); - - return pread_h(rqp, BUS_PER); -} - -/* - * Return the Unload Pointer for the given request queue - */ -uint16 cio_r_ulp(uint8 slot, uint32 qnum, uint32 esize) -{ - uint32 rqp; - - rqp = cio[slot].rqp + - esize + - (qnum * (LUSIZE + (esize * cio[slot].rqs))); - - return pread_h(rqp + 2, BUS_PER); -} - -uint16 cio_c_lp(uint8 slot, uint32 esize) -{ - uint32 cqp; - cqp = cio[slot].cqp + esize; - return pread_h(cqp, BUS_PER); -} - -uint16 cio_c_ulp(uint8 slot, uint32 esize) -{ - uint32 cqp; - cqp = cio[slot].cqp + esize; - return pread_h(cqp + 2, BUS_PER); -} - -/* - * Returns true if there is room in the completion queue - * for a new entry. - */ -t_bool cio_cqueue_avail(uint8 slot, uint32 esize) -{ - uint32 lp, ulp; - - lp = pread_h(cio[slot].cqp + esize, BUS_PER); - ulp = pread_h(cio[slot].cqp + esize + 2, BUS_PER); - - return(((lp + esize) % (cio[slot].cqs * esize)) != ulp); -} - -t_bool cio_rqueue_avail(uint8 slot, uint32 qnum, uint32 esize) -{ - uint32 rqp, lp, ulp; - - /* Get the physical address of the request queue in main memory */ - rqp = cio[slot].rqp + - esize + - (qnum * (LUSIZE + (esize * cio[slot].rqs))); - - lp = pread_h(rqp, BUS_PER); - ulp = pread_h(rqp + 2, BUS_PER); - - return(lp != ulp); -} - -uint32 io_read(uint32 pa, size_t size) -{ - iolink *p; - uint8 slot, reg, data; - -#if defined (REV3) - if (pa >= VCACHE_BOTTOM && pa < VCACHE_TOP) { - sim_debug(EXECUTE_MSG, &cpu_dev, - "[UBUB] (VCACHE) Read addr %08x\n", pa); - CSRBIT(CSRTIMO, TRUE); - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); - return 0; - } - - if (pa >= BUB_BOTTOM && pa < BUB_TOP) { - sim_debug(EXECUTE_MSG, &cpu_dev, - "[BUB] Read addr %08x\n", pa); - CSRBIT(CSRTIMO, TRUE); - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); - return 0; - } -#else - if (pa == MEMSIZE_REG) { - - /* The following values map to memory sizes: - 0x00: 512KB ( 524,288 B) - 0x01: 2MB (2,097,152 B) - 0x02: 1MB (1,048,576 B) - 0x03: 4MB (4,194,304 B) - */ - switch(MEM_SIZE) { - case 0x80000: /* 512KB */ - return 0; - case 0x100000: /* 1MB */ - return 2; - case 0x200000: /* 2MB */ - return 1; - case 0x400000: /* 4MB */ - return 3; - default: - return 0; - } - } -#endif - - /* CIO board area */ - if (pa >= CIO_BOTTOM && pa < CIO_TOP) { - slot = SLOT(pa); - reg = pa - CADDR(slot); - - if (!cio[slot].populated) { - /* Nothing lives here */ - sim_debug(IO_DBG, &cpu_dev, - "[READ] No card at slot=%d reg=%d\n", - slot, reg); - CSRBIT(CSRTIMO, TRUE); - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); - return 0; - } - - /* A normal SYSGEN sequence is: RESET -> INT0 -> INT1. - * However, there's a bug in the 3B2/400 DGMON test suite that - * runs on every startup. This diagnostic code performs a - * SYSGEN by calling RESET -> INT1 -> INT0. So, we must handle - * both orders. */ - - switch (reg) { - case IOF_ID: - case IOF_VEC: - switch(cio[slot].sysgen_s) { - case CIO_INT_NONE: /* We've never seen an INT0 or INT1 */ - case CIO_INT0: /* We've seen an INT0 but not an INT1. */ - cio[slot].sysgen_s |= CIO_INT0; - sim_debug(CIO_DBG, &cpu_dev, - "[READ] [%s] (%d INT0) ID\n", - cio[slot].name, slot); - /* Return the correct byte of our board ID */ - if (reg == IOF_ID) { - data = (cio[slot].id >> 8) & 0xff; - } else { - data = (cio[slot].id & 0xff); - } - break; - case CIO_INT1: /* We've seen an INT1 but not an INT0. Time to sysgen */ - cio[slot].sysgen_s |= CIO_INT0; - sim_debug(CIO_DBG, &cpu_dev, - "[READ] [%s] (%d INT0) SYSGEN\n", - cio[slot].name, slot); - cio_sysgen(slot); - data = cio[slot].ivec; - break; - case CIO_SYSGEN: /* We've already sysgen'ed */ - cio[slot].sysgen_s |= CIO_INT0; /* This must come BEFORE the exp_handler */ - sim_debug(CIO_DBG, &cpu_dev, - "[READ] [%s] (%d INT0) EXPRESS JOB\n", - cio[slot].name, slot); - cio[slot].exp_handler(slot); - data = cio[slot].ivec; - break; - default: - /* This should never happen */ - stop_reason = STOP_ERR; - sim_debug(CIO_DBG, &cpu_dev, - "[READ] [%s] (%d INT0) ERROR IN STATE MACHINE sysgen_s=%02x\n", - cio[slot].name, slot, cio[slot].sysgen_s); - data = 0; - break; - } - - return data; - case IOF_CTRL: - switch(cio[slot].sysgen_s) { - case CIO_INT_NONE: /* We've never seen an INT0 or INT1 */ - case CIO_INT1: /* We've seen an INT1 but not an INT0 */ - /* There's nothing to do in this instance */ - sim_debug(CIO_DBG, &cpu_dev, - "[READ] [%s] (%d INT1) IGNORED\n", - cio[slot].name, slot); - cio[slot].sysgen_s |= CIO_INT1; - break; - case CIO_INT0: /* We've seen an INT0 but not an INT1. Time to sysgen */ - sim_debug(CIO_DBG, &cpu_dev, - "[READ] [%s] (%d INT1) SYSGEN\n", - cio[slot].name, slot); - cio[slot].sysgen_s |= CIO_INT1; - cio_sysgen(slot); - break; - case CIO_SYSGEN: /* We've already sysgen'ed */ - sim_debug(CIO_DBG, &cpu_dev, - "[READ] [%s] (%d INT1) FULL\n", - cio[slot].name, slot); - cio[slot].sysgen_s |= CIO_INT1; /* This must come BEFORE the full handler */ - cio[slot].full_handler(slot); - break; - default: - /* This should never happen */ - stop_reason = STOP_ERR; - sim_debug(CIO_DBG, &cpu_dev, - "[READ] [%s] (%d INT1) ERROR IN STATE MACHINE sysgen_s=%02x\n", - cio[slot].name, slot, cio[slot].sysgen_s); - break; - } - - return 0; /* Data returned is arbitrary */ - case IOF_STAT: - sim_debug(CIO_DBG, &cpu_dev, - "[READ] [%s] (%d RESET)\n", - cio[slot].name, slot); - if (cio[slot].reset_handler) { - cio[slot].reset_handler(slot); - } - cio[slot].sysgen_s = 0; - return 0; /* Data returned is arbitrary */ - default: - /* We should never reach here, but if we do, there's - * nothing listening. */ - sim_debug(CIO_DBG, &cpu_dev, - "[READ] No card at slot=%d reg=%d\n", - slot, reg); - CSRBIT(CSRTIMO, TRUE); - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); - return 0; - } - } - - /* Memory-mapped IO devices */ - for (p = &iotable[0]; p->low != 0; p++) { - if ((pa >= p->low) && (pa < p->high) && p->read) { - return p->read(pa, size); - } - } - - /* Not found. */ - sim_debug(IO_DBG, &cpu_dev, - "[io_read] ADDR=%08x: No device found.\n", - pa); - CSRBIT(CSRTIMO, TRUE); - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); - return 0; -} - -void io_write(uint32 pa, uint32 val, size_t size) -{ - iolink *p; - uint8 slot, reg; - -#if defined(REV3) - if (pa >= VCACHE_BOTTOM && pa < VCACHE_TOP) { - sim_debug(EXECUTE_MSG, &cpu_dev, - "[UBUB] (VCACHE) Write addr %08x val 0x%x\n", - pa, val); - CSRBIT(CSRTIMO, TRUE); - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); - return; - } - - if (pa >= BUB_BOTTOM && pa < BUB_TOP) { - sim_debug(EXECUTE_MSG, &cpu_dev, - "[BUB] Write addr %08x val 0x%x\n", - pa, val); - CSRBIT(CSRTIMO, TRUE); - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); - return; - } -#endif - - /* Feature Card Area */ - if (pa >= CIO_BOTTOM && pa < CIO_TOP) { - slot = SLOT(pa); - reg = pa - CADDR(slot); - - if (!cio[slot].populated) { - /* Nothing lives here */ - sim_debug(CIO_DBG, &cpu_dev, - "[WRITE] No card at slot=%d reg=%d\n", - slot, reg); - CSRBIT(CSRTIMO, TRUE); - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); - return; - } - - /* A normal SYSGEN sequence is: RESET -> INT0 -> INT1. - * However, there's a bug in the 3B2/400 DGMON test suite that - * runs on every startup. This diagnostic code performs a - * SYSGEN by calling RESET -> INT1 -> INT0. So, we must handle - * both orders. */ - - switch (reg) { - case IOF_ID: - case IOF_VEC: - switch(cio[slot].sysgen_s) { - case CIO_INT_NONE: /* We've never seen an INT0 or INT1 */ - case CIO_INT0: /* We've seen an INT0 but not an INT1. */ - sim_debug(CIO_DBG, &cpu_dev, - "[WRITE] [%s] (%d INT0) ID\n", - cio[slot].name, slot); - cio[slot].sysgen_s |= CIO_INT0; - break; - case CIO_INT1: /* We've seen an INT1 but not an INT0. Time to sysgen */ - sim_debug(CIO_DBG, &cpu_dev, - "[WRITE] [%s] (%d INT0) SYSGEN\n", - cio[slot].name, slot); - cio[slot].sysgen_s |= CIO_INT0; - cio_sysgen(slot); - break; - case CIO_SYSGEN: /* We've already sysgen'ed */ - sim_debug(CIO_DBG, &cpu_dev, - "[WRITE] [%s] (%d INT0) EXPRESS JOB\n", - cio[slot].name, slot); - cio[slot].sysgen_s |= CIO_INT0; - cio[slot].exp_handler(slot); - break; - default: - /* This should never happen */ - stop_reason = STOP_ERR; - sim_debug(CIO_DBG, &cpu_dev, - "[WRITE] [%s] (%d INT0) ERROR IN STATE MACHINE sysgen_s=%02x\n", - cio[slot].name, slot, cio[slot].sysgen_s); - break; - } - - return; - case IOF_CTRL: - switch(cio[slot].sysgen_s) { - case CIO_INT_NONE: /* We've never seen an INT0 or INT1 */ - case CIO_INT1: /* We've seen an INT1 but not an INT0 */ - /* There's nothing to do in this instance */ - sim_debug(CIO_DBG, &cpu_dev, - "[WRITE] [%s] (%d INT1) IGNORED\n", - cio[slot].name, slot); - cio[slot].sysgen_s |= CIO_INT1; - break; - case CIO_INT0: /* We've seen an INT0 but not an INT1. Time to sysgen */ - sim_debug(CIO_DBG, &cpu_dev, - "[WRITE] [%s] (%d INT1) SYSGEN\n", - cio[slot].name, slot); - cio[slot].sysgen_s |= CIO_INT1; - cio_sysgen(slot); - break; - case CIO_SYSGEN: /* We've already sysgen'ed */ - sim_debug(CIO_DBG, &cpu_dev, - "[WRITE] [%s] (%d INT1) FULL\n", - cio[slot].name, slot); - cio[slot].sysgen_s |= CIO_INT1; - cio[slot].full_handler(slot); - break; - default: - /* This should never happen */ - stop_reason = STOP_ERR; - sim_debug(CIO_DBG, &cpu_dev, - "[WRITE] [%s] (%d INT1) ERROR IN STATE MACHINE sysgen_s=%02x\n", - cio[slot].name, slot, cio[slot].sysgen_s); - break; - } - - return; - case IOF_STAT: - sim_debug(CIO_DBG, &cpu_dev, - "[WRITE] [%s] (%d RESET)\n", - cio[slot].name, slot); - if (cio[slot].reset_handler) { - cio[slot].reset_handler(slot); - } - cio[slot].sysgen_s = 0; - return; - default: - /* We should never reach here, but if we do, there's - * nothing listening. */ - sim_debug(CIO_DBG, &cpu_dev, - "[WRITE] No card at slot=%d reg=%d\n", - slot, reg); - CSRBIT(CSRTIMO, TRUE); - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); - return; - } - } - - /* Memory-mapped IO devices */ - for (p = &iotable[0]; p->low != 0; p++) { - if ((pa >= p->low) && (pa < p->high) && p->write) { - p->write(pa, val, size); - return; - } - } - - /* Not found. */ - sim_debug(IO_DBG, &cpu_dev, - "[io_write] ADDR=%08x: No device found.\n", - pa); - CSRBIT(CSRTIMO, TRUE); - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); -} +/* 3b2_io.c: Common I/O (CIO) Feature Card Support + + Copyright (c) 2017-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#include "3b2_io.h" + +#include "3b2_cpu.h" +#include "3b2_csr.h" +#include "3b2_dmac.h" +#include "3b2_if.h" +#include "3b2_iu.h" +#include "3b2_mem.h" +#include "3b2_mmu.h" +#include "3b2_stddev.h" +#include "3b2_timer.h" + +#if defined(REV2) +#include "3b2_id.h" +#endif + +CIO_STATE cio[CIO_SLOTS] = {{0}}; +uint16 cio_int_req = 0; /* Bitset of card slots requesting interrupts */ + +#if defined(REV3) +iolink iotable[] = { + { MMUBASE, MMUBASE+MMUSIZE, &mmu_read, &mmu_write }, + { IFBASE, IFBASE+IFSIZE, &if_read, &if_write }, + { IFCSRBASE, IFCSRBASE+IFCSRSIZE, &if_csr_read, &if_csr_write }, + { FLTLBASE, FLTLBASE+FLTLSIZE, &flt_read, &flt_write }, + { FLTHBASE, FLTHBASE+FLTHSIZE, &flt_read, &flt_write }, + { NVRBASE, NVRBASE+NVRSIZE, &nvram_read, &nvram_write }, + { TIMERBASE, TIMERBASE+TIMERSIZE, &timer_read, &timer_write }, + { CSRBASE, CSRBASE+CSRSIZE, &csr_read, &csr_write }, + { IUBASE, IUBASE+IUSIZE, &iu_read, &iu_write }, + { DMAIUABASE, DMAIUABASE+DMAIUASIZE, &dmac_read, &dmac_write }, + { DMAIUBBASE, DMAIUBBASE+DMAIUBSIZE, &dmac_read, &dmac_write }, + { DMACBASE, DMACBASE+DMACSIZE, &dmac_read, &dmac_write }, + { DMAIFBASE, DMAIFBASE+DMAIFSIZE, &dmac_read, &dmac_write }, + { TODBASE, TODBASE+TODSIZE, &tod_read, &tod_write }, + { 0, 0, NULL, NULL} +}; +#else +iolink iotable[] = { + { MMUBASE, MMUBASE+MMUSIZE, &mmu_read, &mmu_write }, + { IFBASE, IFBASE+IFSIZE, &if_read, &if_write }, + { IDBASE, IDBASE+IDSIZE, &id_read, &id_write }, + { DMAIDBASE, DMAIDBASE+DMAIDSIZE, &dmac_read, &dmac_write }, + { NVRBASE, NVRBASE+NVRSIZE, &nvram_read, &nvram_write }, + { TIMERBASE, TIMERBASE+TIMERSIZE, &timer_read, &timer_write }, + { CSRBASE, CSRBASE+CSRSIZE, &csr_read, &csr_write }, + { IUBASE, IUBASE+IUSIZE, &iu_read, &iu_write }, + { DMAIUABASE, DMAIUABASE+DMAIUASIZE, &dmac_read, &dmac_write }, + { DMAIUBBASE, DMAIUBBASE+DMAIUBSIZE, &dmac_read, &dmac_write }, + { DMACBASE, DMACBASE+DMACSIZE, &dmac_read, &dmac_write }, + { DMAIFBASE, DMAIFBASE+DMAIFSIZE, &dmac_read, &dmac_write }, + { TODBASE, TODBASE+TODSIZE, &tod_read, &tod_write }, + { 0, 0, NULL, NULL} +}; +#endif + +/* + * Insert a CIO card into the backplane. + * + * If a space could be found, SPCE_OK is returned, and the slot the + * card was installed in is placed in `slot`. + * + * If no room is availalbe, return SCPE_NXM. + */ +t_stat cio_install(uint16 id, + CONST char *name, + uint8 ipl, + void (*exp_handler)(uint8 slot), + void (*full_handler)(uint8 slot), + void (*sysgen)(uint8 slot), + void (*reset_handler)(uint8 slot), + uint8 *slot) +{ + uint8 s; + + for (s = 0; s < CIO_SLOTS; s++) { + sim_debug(EXECUTE_MSG, &cpu_dev, + "[cio_install] cio[%d]: populated=%d, id=%d\n", + s, cio[s].populated, cio[s].id); + if (!cio[s].populated) { + sim_debug(EXECUTE_MSG, &cpu_dev, + "[cio_install] >>> I found a free slot! Slot #%d has nothing\n", s); + *slot = s; + /* Ensure the slot is in a clean state */ + cio_remove(s); + /* Populate the slot */ + cio[s].populated = TRUE; + cio[s].id = id; + cio[s].ipl = ipl; + strncpy(cio[s].name, name, CIO_NAME_LEN); + cio[s].exp_handler = exp_handler; + cio[s].full_handler = full_handler; + cio[s].sysgen = sysgen; + cio[s].reset_handler = reset_handler; + return SCPE_OK; + } + } + + return SCPE_NXM; +} + +/* + * Remove a CIO card from the specified backplane slot. + */ +void cio_remove(uint8 slot) +{ + memset(&cio[slot], 0, sizeof(CIO_STATE)); + /* cio[slot].populated = FALSE; */ + CIO_CLR_INT(slot); +} + +/* + * Remove all CIO cards of the matching type. + */ +void cio_remove_all(uint16 id) +{ + int i; + + for (i = 0; i < CIO_SLOTS; i++) { + if (cio[i].populated && cio[i].id == id) { + cio_remove(i); + } + } +} + +/* + * A braindead CRC32 calculator. + * + * This is overkill for what we need: A simple way to tag the contents + * of a block of memory uploaded to a CIO card (so we can + * differentiate between desired functions without actually having to + * disassemble and understand 80186 code!) + */ +uint32 cio_crc32_shift(uint32 crc, uint8 data) +{ + uint8 i; + + crc = ~crc; + crc ^= data; + for (i = 0; i < 8; i++) { + if (crc & 1) { + crc = (crc >> 1) ^ CRC_POLYNOMIAL; + } else { + crc = crc >> 1; + } + } + + return ~crc; +} + +void cio_sysgen(uint8 slot) +{ + uint32 sysgen_p; + + sysgen_p = pread_w(SYSGEN_PTR, BUS_PER); + + sim_debug(CIO_DBG, &cpu_dev, + "[SYSGEN] Starting sysgen for card %d (%s). sysgen_p=%08x\n", + slot, cio[slot].name, sysgen_p); + + /* seqbit is always reset to 0 on completion */ + cio[slot].seqbit = 0; + + cio[slot].rqp = pread_w(sysgen_p, BUS_PER); + cio[slot].cqp = pread_w(sysgen_p + 4, BUS_PER); + cio[slot].rqs = pread_b(sysgen_p + 8, BUS_PER); + cio[slot].cqs = pread_b(sysgen_p + 9, BUS_PER); + cio[slot].ivec = pread_b(sysgen_p + 10, BUS_PER); + cio[slot].no_rque = pread_b(sysgen_p + 11, BUS_PER); + + sim_debug(CIO_DBG, &cpu_dev, + "[SYSGEN] sysgen rqp = %08x\n", + cio[slot].rqp); + sim_debug(CIO_DBG, &cpu_dev, + "[SYSGEN] sysgen cqp = %08x\n", + cio[slot].cqp); + sim_debug(CIO_DBG, &cpu_dev, + "[SYSGEN] sysgen rqs = %02x\n", + cio[slot].rqs); + sim_debug(CIO_DBG, &cpu_dev, + "[SYSGEN] sysgen cqs = %02x\n", + cio[slot].cqs); + sim_debug(CIO_DBG, &cpu_dev, + "[SYSGEN] sysgen ivec = %02x\n", + cio[slot].ivec); + sim_debug(CIO_DBG, &cpu_dev, + "[SYSGEN] sysgen no_rque = %02x\n", + cio[slot].no_rque); + + /* If the card has a custom sysgen handler, run it */ + if (cio[slot].sysgen != NULL) { + cio[slot].sysgen(slot); + } else { + sim_debug(CIO_DBG, &cpu_dev, + "[cio_sysgen] Not running custom sysgen.\n"); + } +} + +void cio_cexpress(uint8 slot, uint32 esize, cio_entry *cqe, uint8 *app_data) +{ + uint32 i, cqp; + + cqp = cio[slot].cqp; + + sim_debug(CIO_DBG, &cpu_dev, + "[cio_cexpress] [%s] cqp = %08x seqbit = %d\n", + cio[slot].name, cqp, cio[slot].seqbit); + + cio[slot].seqbit ^= 1; + + cqe->subdevice |= (cio[slot].seqbit << 6); + + pwrite_h(cqp, cqe->byte_count, BUS_PER); + pwrite_b(cqp + 2, cqe->subdevice, BUS_PER); + pwrite_b(cqp + 3, cqe->opcode, BUS_PER); + pwrite_w(cqp + 4, cqe->address, BUS_PER); + + /* Write application-specific data. */ + for (i = 0; i < (esize - QESIZE); i++) { + pwrite_b(cqp + 8 + i, app_data[i], BUS_PER); + } +} + +void cio_cqueue(uint8 slot, uint8 cmd_stat, uint32 esize, + cio_entry *cqe, uint8 *app_data) +{ + uint32 i, cqp, top; + uint16 lp; + + /* Apply the CMD/STAT bit */ + cqe->subdevice |= (cmd_stat << 7); + + /* Get the physical address of the completion queue + * in main memory */ + cqp = cio[slot].cqp; + + /* Get the physical address of the first entry in + * the completion queue */ + top = cqp + esize + LUSIZE; + + /* Get the load pointer. This is a 16-bit absolute offset + * from the top of the queue to the start of the entry. */ + lp = pread_h(cqp + esize, BUS_PER); + + /* Load the entry at the supplied address */ + pwrite_h(top + lp, cqe->byte_count, BUS_PER); + pwrite_b(top + lp + 2, cqe->subdevice, BUS_PER); + pwrite_b(top + lp + 3, cqe->opcode, BUS_PER); + pwrite_w(top + lp + 4, cqe->address, BUS_PER); + + /* Write application-specific data. */ + for (i = 0; i < (esize - QESIZE); i++) { + pwrite_b(top + lp + 8 + i, app_data[i], BUS_PER); + } + + /* Increment the load pointer to the next queue location. + * If we go past the end of the queue, wrap around to the + * start of the queue */ + if (cio[slot].cqs > 0) { + lp = (lp + esize) % (esize * cio[slot].cqs); + /* Store it back to the correct location */ + pwrite_h(cqp + esize, lp, BUS_PER); + } +} + +/* + * Retrieve the Express Entry from the Request Queue + */ +void cio_rexpress(uint8 slot, uint32 esize, cio_entry *rqe, uint8 *app_data) +{ + uint32 i; + uint32 rqp; + + rqp = cio[slot].rqp; + + /* Unload the express entry from the request queue */ + rqe->byte_count = pread_h(rqp, BUS_PER); + rqe->subdevice = pread_b(rqp + 2, BUS_PER); + rqe->opcode = pread_b(rqp + 3, BUS_PER); + rqe->address = pread_w(rqp + 4, BUS_PER); + + for (i = 0; i < (esize - QESIZE); i++) { + app_data[i] = pread_b(rqp + 8 + i, BUS_PER); + } +} + +/* + * Retrieve an entry from the Request Queue. This function + * returns the load pointer that points to the NEXT available slot. + * This may be used by callers to determine which queue(s) need to + * be serviced. + * + * Returns SCPE_OK on success, or SCPE_NXM if no entry was found. + * + */ +t_stat cio_rqueue(uint8 slot, uint32 qnum, uint32 esize, + cio_entry *rqe, uint8 *app_data) +{ + uint32 i, rqp, top; + uint16 lp, ulp; + + /* Get the physical address of the request queue in main memory */ + rqp = cio[slot].rqp + + esize + + (qnum * (LUSIZE + (esize * cio[slot].rqs))); + + lp = pread_h(rqp, BUS_PER); + ulp = pread_h(rqp + 2, BUS_PER); + + /* Check to see if the request queue is empty. If it is, there's + * nothing to take. */ + if (lp == ulp) { + return SCPE_NXM; + } + + top = rqp + LUSIZE; + + /* Retrieve the entry at the supplied address */ + rqe->byte_count = pread_h(top + ulp, BUS_PER); + rqe->subdevice = pread_b(top + ulp + 2, BUS_PER); + rqe->opcode = pread_b(top + ulp + 3, BUS_PER); + rqe->address = pread_w(top + ulp + 4, BUS_PER); + + /* Read application-specific data. */ + for (i = 0; i < (esize - QESIZE); i++) { + app_data[i] = pread_b(top + ulp + 8 + i, BUS_PER); + } + + /* Increment the unload pointer to the next queue location. If we + * go past the end of the queue, wrap around to the start of the + * queue */ + if (cio[slot].rqs > 0) { + ulp = (ulp + esize) % (esize * cio[slot].rqs); + pwrite_h(rqp + 2, ulp, BUS_PER); + } + + return SCPE_OK; +} + +/* + * Return the Load Pointer for the given request queue + */ +uint16 cio_r_lp(uint8 slot, uint32 qnum, uint32 esize) +{ + uint32 rqp; + + rqp = cio[slot].rqp + + esize + + (qnum * (LUSIZE + (esize * cio[slot].rqs))); + + return pread_h(rqp, BUS_PER); +} + +/* + * Return the Unload Pointer for the given request queue + */ +uint16 cio_r_ulp(uint8 slot, uint32 qnum, uint32 esize) +{ + uint32 rqp; + + rqp = cio[slot].rqp + + esize + + (qnum * (LUSIZE + (esize * cio[slot].rqs))); + + return pread_h(rqp + 2, BUS_PER); +} + +uint16 cio_c_lp(uint8 slot, uint32 esize) +{ + uint32 cqp; + cqp = cio[slot].cqp + esize; + return pread_h(cqp, BUS_PER); +} + +uint16 cio_c_ulp(uint8 slot, uint32 esize) +{ + uint32 cqp; + cqp = cio[slot].cqp + esize; + return pread_h(cqp + 2, BUS_PER); +} + +/* + * Returns true if there is room in the completion queue + * for a new entry. + */ +t_bool cio_cqueue_avail(uint8 slot, uint32 esize) +{ + uint32 lp, ulp; + + lp = pread_h(cio[slot].cqp + esize, BUS_PER); + ulp = pread_h(cio[slot].cqp + esize + 2, BUS_PER); + + return(((lp + esize) % (cio[slot].cqs * esize)) != ulp); +} + +t_bool cio_rqueue_avail(uint8 slot, uint32 qnum, uint32 esize) +{ + uint32 rqp, lp, ulp; + + /* Get the physical address of the request queue in main memory */ + rqp = cio[slot].rqp + + esize + + (qnum * (LUSIZE + (esize * cio[slot].rqs))); + + lp = pread_h(rqp, BUS_PER); + ulp = pread_h(rqp + 2, BUS_PER); + + return(lp != ulp); +} + +uint32 io_read(uint32 pa, size_t size) +{ + iolink *p; + uint8 slot, reg, data; + +#if defined (REV3) + if (pa >= VCACHE_BOTTOM && pa < VCACHE_TOP) { + sim_debug(EXECUTE_MSG, &cpu_dev, + "[UBUB] (VCACHE) Read addr %08x\n", pa); + CSRBIT(CSRTIMO, TRUE); + cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); + return 0; + } + + if (pa >= BUB_BOTTOM && pa < BUB_TOP) { + sim_debug(EXECUTE_MSG, &cpu_dev, + "[BUB] Read addr %08x\n", pa); + CSRBIT(CSRTIMO, TRUE); + cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); + return 0; + } +#else + if (pa == MEMSIZE_REG) { + + /* The following values map to memory sizes: + 0x00: 512KB ( 524,288 B) + 0x01: 2MB (2,097,152 B) + 0x02: 1MB (1,048,576 B) + 0x03: 4MB (4,194,304 B) + */ + switch(MEM_SIZE) { + case 0x80000: /* 512KB */ + return 0; + case 0x100000: /* 1MB */ + return 2; + case 0x200000: /* 2MB */ + return 1; + case 0x400000: /* 4MB */ + return 3; + default: + return 0; + } + } +#endif + + /* CIO board area */ + if (pa >= CIO_BOTTOM && pa < CIO_TOP) { + slot = SLOT(pa); + reg = pa - CADDR(slot); + + if (!cio[slot].populated) { + /* Nothing lives here */ + sim_debug(IO_DBG, &cpu_dev, + "[READ] No card at slot=%d reg=%d\n", + slot, reg); + CSRBIT(CSRTIMO, TRUE); + cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); + return 0; + } + + /* A normal SYSGEN sequence is: RESET -> INT0 -> INT1. + * However, there's a bug in the 3B2/400 DGMON test suite that + * runs on every startup. This diagnostic code performs a + * SYSGEN by calling RESET -> INT1 -> INT0. So, we must handle + * both orders. */ + + switch (reg) { + case IOF_ID: + case IOF_VEC: + switch(cio[slot].sysgen_s) { + case CIO_INT_NONE: /* We've never seen an INT0 or INT1 */ + case CIO_INT0: /* We've seen an INT0 but not an INT1. */ + cio[slot].sysgen_s |= CIO_INT0; + sim_debug(CIO_DBG, &cpu_dev, + "[READ] [%s] (%d INT0) ID\n", + cio[slot].name, slot); + /* Return the correct byte of our board ID */ + if (reg == IOF_ID) { + data = (cio[slot].id >> 8) & 0xff; + } else { + data = (cio[slot].id & 0xff); + } + break; + case CIO_INT1: /* We've seen an INT1 but not an INT0. Time to sysgen */ + cio[slot].sysgen_s |= CIO_INT0; + sim_debug(CIO_DBG, &cpu_dev, + "[READ] [%s] (%d INT0) SYSGEN\n", + cio[slot].name, slot); + cio_sysgen(slot); + data = cio[slot].ivec; + break; + case CIO_SYSGEN: /* We've already sysgen'ed */ + cio[slot].sysgen_s |= CIO_INT0; /* This must come BEFORE the exp_handler */ + sim_debug(CIO_DBG, &cpu_dev, + "[READ] [%s] (%d INT0) EXPRESS JOB\n", + cio[slot].name, slot); + cio[slot].exp_handler(slot); + data = cio[slot].ivec; + break; + default: + /* This should never happen */ + stop_reason = STOP_ERR; + sim_debug(CIO_DBG, &cpu_dev, + "[READ] [%s] (%d INT0) ERROR IN STATE MACHINE sysgen_s=%02x\n", + cio[slot].name, slot, cio[slot].sysgen_s); + data = 0; + break; + } + + return data; + case IOF_CTRL: + switch(cio[slot].sysgen_s) { + case CIO_INT_NONE: /* We've never seen an INT0 or INT1 */ + case CIO_INT1: /* We've seen an INT1 but not an INT0 */ + /* There's nothing to do in this instance */ + sim_debug(CIO_DBG, &cpu_dev, + "[READ] [%s] (%d INT1) IGNORED\n", + cio[slot].name, slot); + cio[slot].sysgen_s |= CIO_INT1; + break; + case CIO_INT0: /* We've seen an INT0 but not an INT1. Time to sysgen */ + sim_debug(CIO_DBG, &cpu_dev, + "[READ] [%s] (%d INT1) SYSGEN\n", + cio[slot].name, slot); + cio[slot].sysgen_s |= CIO_INT1; + cio_sysgen(slot); + break; + case CIO_SYSGEN: /* We've already sysgen'ed */ + sim_debug(CIO_DBG, &cpu_dev, + "[READ] [%s] (%d INT1) FULL\n", + cio[slot].name, slot); + cio[slot].sysgen_s |= CIO_INT1; /* This must come BEFORE the full handler */ + cio[slot].full_handler(slot); + break; + default: + /* This should never happen */ + stop_reason = STOP_ERR; + sim_debug(CIO_DBG, &cpu_dev, + "[READ] [%s] (%d INT1) ERROR IN STATE MACHINE sysgen_s=%02x\n", + cio[slot].name, slot, cio[slot].sysgen_s); + break; + } + + return 0; /* Data returned is arbitrary */ + case IOF_STAT: + sim_debug(CIO_DBG, &cpu_dev, + "[READ] [%s] (%d RESET)\n", + cio[slot].name, slot); + if (cio[slot].reset_handler) { + cio[slot].reset_handler(slot); + } + cio[slot].sysgen_s = 0; + return 0; /* Data returned is arbitrary */ + default: + /* We should never reach here, but if we do, there's + * nothing listening. */ + sim_debug(CIO_DBG, &cpu_dev, + "[READ] No card at slot=%d reg=%d\n", + slot, reg); + CSRBIT(CSRTIMO, TRUE); + cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); + return 0; + } + } + + /* Memory-mapped IO devices */ + for (p = &iotable[0]; p->low != 0; p++) { + if ((pa >= p->low) && (pa < p->high) && p->read) { + return p->read(pa, size); + } + } + + /* Not found. */ + sim_debug(IO_DBG, &cpu_dev, + "[io_read] ADDR=%08x: No device found.\n", + pa); + CSRBIT(CSRTIMO, TRUE); + cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); + return 0; +} + +void io_write(uint32 pa, uint32 val, size_t size) +{ + iolink *p; + uint8 slot, reg; + +#if defined(REV3) + if (pa >= VCACHE_BOTTOM && pa < VCACHE_TOP) { + sim_debug(EXECUTE_MSG, &cpu_dev, + "[UBUB] (VCACHE) Write addr %08x val 0x%x\n", + pa, val); + CSRBIT(CSRTIMO, TRUE); + cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); + return; + } + + if (pa >= BUB_BOTTOM && pa < BUB_TOP) { + sim_debug(EXECUTE_MSG, &cpu_dev, + "[BUB] Write addr %08x val 0x%x\n", + pa, val); + CSRBIT(CSRTIMO, TRUE); + cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); + return; + } +#endif + + /* Feature Card Area */ + if (pa >= CIO_BOTTOM && pa < CIO_TOP) { + slot = SLOT(pa); + reg = pa - CADDR(slot); + + if (!cio[slot].populated) { + /* Nothing lives here */ + sim_debug(CIO_DBG, &cpu_dev, + "[WRITE] No card at slot=%d reg=%d\n", + slot, reg); + CSRBIT(CSRTIMO, TRUE); + cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); + return; + } + + /* A normal SYSGEN sequence is: RESET -> INT0 -> INT1. + * However, there's a bug in the 3B2/400 DGMON test suite that + * runs on every startup. This diagnostic code performs a + * SYSGEN by calling RESET -> INT1 -> INT0. So, we must handle + * both orders. */ + + switch (reg) { + case IOF_ID: + case IOF_VEC: + switch(cio[slot].sysgen_s) { + case CIO_INT_NONE: /* We've never seen an INT0 or INT1 */ + case CIO_INT0: /* We've seen an INT0 but not an INT1. */ + sim_debug(CIO_DBG, &cpu_dev, + "[WRITE] [%s] (%d INT0) ID\n", + cio[slot].name, slot); + cio[slot].sysgen_s |= CIO_INT0; + break; + case CIO_INT1: /* We've seen an INT1 but not an INT0. Time to sysgen */ + sim_debug(CIO_DBG, &cpu_dev, + "[WRITE] [%s] (%d INT0) SYSGEN\n", + cio[slot].name, slot); + cio[slot].sysgen_s |= CIO_INT0; + cio_sysgen(slot); + break; + case CIO_SYSGEN: /* We've already sysgen'ed */ + sim_debug(CIO_DBG, &cpu_dev, + "[WRITE] [%s] (%d INT0) EXPRESS JOB\n", + cio[slot].name, slot); + cio[slot].sysgen_s |= CIO_INT0; + cio[slot].exp_handler(slot); + break; + default: + /* This should never happen */ + stop_reason = STOP_ERR; + sim_debug(CIO_DBG, &cpu_dev, + "[WRITE] [%s] (%d INT0) ERROR IN STATE MACHINE sysgen_s=%02x\n", + cio[slot].name, slot, cio[slot].sysgen_s); + break; + } + + return; + case IOF_CTRL: + switch(cio[slot].sysgen_s) { + case CIO_INT_NONE: /* We've never seen an INT0 or INT1 */ + case CIO_INT1: /* We've seen an INT1 but not an INT0 */ + /* There's nothing to do in this instance */ + sim_debug(CIO_DBG, &cpu_dev, + "[WRITE] [%s] (%d INT1) IGNORED\n", + cio[slot].name, slot); + cio[slot].sysgen_s |= CIO_INT1; + break; + case CIO_INT0: /* We've seen an INT0 but not an INT1. Time to sysgen */ + sim_debug(CIO_DBG, &cpu_dev, + "[WRITE] [%s] (%d INT1) SYSGEN\n", + cio[slot].name, slot); + cio[slot].sysgen_s |= CIO_INT1; + cio_sysgen(slot); + break; + case CIO_SYSGEN: /* We've already sysgen'ed */ + sim_debug(CIO_DBG, &cpu_dev, + "[WRITE] [%s] (%d INT1) FULL\n", + cio[slot].name, slot); + cio[slot].sysgen_s |= CIO_INT1; + cio[slot].full_handler(slot); + break; + default: + /* This should never happen */ + stop_reason = STOP_ERR; + sim_debug(CIO_DBG, &cpu_dev, + "[WRITE] [%s] (%d INT1) ERROR IN STATE MACHINE sysgen_s=%02x\n", + cio[slot].name, slot, cio[slot].sysgen_s); + break; + } + + return; + case IOF_STAT: + sim_debug(CIO_DBG, &cpu_dev, + "[WRITE] [%s] (%d RESET)\n", + cio[slot].name, slot); + if (cio[slot].reset_handler) { + cio[slot].reset_handler(slot); + } + cio[slot].sysgen_s = 0; + return; + default: + /* We should never reach here, but if we do, there's + * nothing listening. */ + sim_debug(CIO_DBG, &cpu_dev, + "[WRITE] No card at slot=%d reg=%d\n", + slot, reg); + CSRBIT(CSRTIMO, TRUE); + cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); + return; + } + } + + /* Memory-mapped IO devices */ + for (p = &iotable[0]; p->low != 0; p++) { + if ((pa >= p->low) && (pa < p->high) && p->write) { + p->write(pa, val, size); + return; + } + } + + /* Not found. */ + sim_debug(IO_DBG, &cpu_dev, + "[io_write] ADDR=%08x: No device found.\n", + pa); + CSRBIT(CSRTIMO, TRUE); + cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); +} diff --git a/3B2/3b2_io.h b/3B2/3b2_io.h index 7a8dfd23..77a1306d 100644 --- a/3B2/3b2_io.h +++ b/3B2/3b2_io.h @@ -1,271 +1,271 @@ -/* 3b2_io.h: Common I/O (CIO) Feature Card Support - - Copyright (c) 2017-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -/* Reference Documentation - * ======================= - * - * All communication between the system board and feature cards is - * done through in-memory queues, and causing interrupts in the - * feature card by accessing the Control or ID/VEC memory-mapped IO - * addresses. The structure of these queues is defined below in - * tables. - * - * Sysgen Block - * ------------ - * - * Pointed to by address at 0x2000000 after an INT0/INT1 combo - * - * - * | Address | Size | Contents | - * +---------------+------+-----------------------------------------+ - * | SYSGEN_P | 4 | Address of request queue | - * | SYSGEN_P + 4 | 4 | Address of completion queue | - * | SYSGEN_P + 8 | 1 | Number of entries in request queue | - * | SYSGEN_P + 9 | 1 | Number of entries in completion queue | - * | SYSGEN_P + 10 | 1 | Interrupt Vector number | - * | SYSGEN_P + 11 | 1 | Number of request queues | - * - * - * Queue Entry - * ----------- - * - * Each queue has one Express Entry, and n regular entries. - * - * | Address | Size | Contents | - * +---------------+------+-----------------------------------------+ - * | ENTRY_P | 2 | Byte Count | - * | ENTRY_P + 2 | 1 | Subdevice [1] | - * | ENTRY_P + 3 | 1 | Opcode | - * | ENTRY_P + 4 | 4 | Address / Data | - * | ENTRY_P + 8 | 4 | Application Specific Data | - * - * [1] The "Subdevice" entry is further divided into a bitset: - * Bit 7: Command (1) / Status (0) - * Bit 6: Sequence Bit - * Bit 5-1: Subdevice - * - * - * Queue - * ----- - * - * The Queue structures (one for request, one for completion) hold: - * - An express entry - * - * And then one or more queues, each queue consiting of - * - A set of pointers for load and unload from the queue - * - One or more Queue Entries - * - * | Address | Size | Contents | - * +---------------+------+-----------------------------------------+ - * | QUEUE_P | 12 | Express Queue Entry [1] | - * +---------------+------+-----------------------------------------+ - * | QUEUE_P + 12 | 2 | Load Pointer for Queue 0 | - * | QUEUE_P + 14 | 2 | Unload Pointer for Queue 0 | - * | QUEUE_P + 16 | 12 | Queue 0 Entry 0 [1] | - * | QUEUE_P + 28 | 12 | Queue 0 Entry 1 [1] | - * | ... | ... | ... | - * +---------------+------+-----------------------------------------+ - * | QUEUE_P + n | 2 | Load Pointer for Queue 1 | - * | QUEUE_P + n | 2 | Unload Pointer for Queue 1 | - * | QUEUE_P + n | 12 | Queue 1 Entry 0 [1] | - * | QUEUE_P + n | 12 | Queue 1 Entry 1 [1] | - * | ... | ... | ... | - * - * [1] See Queue Entry above - * - * NB: There are multiple Request queues, usually one per subdevice, - * and EACH Request queue starts with a Load Pointer, an Unload - * Pointer, and then 'n' Queue Entries. - * - */ - -#ifndef _3B2_IO_H_ -#define _3B2_IO_H_ - -#include "3b2_defs.h" - -#define CRC_POLYNOMIAL 0xEDB88320 - -#define CIO_SLOTS 12 - -/* IO area */ -#define IO_BOTTOM 0x40000 -#define IO_TOP 0x50000 - -#if defined(REV3) -#define UBUS_BOTTOM 0x1c00000 -#define UBUS_TOP 0x2000000 -#endif - -/* CIO area */ -#define CIO_BOTTOM 0x200000 -#if defined(REV3) -#define CIO_TOP 0x1a00000 -#else -#define CIO_TOP 0x2000000 -#endif - -#define IOF_ID 0 -#define IOF_VEC 1 -#define IOF_CTRL 3 -#define IOF_STAT 5 - -#define SYSGEN_PTR PHYS_MEM_BASE - -/* CIO opcodes */ -#define CIO_DLM 1 -#define CIO_ULM 2 -#define CIO_FCF 3 -#define CIO_DOS 4 -#define CIO_DSD 5 - -/* Response */ -#define CIO_SUCCESS 0 -#define CIO_FAILURE 2 -#define CIO_SYSGEN_OK 3 - -/* Map a physical address to a card ID */ -#define SLOT(pa) (((((pa) >> 0x14) & 0x1f) / 2) - 1) -/* Map a card ID to a base address */ -#define CADDR(bid) (((((bid) + 1) * 2) << 0x14)) - -/* Offsets into the request/completion queues of various values */ -#define LUSIZE 4 /* Load/Unload pointers size */ -#define QESIZE 8 /* Queue entry is 8 bytes + application data */ - -#define CIO_STAT 0 -#define CIO_CMD 1 - -/* Sysgen State */ -#define CIO_INT_NONE 0 -#define CIO_INT0 1 -#define CIO_INT1 2 -#define CIO_SYSGEN 3 - -#define CIO_SET_INT(slot) if (cio[slot].populated && cio[slot].ivec >= 2) (cio_int_req |= (1 << slot)) -#define CIO_CLR_INT(slot) (cio_int_req &= ~(1 << slot)) - -#define CIO_NAME_LEN 8 - -typedef struct { - t_bool populated; /* Populated? */ - uint16 id; /* CIO identifier */ - char name[CIO_NAME_LEN]; /* Device name */ - void (*exp_handler)(uint8 slot); /* Handler for express jobs */ - void (*full_handler)(uint8 slot); /* Handler for full jobs */ - void (*sysgen)(uint8 slot); /* Sysgen routine (optional) */ - void (*reset_handler)(uint8 slot); /* RESET request handler (optional) */ - uint32 rqp; /* Request Queue Pointer */ - uint32 cqp; /* Completion Queue Pointer */ - uint8 rqs; /* Request queue size */ - uint8 cqs; /* Completion queue size */ - uint8 ivec; /* Interrupt Vector */ - uint8 no_rque; /* Number of request queues */ - uint8 ipl; /* IPL that this card uses */ - uint8 sysgen_s; /* Sysgen state */ - uint8 seqbit; /* Squence Bit */ - uint8 op; /* Last received opcode */ -} CIO_STATE; - -typedef struct { - uint16 byte_count; - uint8 subdevice; - uint8 opcode; - uint32 address; -} cio_entry; - -typedef struct { - uint32 low; - uint32 high; - uint32 (*read)(uint32 pa, size_t size); - void (*write)(uint32 pa, uint32 val, size_t size); -} iolink; - -/* Example pump structure - * ---------------------- - * - * Used during initial setup of PORTS card in slot 0: - * - * dev = 0100 - * min = 0000 - * cmdcode = 0003 - * options = 0000 - * bufaddr = 808821A0 - * ioaddr = 00000500 - * size = 00000650 - * numbrd = 00000000 - * retcode = 00000008 (PU_NULL) - */ - -typedef struct { - uint16 dev; - uint16 min; - uint16 cmdcode; - uint16 options; - uint32 bufaddr; - uint32 ioaddr; - uint32 size; - uint32 numbrd; - uint32 retcode; -} pump; - -t_stat cio_reset(DEVICE *dptr); -t_stat cio_svc(UNIT *uptr); - -t_stat cio_install(uint16 id, - CONST char *name, - uint8 ipl, - void (*exp_handler)(uint8 slot), - void (*full_handler)(uint8 slot), - void (*sysgen)(uint8 slot), - void (*reset_handler)(uint8 slot), - uint8 *slot); -void cio_remove(uint8 slot); -void cio_remove_all(uint16 id); -uint32 cio_crc32_shift(uint32 crc, uint8 data); -void cio_cexpress(uint8 slot, uint32 esize, cio_entry *cqe, uint8 *app_data); -void cio_cqueue(uint8 slot, uint8 cmd_stat, uint32 esize, cio_entry *cqe, uint8 *app_data); -t_bool cio_cqueue_avail(uint8 slot, uint32 esize); -void cio_rexpress(uint8 slot, uint32 esize, cio_entry *rqe, uint8 *app_data); -t_stat cio_rqueue(uint8 slot, uint32 qnum, uint32 esize, cio_entry *rqe, uint8 *app_data); -t_bool cio_rqueue_avail(uint8 slot, uint32 qnum, uint32 esize); -uint16 cio_r_lp(uint8 slot, uint32 qnum, uint32 esize); -uint16 cio_r_ulp(uint8 slot, uint32 qnum, uint32 esize); -uint16 cio_c_lp(uint8 slot, uint32 esize); -uint16 cio_c_ulp(uint8 slot, uint32 esize); -void cio_sysgen(uint8 slot); - -uint32 io_read(uint32 pa, size_t size); -void io_write(uint32 pa, uint32 val, size_t size); - -extern uint16 cio_int_req; -extern CIO_STATE cio[CIO_SLOTS]; - -#endif +/* 3b2_io.h: Common I/O (CIO) Feature Card Support + + Copyright (c) 2017-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +/* Reference Documentation + * ======================= + * + * All communication between the system board and feature cards is + * done through in-memory queues, and causing interrupts in the + * feature card by accessing the Control or ID/VEC memory-mapped IO + * addresses. The structure of these queues is defined below in + * tables. + * + * Sysgen Block + * ------------ + * + * Pointed to by address at 0x2000000 after an INT0/INT1 combo + * + * + * | Address | Size | Contents | + * +---------------+------+-----------------------------------------+ + * | SYSGEN_P | 4 | Address of request queue | + * | SYSGEN_P + 4 | 4 | Address of completion queue | + * | SYSGEN_P + 8 | 1 | Number of entries in request queue | + * | SYSGEN_P + 9 | 1 | Number of entries in completion queue | + * | SYSGEN_P + 10 | 1 | Interrupt Vector number | + * | SYSGEN_P + 11 | 1 | Number of request queues | + * + * + * Queue Entry + * ----------- + * + * Each queue has one Express Entry, and n regular entries. + * + * | Address | Size | Contents | + * +---------------+------+-----------------------------------------+ + * | ENTRY_P | 2 | Byte Count | + * | ENTRY_P + 2 | 1 | Subdevice [1] | + * | ENTRY_P + 3 | 1 | Opcode | + * | ENTRY_P + 4 | 4 | Address / Data | + * | ENTRY_P + 8 | 4 | Application Specific Data | + * + * [1] The "Subdevice" entry is further divided into a bitset: + * Bit 7: Command (1) / Status (0) + * Bit 6: Sequence Bit + * Bit 5-1: Subdevice + * + * + * Queue + * ----- + * + * The Queue structures (one for request, one for completion) hold: + * - An express entry + * + * And then one or more queues, each queue consiting of + * - A set of pointers for load and unload from the queue + * - One or more Queue Entries + * + * | Address | Size | Contents | + * +---------------+------+-----------------------------------------+ + * | QUEUE_P | 12 | Express Queue Entry [1] | + * +---------------+------+-----------------------------------------+ + * | QUEUE_P + 12 | 2 | Load Pointer for Queue 0 | + * | QUEUE_P + 14 | 2 | Unload Pointer for Queue 0 | + * | QUEUE_P + 16 | 12 | Queue 0 Entry 0 [1] | + * | QUEUE_P + 28 | 12 | Queue 0 Entry 1 [1] | + * | ... | ... | ... | + * +---------------+------+-----------------------------------------+ + * | QUEUE_P + n | 2 | Load Pointer for Queue 1 | + * | QUEUE_P + n | 2 | Unload Pointer for Queue 1 | + * | QUEUE_P + n | 12 | Queue 1 Entry 0 [1] | + * | QUEUE_P + n | 12 | Queue 1 Entry 1 [1] | + * | ... | ... | ... | + * + * [1] See Queue Entry above + * + * NB: There are multiple Request queues, usually one per subdevice, + * and EACH Request queue starts with a Load Pointer, an Unload + * Pointer, and then 'n' Queue Entries. + * + */ + +#ifndef _3B2_IO_H_ +#define _3B2_IO_H_ + +#include "3b2_defs.h" + +#define CRC_POLYNOMIAL 0xEDB88320 + +#define CIO_SLOTS 12 + +/* IO area */ +#define IO_BOTTOM 0x40000 +#define IO_TOP 0x50000 + +#if defined(REV3) +#define UBUS_BOTTOM 0x1c00000 +#define UBUS_TOP 0x2000000 +#endif + +/* CIO area */ +#define CIO_BOTTOM 0x200000 +#if defined(REV3) +#define CIO_TOP 0x1a00000 +#else +#define CIO_TOP 0x2000000 +#endif + +#define IOF_ID 0 +#define IOF_VEC 1 +#define IOF_CTRL 3 +#define IOF_STAT 5 + +#define SYSGEN_PTR PHYS_MEM_BASE + +/* CIO opcodes */ +#define CIO_DLM 1 +#define CIO_ULM 2 +#define CIO_FCF 3 +#define CIO_DOS 4 +#define CIO_DSD 5 + +/* Response */ +#define CIO_SUCCESS 0 +#define CIO_FAILURE 2 +#define CIO_SYSGEN_OK 3 + +/* Map a physical address to a card ID */ +#define SLOT(pa) (((((pa) >> 0x14) & 0x1f) / 2) - 1) +/* Map a card ID to a base address */ +#define CADDR(bid) (((((bid) + 1) * 2) << 0x14)) + +/* Offsets into the request/completion queues of various values */ +#define LUSIZE 4 /* Load/Unload pointers size */ +#define QESIZE 8 /* Queue entry is 8 bytes + application data */ + +#define CIO_STAT 0 +#define CIO_CMD 1 + +/* Sysgen State */ +#define CIO_INT_NONE 0 +#define CIO_INT0 1 +#define CIO_INT1 2 +#define CIO_SYSGEN 3 + +#define CIO_SET_INT(slot) if (cio[slot].populated && cio[slot].ivec >= 2) (cio_int_req |= (1 << slot)) +#define CIO_CLR_INT(slot) (cio_int_req &= ~(1 << slot)) + +#define CIO_NAME_LEN 8 + +typedef struct { + t_bool populated; /* Populated? */ + uint16 id; /* CIO identifier */ + char name[CIO_NAME_LEN]; /* Device name */ + void (*exp_handler)(uint8 slot); /* Handler for express jobs */ + void (*full_handler)(uint8 slot); /* Handler for full jobs */ + void (*sysgen)(uint8 slot); /* Sysgen routine (optional) */ + void (*reset_handler)(uint8 slot); /* RESET request handler (optional) */ + uint32 rqp; /* Request Queue Pointer */ + uint32 cqp; /* Completion Queue Pointer */ + uint8 rqs; /* Request queue size */ + uint8 cqs; /* Completion queue size */ + uint8 ivec; /* Interrupt Vector */ + uint8 no_rque; /* Number of request queues */ + uint8 ipl; /* IPL that this card uses */ + uint8 sysgen_s; /* Sysgen state */ + uint8 seqbit; /* Squence Bit */ + uint8 op; /* Last received opcode */ +} CIO_STATE; + +typedef struct { + uint16 byte_count; + uint8 subdevice; + uint8 opcode; + uint32 address; +} cio_entry; + +typedef struct { + uint32 low; + uint32 high; + uint32 (*read)(uint32 pa, size_t size); + void (*write)(uint32 pa, uint32 val, size_t size); +} iolink; + +/* Example pump structure + * ---------------------- + * + * Used during initial setup of PORTS card in slot 0: + * + * dev = 0100 + * min = 0000 + * cmdcode = 0003 + * options = 0000 + * bufaddr = 808821A0 + * ioaddr = 00000500 + * size = 00000650 + * numbrd = 00000000 + * retcode = 00000008 (PU_NULL) + */ + +typedef struct { + uint16 dev; + uint16 min; + uint16 cmdcode; + uint16 options; + uint32 bufaddr; + uint32 ioaddr; + uint32 size; + uint32 numbrd; + uint32 retcode; +} pump; + +t_stat cio_reset(DEVICE *dptr); +t_stat cio_svc(UNIT *uptr); + +t_stat cio_install(uint16 id, + CONST char *name, + uint8 ipl, + void (*exp_handler)(uint8 slot), + void (*full_handler)(uint8 slot), + void (*sysgen)(uint8 slot), + void (*reset_handler)(uint8 slot), + uint8 *slot); +void cio_remove(uint8 slot); +void cio_remove_all(uint16 id); +uint32 cio_crc32_shift(uint32 crc, uint8 data); +void cio_cexpress(uint8 slot, uint32 esize, cio_entry *cqe, uint8 *app_data); +void cio_cqueue(uint8 slot, uint8 cmd_stat, uint32 esize, cio_entry *cqe, uint8 *app_data); +t_bool cio_cqueue_avail(uint8 slot, uint32 esize); +void cio_rexpress(uint8 slot, uint32 esize, cio_entry *rqe, uint8 *app_data); +t_stat cio_rqueue(uint8 slot, uint32 qnum, uint32 esize, cio_entry *rqe, uint8 *app_data); +t_bool cio_rqueue_avail(uint8 slot, uint32 qnum, uint32 esize); +uint16 cio_r_lp(uint8 slot, uint32 qnum, uint32 esize); +uint16 cio_r_ulp(uint8 slot, uint32 qnum, uint32 esize); +uint16 cio_c_lp(uint8 slot, uint32 esize); +uint16 cio_c_ulp(uint8 slot, uint32 esize); +void cio_sysgen(uint8 slot); + +uint32 io_read(uint32 pa, size_t size); +void io_write(uint32 pa, uint32 val, size_t size); + +extern uint16 cio_int_req; +extern CIO_STATE cio[CIO_SLOTS]; + +#endif diff --git a/3B2/3b2_iu.c b/3B2/3b2_iu.c index 2dcc7cba..e3639eec 100644 --- a/3B2/3b2_iu.c +++ b/3B2/3b2_iu.c @@ -1,1147 +1,1147 @@ -/* 3b2_iu.c: SCN2681A Dual UART - - Copyright (c) 2017-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#include "3b2_iu.h" - -#include "sim_tmxr.h" - -#include "3b2_cpu.h" -#include "3b2_csr.h" -#include "3b2_dmac.h" -#include "3b2_mem.h" -#include "3b2_stddev.h" -#include "3b2_timer.h" - -/* - * The 3B2/400 and 3B2/700 both have two on-board serial ports, - * labeled CONSOLE and CONTTY. The CONSOLE port is the system console. - * The CONTTY port serves as a secondary serial port for one - * additional terminal. - * - * These lines are driven by an SCN2681A Dual UART, with two receivers - * and two transmitters. - * - * In addition to the two TX/RX ports, the SCN27681A also has one - * programmable timer that is used in the 3B2 for various one-shot - * timing tasks. - * - * The SCN2681A UART is represented here by four devices: - * - * - Console TTI (Console Input, port A) - * - Console TTO (Console Output, port A) - * - CONTTY (I/O, port B. Terminal multiplexer with one line) - * - IU Timer - */ - -#if defined(REV3) -#define DMA_INT INT_UART_DMA -#else -#define DMA_INT INT_DMA -#endif - -#define UPDATE_IRQ do { \ - if (iu_state.imr & iu_state.isr) { \ - CPU_SET_INT(INT_UART); \ - SET_CSR(CSRUART); \ - } else { \ - CPU_CLR_INT(INT_UART); \ - CLR_CSR(CSRUART); \ - } \ - } while (0) - -#define SET_DMA_INT do { \ - CPU_SET_INT(DMA_INT); \ - SET_CSR(CSRDMA); \ - } while (0) - -#define CLR_DMA_INT do { \ - CPU_CLR_INT(DMA_INT); \ - CLR_CSR(CSRDMA); \ - } while (0) - -#define LOOPBACK(P) (((P)->mode[1] & 0xc0) == 0x80) -#define TX_ENABLED(P) ((P).conf & TX_EN) -#define PORTNO(P) ((P) == &iu_console ? PORT_A : PORT_B) - -/* Static function declarations */ -static void iu_w_cmd(IU_PORT *port, uint8 val); -static t_stat iu_tx(IU_PORT *port, uint8 val); -static void iu_rx(IU_PORT *port, uint8 val); -static uint8 iu_rx_getc(IU_PORT *port); - -/* - * Registers - */ - -/* The IU state shared between A and B */ -IU_STATE iu_state; - -/* The tx/rx state for ports A and B */ -IU_PORT iu_console; -IU_PORT iu_contty; - -/* The timer state */ -IU_TIMER_STATE iu_timer_state; - -/* Flags for incrementing mode pointers */ -t_bool iu_increment_a = FALSE; -t_bool iu_increment_b = FALSE; - -double iu_timer_multiplier = IU_TIMER_MULTIPLIER; - -BITFIELD sr_bits[] = { - BIT(RXRDY), - BIT(FFULL), - BIT(TXRDY), - BIT(TXEMT), - BIT(OVRN_E), - BIT(PRTY_E), - BIT(FRM_E), - BIT(BRK), - ENDBITS -}; - -BITFIELD isr_bits[] = { - BIT(TXRDYA), - BIT(RXRDY_FFA), - BIT(DLTA_BRKA), - BIT(CTR_RDY), - BIT(TXRDYB), - BIT(RXRDY_FFB), - BIT(DLTA_BRKB), - BIT(IPC), - ENDBITS -}; - -BITFIELD acr_bits[] = { - BIT(BRG_SET), - BITFFMT(TMR_MODE,3,%d), - BIT(DLTA_IP3), - BIT(DLTA_IP2), - BIT(DLTA_IP1), - BIT(DLTA_IP0), - ENDBITS -}; - -BITFIELD conf_bits[] = { - BIT(TX_EN), - BIT(RX_EN), - ENDBITS -}; - -/* TTI (Console) data structures */ - -UNIT tti_unit = { UDATA(&iu_svc_tti, UNIT_IDLE|TT_MODE_8B, 0), SERIAL_IN_WAIT }; - -REG tti_reg[] = { - { HRDATADF(SRA, iu_console.sr, 8, "Status", sr_bits) }, - { HRDATADF(CONF, iu_console.conf, 8, "Config", conf_bits) }, - { BRDATAD(DATA, iu_console.rxbuf, 16, 8, IU_BUF_SIZE, "Data") }, - { DRDATAD(POS, tti_unit.pos, T_ADDR_W, "number of characters input"), PV_LEFT }, - { DRDATAD(TIME, tti_unit.wait, 24, "input polling interval"), PV_LEFT }, - { NULL } -}; - -DEVICE tti_dev = { - "TTI", &tti_unit, tti_reg, NULL, - 1, 8, 32, 1, 8, 8, - NULL, NULL, &tti_reset, - NULL, NULL, NULL, NULL, - DEV_DEBUG, 0, sys_deb_tab -}; - -/* TTO (Console) data structures */ - -UNIT tto_unit = { UDATA(&iu_svc_tto, UNIT_IDLE|TT_MODE_8B, 0), SERIAL_OUT_WAIT }; - -REG tto_reg[] = { - { HRDATADF(SRA, iu_console.sr, 8, "Status Register", sr_bits) }, - { HRDATADF(ISR, iu_state.isr, 8, "Interrupt Status", isr_bits) }, - { HRDATAD(IMR, iu_state.imr, 8, "Interrupt Mask") }, - { HRDATADF(ACR, iu_state.acr, 8, "Aux. Control Register", acr_bits) }, - { NULL } -}; - -DEVICE tto_dev = { - "TTO", &tto_unit, tto_reg, NULL, - 1, 8, 32, 1, 8, 8, - NULL, NULL, NULL, - NULL, NULL, NULL, NULL, - DEV_DEBUG, 0, sys_deb_tab -}; - -/* CONTTY data structures */ - -/* - * The CONTTY "multiplexer" is a bit unusual in that it serves only a - * single line, representing the built-in CONTTY port. On a real - * 3B2/400, the system board's dual UART serves both CONSOLE and - * CONTTY lines, giving support for two terminals. In the simulator, - * the CONSOLE is served by TTI and TTO devices, whereas the CONTTY is - * served by a TMXR multiplexer. - */ - -TMLN contty_ldsc[1] = { 0 }; -TMXR contty_desc = { 1, 0, 0, contty_ldsc }; /* One fixed line */ - -UNIT contty_unit[2] = { - { UDATA(&iu_svc_contty, UNIT_IDLE|UNIT_ATTABLE|TT_MODE_8B, 0), SERIAL_IN_WAIT }, - { UDATA(&iu_svc_contty_xmt, UNIT_IDLE|UNIT_DIS, 0), SERIAL_OUT_WAIT } -}; - -REG contty_reg[] = { - { HRDATADF(SRB, iu_contty.sr, 8, "Status Register", sr_bits) }, - { HRDATADF(CONF, iu_contty.conf, 8, "Config", conf_bits) }, - { BRDATAD(RXDATA, iu_contty.rxbuf, 16, 8, IU_BUF_SIZE, "RX Data") }, - { HRDATADF(ISR, iu_state.isr, 8, "Interrupt Status", isr_bits) }, - { HRDATAD(IMR, iu_state.imr, 8, "Interrupt Mask") }, - { HRDATADF(ACR, iu_state.acr, 8, "Auxiliary Control Register", acr_bits) }, - { DRDATAD(TIME, contty_unit[1].wait, 24, "output character delay"), PV_LEFT }, - { NULL } -}; - -MTAB contty_mod[] = { - { UNIT_ATT, UNIT_ATT, "summary", NULL, - NULL, &tmxr_show_summ, (void *)&contty_desc, "Display a summary of line state" }, - { MTAB_XTD|MTAB_VDV|MTAB_NMO, 1, "CONNECTIONS", NULL, - NULL, &tmxr_show_cstat, (void *)&contty_desc, "Display current connection" }, - { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "STATISTICS", NULL, - NULL, &tmxr_show_cstat, (void *)&contty_desc, "Display CONTTY statistics" } -}; - -CONST char *brg_rates[IU_SPEED_REGS][IU_SPEEDS] = { - {"50", "110", "134.5", "200", - "300", "600", "1200", "1050", - "2400", "4800", "7200", "9600", - "38400", NULL, NULL, NULL}, - {"75", "110", "134.5", "150", - "300", "600", "1200", "2000", - "2400", "4800", "1800", "9600", - "19200", NULL, NULL, NULL} -}; - -CONST char *parity[3] = {"O", "E", "N"}; - -DEBTAB contty_deb_tab[] = { - {"EXEC", EXECUTE_MSG, "Execute"}, - {"XMT", TMXR_DBG_XMT, "Transmitted Data"}, - {"RCV", TMXR_DBG_RCV, "Received Data"}, - {"MDM", TMXR_DBG_MDM, "Modem Signals"}, - {"CON", TMXR_DBG_CON, "connection activities"}, - {"TRC", TMXR_DBG_TRC, "trace routine calls"}, - {"ASY", TMXR_DBG_ASY, "Asynchronous Activities"}, - {0} -}; - -DEVICE contty_dev = { - "CONTTY", contty_unit, contty_reg, contty_mod, - 2, 8, 32, 1, 8, 8, - &tmxr_ex, &tmxr_dep, &contty_reset, - NULL, &contty_attach, &contty_detach, - NULL, DEV_DISABLE|DEV_DEBUG|DEV_MUX, - 0, contty_deb_tab, NULL, NULL, - NULL, NULL, - (void *)&contty_desc, - NULL -}; - -/* IU Timer data structures */ - -MTAB iu_timer_mod[] = { - { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "MULT", "MULT={1|2|3|4}", - &iu_timer_set_mult, &iu_timer_show_mult, NULL, "Timer Multiplier" } -}; - -REG iu_timer_reg[] = { - { HRDATAD(CTR_SET, iu_timer_state.c_set, 16, "Counter Setting") }, - { NULL } -}; - -UNIT iu_timer_unit = { UDATA(&iu_svc_timer, UNIT_IDLE, 0) }; - -DEVICE iu_timer_dev = { - "IUTIMER", &iu_timer_unit, iu_timer_reg, iu_timer_mod, - 1, 8, 32, 1, 8, 8, - NULL, NULL, &iu_timer_reset, - NULL, NULL, NULL, NULL, - DEV_DEBUG, 0, sys_deb_tab -}; - -t_stat iu_timer_show_mult(FILE *st, UNIT *uptr, int val, const void *desc) -{ - fprintf(st, "mult=%d", (int) iu_timer_multiplier); - return SCPE_OK; -} - -t_stat iu_timer_set_mult(UNIT *uptr, int32 val, const char *cptr, void *desc) -{ - t_stat r; - t_value v; - v = get_uint(cptr, 10, 8, &r); - if (r != SCPE_OK) { - return r; - } - if (v < 1 || v > 4) { - return SCPE_ARG; - } - iu_timer_multiplier = (uint32) v; - return SCPE_OK; - -} - -uint8 brg_reg = 0; /* Selected baud-rate generator register */ -uint8 brg_clk = 11; /* Selected baud-rate generator clock */ -uint8 parity_sel = 1; /* Selected parity */ -uint8 bits_per_char = 7; - -t_stat contty_attach(UNIT *uptr, CONST char *cptr) -{ - t_stat r; - TMLN *lp; - char line_config[16]; - - /* Set initial line speed */ - brg_reg = 0; - brg_clk = BRG_DEFAULT; - parity_sel = IU_PARITY_EVEN; - bits_per_char = 7; - - sprintf(line_config, "%s-%d%s1", - brg_rates[brg_reg][brg_clk], - bits_per_char, - parity[parity_sel]); - - tmxr_set_config_line(&contty_ldsc[0], line_config); - - if ((sim_switches & SWMASK('M'))) { - tmxr_set_modem_control_passthru(&contty_desc); - } - - tmxr_set_line_output_unit(&contty_desc, 0, &contty_unit[1]); - - r = tmxr_attach(&contty_desc, uptr, cptr); - if (r != SCPE_OK) { - tmxr_clear_modem_control_passthru(&contty_desc); - return r; - } - - lp = &contty_ldsc[0]; - tmxr_set_get_modem_bits(lp, TMXR_MDM_DTR|TMXR_MDM_RTS, 0, NULL); - - return SCPE_OK; -} - -t_stat contty_detach(UNIT *uptr) -{ - t_stat r = tmxr_detach(&contty_desc, uptr); - tmxr_clear_modem_control_passthru(&contty_desc); - - return r; -} - -void increment_modep_a() -{ - iu_increment_a = FALSE; - iu_console.modep++; - - if (iu_console.modep > 1) { - iu_console.modep = 0; - } -} - -void increment_modep_b() -{ - iu_increment_b = FALSE; - iu_contty.modep++; - - if (iu_contty.modep > 1) { - iu_contty.modep = 0; - } -} - -t_stat tti_reset(DEVICE *dptr) -{ - memset(&iu_state, 0, sizeof(IU_STATE)); - memset(&iu_console, 0, sizeof(IU_PORT)); - - /* Input Port is active low */ - iu_state.inprt = ~IU_DCDA; - iu_state.ipcr = IU_DCDA_CH | (0xf & ~IU_DCDA); - - tmxr_set_console_units(&tti_unit, &tto_unit); - - /* Start the Console TTI polling loop */ - sim_activate_after(&tti_unit, tti_unit.wait); - - return SCPE_OK; -} - -t_stat contty_reset(DEVICE *dtpr) -{ - sim_set_uname(&contty_unit[0], "CONTTY-RCV"); - sim_set_uname(&contty_unit[1], "CONTTY-XMT"); - - tmxr_set_port_speed_control(&contty_desc); - - memset(&iu_contty, 0, sizeof(IU_PORT)); - - if (contty_unit[0].flags & UNIT_ATT) { - sim_activate_after(&contty_unit[0], contty_unit[0].wait); - } else { - sim_cancel(&contty_unit[0]); - } - - return SCPE_OK; -} - -static void iu_rx(IU_PORT *port, uint8 val) -{ - if (port->conf & RX_EN) { - if (!(port->sr & STS_FFL)) { - /* If not full, we're reading into the FIFO */ - port->rxbuf[port->w_p] = val; - port->w_p = (port->w_p + 1) % IU_BUF_SIZE; - if (port->w_p == port->r_p) { - port->sr |= STS_FFL; - } - } else { - /* FIFO is full, and now we're going to have to hold a - * character in the shift register until space is - * available */ - - /* If the register already had data, it's going to be - * overwritten, so we have to set the overflow flag */ - if (port->rxr_full) { - port->sr |= STS_OER; - } - - /* Save the character */ - port->rxr = val; - port->rxr_full = TRUE; - } - - port->sr |= STS_RXR; - iu_state.isr |= (port == &iu_console) ? ISTS_RXRA : ISTS_RXRB; - } - - UPDATE_IRQ; -} - -static uint8 iu_rx_getc(IU_PORT *port) -{ - uint8 val = 0; - - if (port->conf & RX_EN) { - val = port->rxbuf[port->r_p]; - port->r_p = (port->r_p + 1) % IU_BUF_SIZE; - /* No longer full */ - port->sr &= ~STS_FFL; - if (port->r_p == port->w_p) { - /* Empty FIFO, nothing left to read */ - port->sr &= ~STS_RXR; - iu_state.isr &= (port == &iu_console) ? ~ISTS_RXRA : ~ISTS_RXRB; - } - - if (port->rxr_full) { - /* Need to shift data from the Receive Shift Register into - the space that we just freed up */ - port->rxbuf[port->w_p] = port->rxr; - port->w_p = (port->w_p + 1) % IU_BUF_SIZE; - /* FIFO can logically never be full here, since we just - * freed up space above. */ - port->rxr_full = FALSE; - } - - if (!(port->mode[0] & 0x20)) { - /* Receiver is in "Char Error" mode, so reset SR error - bits on read */ - port->sr &= ~(STS_RXB|STS_FER|STS_PER); - } - } - - UPDATE_IRQ; - - return val; -} - -t_stat iu_timer_reset(DEVICE *dptr) -{ - memset(&iu_timer_state, 0, sizeof(IU_TIMER_STATE)); - - return SCPE_OK; -} - -/* Service routines */ - -t_stat iu_svc_tti(UNIT *uptr) -{ - int32 c; - - tmxr_clock_coschedule(uptr, tmxr_poll); - - if ((c = sim_poll_kbd()) < SCPE_KFLAG) { - return c; - } - - iu_rx(&iu_console, (uint8) c); - - return SCPE_OK; -} - - -t_stat iu_svc_tto(UNIT *uptr) -{ - t_stat result; - dma_channel *chan = &dma_state.channels[DMA_IUA_CHAN]; - - /* Check for data in the transmitter shift register that's ready - * to go out to the TX line */ - if (iu_console.tx_state & T_XMIT) { - if (LOOPBACK(&iu_console)) { - /* Handle loopback mode if set */ - sim_debug(EXECUTE_MSG, &tto_dev, "iu_svc_tto: CONSOLE is in loopback.\n"); - iu_console.tx_state &= ~T_XMIT; - - iu_rx(&iu_console, iu_console.txr); - - if (TX_ENABLED(iu_console) && !(iu_console.tx_state & T_HOLD)) { - iu_console.sr |= STS_TXE; - } - } else { - /* Direct mode, no loopback */ - result = sim_putchar_s(iu_console.txr); - if (result == SCPE_STALL) { - sim_debug(EXECUTE_MSG, &tto_dev, "iu_svc_tto: CONSOLE PUTC STALL\n"); - sim_activate_after(uptr, 1000); - return SCPE_OK; - } else { - iu_console.tx_state &= ~T_XMIT; - if (TX_ENABLED(iu_console) && !(iu_console.tx_state & T_HOLD)) { - iu_console.sr |= STS_TXE; - } - } - } - } - - /* Check for data in the holding register that's ready to go - * out to the transmitter shift register */ - if (iu_console.tx_state & T_HOLD) { - iu_console.tx_state &= ~T_HOLD; - iu_console.tx_state |= T_XMIT; - iu_console.txr = iu_console.thr; - /* If the transmitter is currently enabled, we need to update - * the TxRDY and TxEMT flags */ - if (TX_ENABLED(iu_console)) { - iu_console.sr &= ~STS_TXE; - iu_console.sr |= STS_TXR; - iu_state.isr |= ISTS_TXRA; - /* DRQ is always tied to TxRDY */ - iu_console.drq = TRUE; - } - - sim_activate_after_abs(uptr, uptr->wait); - } - - UPDATE_IRQ; - - /* If we're done transmitting and there's more DMA to do, - * do it. */ - if (!iu_console.tx_state && - chan->wcount_c >= 0 && - ((dma_state.mask >> DMA_IUA_CHAN) & 0x1) == 0) { - sim_debug(EXECUTE_MSG, &tto_dev, - "iu_svc_tto: Triggering next DMA\n"); - iu_dma_console(DMA_IUA_CHAN, IUBASE+IUA_DATA_REG); - } - - return SCPE_OK; -} - -t_stat iu_svc_contty(UNIT *uptr) -{ - int32 c; - - if ((uptr->flags & UNIT_ATT) == 0) { - return SCPE_OK; - } - - /* Check for connect of our single line */ - if (tmxr_poll_conn(&contty_desc) == 0) { - contty_ldsc[0].rcve = 1; - - iu_state.inprt &= ~IU_DCDB; - iu_state.ipcr &= ~IU_DCDB; - iu_state.ipcr |= IU_DCDB_CH; - - UPDATE_IRQ; - } - - tmxr_poll_tx(&contty_desc); - tmxr_poll_rx(&contty_desc); - - /* Check for disconnect */ - if (!contty_ldsc[0].conn && contty_ldsc[0].rcve) { - contty_ldsc[0].rcve = 0; - iu_state.inprt |= IU_DCDB; - iu_state.ipcr |= (IU_DCDB_CH|IU_DCDB); - - UPDATE_IRQ; - } - - /* Check for RX */ - if ((iu_contty.conf & RX_EN) && contty_ldsc[0].conn) { - c = tmxr_getc_ln(&contty_ldsc[0]); - if (c && !(c & SCPE_BREAK)) { - iu_rx(&iu_contty, (uint8) c); - } - } - - tmxr_clock_coschedule(uptr, tmxr_poll); - - return SCPE_OK; -} - -t_stat iu_svc_contty_xmt(UNIT *uptr) -{ - dma_channel *chan = &dma_state.channels[DMA_IUB_CHAN]; - TMLN *lp = &contty_ldsc[0]; - t_stat result; - - /* Check for data in the transmitter shift register that's ready - * to go out to the TX line */ - if (iu_contty.tx_state & T_XMIT) { - if (LOOPBACK(&iu_contty)) { - /* Handle loopback mode if set */ - sim_debug(EXECUTE_MSG, &contty_dev, "iu_svc_contty: CONTTY is in loopback.\n"); - iu_contty.tx_state &= ~T_XMIT; - - iu_rx(&iu_contty, iu_contty.txr); - - if (TX_ENABLED(iu_contty) && !(iu_contty.tx_state & T_HOLD)) { - iu_contty.sr |= STS_TXE; - } - } else { - /* Direct mode, no loopback */ - result = tmxr_putc_ln(lp, iu_contty.txr); - if (result == SCPE_STALL) { - sim_debug(EXECUTE_MSG, &contty_dev, "iu_svc_contty: CONTTY PUTC STALL: %d\n", result); - sim_activate_after(uptr, 1000); - return SCPE_OK; - } else { - tmxr_poll_tx(&contty_desc); - iu_contty.tx_state &= ~T_XMIT; - if (TX_ENABLED(iu_contty) && !(iu_contty.tx_state & T_HOLD)) { - iu_contty.sr |= STS_TXE; - } - } - } - } - - /* Check for data in the holding register that's ready to go - * out to the transmitter shift register */ - if (iu_contty.tx_state & T_HOLD) { - sim_debug(EXECUTE_MSG, &contty_dev, - "THRB->TXRB: 0x%02x (%c)\n", - iu_contty.thr, PCHAR(iu_contty.thr)); - iu_contty.tx_state &= ~T_HOLD; - iu_contty.tx_state |= T_XMIT; - iu_contty.txr = iu_contty.thr; - /* If the transmitter is currently enabled, we need to update - * the TxRDY and TxEMT flags */ - if (TX_ENABLED(iu_contty)) { - iu_contty.sr &= ~STS_TXE; - iu_contty.sr |= STS_TXR; - iu_state.isr |= ISTS_TXRB; - /* DRQ is always tied to TxRDY */ - iu_contty.drq = TRUE; - } - - sim_activate_after_abs(uptr, uptr->wait); - } - - UPDATE_IRQ; - - /* If we're done transmitting and there's more DMA to do, - * do it. */ - if (!iu_contty.tx_state && - chan->wcount_c >= 0 && - ((dma_state.mask >> DMA_IUB_CHAN) & 0x1) == 0) { - sim_debug(EXECUTE_MSG, &tto_dev, - "iu_svc_contty_xmt: Triggering next DMA\n"); - iu_dma_contty(DMA_IUB_CHAN, IUBASE+IUB_DATA_REG); - } - - return SCPE_OK; -} - -t_stat iu_svc_timer(UNIT *uptr) -{ - iu_state.isr |= ISTS_CRI; - - sim_debug(EXECUTE_MSG, &iu_timer_dev, - "[iu_svc_timer] IMR=%02x ISR=%02x => %02x\n", - iu_state.imr, iu_state.isr, (iu_state.imr & iu_state.isr)); - - UPDATE_IRQ; - return SCPE_OK; -} - -/* - * Reg | Name (Read) | Name (Write) - * -----+-------------------------+---------------------------- - * 0 | Mode Register 1/2 A | Mode Register 1/2 A - * 1 | Status Register A | Clock Select Register A - * 2 | BRG Test | Command Register A - * 3 | Rx Holding Register A | Tx Holding Register A - * 4 | Input Port Change Reg. | Aux. Control Register - * 5 | Interrupt Status Reg. | Interrupt Mask Register - * 6 | Counter/Timer Upper Val | C/T Upper Preset Val. - * 7 | Counter/Timer Lower Val | C/T Lower Preset Val. - * 8 | Mode Register B | Mode Register B - * 9 | Status Register B | Clock Select Register B - * 10 | 1X/16X Test | Command Register B - * 11 | Rx Holding Register B | Tx Holding Register B - * 12 | *Reserved* | *Reserved* - * 13 | Input Ports IP0 to IP6 | Output Port Conf. Reg. - * 14 | Start Counter Command | Set Output Port Bits Cmd. - * 15 | Stop Counter Command | Reset Output Port Bits Cmd. - */ - - -uint32 iu_read(uint32 pa, size_t size) -{ - uint8 reg, modep; - uint32 data = 0; - - reg = (uint8) (pa - IUBASE); - - switch (reg) { - case MR12A: - modep = iu_console.modep; - data = iu_console.mode[modep]; - iu_increment_a = TRUE; - break; - case SRA: - data = iu_console.sr; - break; - case RHRA: - data = iu_rx_getc(&iu_console); - break; - case IPCR: - data = iu_state.ipcr; - /* Reading the port resets the top 4 bits */ - iu_state.ipcr &= 0xf; - break; - case ISR: - data = iu_state.isr; - break; - case CTU: - data = (iu_timer_state.c_set >> 8) & 0xff; - break; - case CTL: - data = iu_timer_state.c_set & 0xff; - break; - case MR12B: - modep = iu_contty.modep; - data = iu_contty.mode[modep]; - iu_increment_b = TRUE; - break; - case SRB: - data = iu_contty.sr; - break; - case RHRB: - data = iu_rx_getc(&iu_contty); - break; - case INPRT: - data = iu_state.inprt; - break; - case START_CTR: - data = 0; - iu_state.isr &= ~ISTS_CRI; - sim_debug(EXECUTE_MSG, &iu_timer_dev, - "ACR=%02x : Activating IU Timer in %d ticks / %d microseconds\n", - iu_state.acr, iu_timer_state.c_set, (int32)(iu_timer_state.c_set * iu_timer_multiplier)); - sim_activate_after(&iu_timer_unit, (int32)(iu_timer_state.c_set * iu_timer_multiplier)); - break; - case STOP_CTR: - data = 0; - iu_state.isr &= ~ISTS_CRI; - UPDATE_IRQ; - sim_cancel(&iu_timer_unit); - break; - case 17: /* Clear DMA interrupt */ - data = 0; - CLR_DMA_INT; - break; - default: - break; - } - - return data; -} - -void iu_write(uint32 pa, uint32 val, size_t size) -{ - uint8 reg; - uint8 modep; - uint8 bval = (uint8) val; - char line_config[16]; - - reg = (uint8) (pa - IUBASE); - - switch (reg) { - case MR12A: - modep = iu_console.modep; - iu_console.mode[modep] = bval; - iu_increment_a = TRUE; - break; - case CSRA: - /* Nothing supported */ - break; - case CRA: /* Command A */ - iu_w_cmd(&iu_console, bval); - break; - case THRA: /* TX/RX Buf A */ - iu_tx(&iu_console, bval); - break; - case ACR: /* Auxiliary Control Register */ - iu_state.acr = bval; - brg_reg = (bval >> 7) & 1; - break; - case IMR: - iu_state.imr = bval; - UPDATE_IRQ; - break; - case CTUR: /* Counter/Timer Upper Preset Value */ - /* Clear out high byte */ - iu_timer_state.c_set &= 0x00ff; - /* Set high byte */ - iu_timer_state.c_set |= ((uint16) bval << 8); - break; - case CTLR: /* Counter/Timer Lower Preset Value */ - /* Clear out low byte */ - iu_timer_state.c_set &= 0xff00; - /* Set low byte */ - iu_timer_state.c_set |= bval; - break; - case MR12B: - modep = iu_contty.modep; - iu_contty.mode[modep] = bval; - sim_debug(EXECUTE_MSG, &tto_dev, "MR12B: Page %d Mode = %02x\n", modep, bval); - iu_increment_b = TRUE; - if (modep == 0) { - if ((bval >> 4) & 1) { - /* No parity */ - parity_sel = IU_PARITY_NONE; - } else { - /* Parity enabled */ - if (bval & 4) { - parity_sel = IU_PARITY_ODD; - } else { - parity_sel = IU_PARITY_EVEN; - } - } - - bits_per_char = (bval & 3) + 5; - } - break; - case CRB: - iu_w_cmd(&iu_contty, bval); - break; - case CSRB: - brg_clk = (bval >> 4) & 0xf; - - if (brg_rates[brg_reg][brg_clk] != NULL) { - sprintf(line_config, "%s-%d%s1", - brg_rates[brg_reg][brg_clk], - bits_per_char, - parity[parity_sel]); - - sim_debug(EXECUTE_MSG, &contty_dev, - "Setting CONTTY line to %s\n", - line_config); - - tmxr_set_config_line(&contty_ldsc[0], line_config); - } - - break; - case THRB: - iu_tx(&iu_contty, bval); - break; - case OPCR: - iu_state.opcr = bval; - break; - case SOPR: -#if defined (REV2) - /* Bit 2 of the IU output register is used as a soft power - * switch. When set, the machine will power down - * immediately. */ - if (bval & IU_KILLPWR) { - stop_reason = STOP_POWER; - } -#endif - break; - case ROPR: - break; - case 17: /* Clear DMA interrupt */ - sim_debug(EXECUTE_MSG, &tto_dev, - "[WRITE] Clear DMA interrupt in UART\n"); - CLR_DMA_INT; - break; - default: - break; - } -} - -/* - * Transmit a single character - */ -t_stat iu_tx(IU_PORT *port, uint8 val) -{ - int32 c; - uint8 tx_ists = (port == &iu_console) ? ISTS_TXRA : ISTS_TXRB; - UNIT *uptr = (port == &iu_console) ? &tto_unit : &contty_unit[1]; - - sim_debug(EXECUTE_MSG, &tto_dev, - "iu_tx PORT=%d CHAR=%02x (%c)\n", - PORTNO(port), val, PCHAR(val)); - - if (!(port->conf & TX_EN) || !(port->sr & STS_TXR)) { - sim_debug(EXECUTE_MSG, &tto_dev, - ">>> IGNORING TRANSMIT, NOT ENABLED OR NOT READY!!!\n"); - return SCPE_INCOMP; - } - - c = sim_tt_outcvt(val, TTUF_MODE_8B); - - if (c >= 0) { - port->tx_state |= T_HOLD; - port->sr &= ~(STS_TXR|STS_TXE); - port->drq = FALSE; - iu_state.isr &= ~(tx_ists); - port->thr = c; - sim_activate_after(uptr, uptr->wait); - } - - return SCPE_OK; -} - -static void iu_w_cmd(IU_PORT *port, uint8 cmd) -{ - uint8 tx_ists = (port == &iu_console) ? ISTS_TXRA : ISTS_TXRB; - uint8 dbk_ists = (port == &iu_console) ? ISTS_DBA : ISTS_DBB; - - /* Enable or disable transmitter */ - /* Disable always wins, if both are set */ - if (cmd & CMD_DTX) { - port->conf &= ~TX_EN; - port->sr &= ~(STS_TXR|STS_TXE); - port->drq = FALSE; - iu_state.isr &= ~tx_ists; - UPDATE_IRQ; - sim_debug(EXECUTE_MSG, &tto_dev, - "DISABLE TX, PORT %d\n", PORTNO(port)); - } else if (cmd & CMD_ETX) { - if (!(port->conf & TX_EN)) { - /* TXE and TXR are always set by an ENABLE if prior state - was DISABLED */ - port->sr |= (STS_TXR|STS_TXE); - port->drq = TRUE; - } - port->conf |= TX_EN; - iu_state.isr |= tx_ists; - UPDATE_IRQ; - sim_debug(EXECUTE_MSG, &tto_dev, - "ENABLE TX, PORT %d\n", PORTNO(port)); - } - - /* Enable or disable receiver. */ - /* Disable always wins, if both are set */ - if (cmd & CMD_DRX) { - port->conf &= ~RX_EN; - port->sr &= ~STS_RXR; - } else if (cmd & CMD_ERX) { - port->conf |= RX_EN; - } - - /* Command register bits 6-4 have special meaning */ - switch ((cmd >> CMD_MISC_SHIFT) & CMD_MISC_MASK) { - case CR_RST_MR: - /* Causes the Channel A MR pointer to point to MR1. */ - port->modep = 0; - break; - case CR_RST_RX: - sim_debug(EXECUTE_MSG, &tto_dev, - "PORT %d Command: RESET RX\n", PORTNO(port)); - /* Reset receiver. Resets the Channel's receiver as if a - hardware reset had been applied. The receiver is disabled - and the FIFO is flushed. */ - port->sr &= ~STS_RXR; - port->conf &= ~RX_EN; - port->w_p = 0; - port->r_p = 0; - break; - case CR_RST_TX: - sim_debug(EXECUTE_MSG, &tto_dev, - "PORT %d Command: RESET TX\n", PORTNO(port)); - /* Reset transmitter. Resets the Channel's transmitter as if a - hardware reset had been applied. */ - port->sr &= ~STS_TXR; - port->sr &= ~STS_TXE; - port->drq = FALSE; /* drq is tied to TXR */ - port->conf &= ~TX_EN; - break; - case CR_RST_ERR: - /* Reset error status. Clears the Channel's Received Break, - Parity Error, Framing Error, and Overrun Error bits in the - status register (SRn[7:4]). */ - sim_debug(EXECUTE_MSG, &tto_dev, - "PORT %d Command: RESET ERROR\n", PORTNO(port)); - port->sr &= ~(STS_RXB|STS_FER|STS_PER|STS_OER); - break; - case CR_RST_BRK: - /* Reset Channel's break change interrupt. Causes the Channel - A break detect change bit in the interrupt status register - (ISR[2] for Chan. A, ISR[6] for Chan. B) to be cleared to - zero. */ - sim_debug(EXECUTE_MSG, &tto_dev, - "PORT %d Command: RESET BREAK IRQ\n", PORTNO(port)); - iu_state.isr &= ~dbk_ists; - break; - case CR_START_BRK: - sim_debug(EXECUTE_MSG, &tto_dev, - "PORT %d Command: START BREAK. loopback=%d\n", - PORTNO(port), LOOPBACK(port)); - if (LOOPBACK(port)) { - /* Set "Received Break" and "Parity Error" bits in - SRA/SRB */ - port->sr |= (STS_RXB|STS_PER); - /* Set "Delta Break" bit A or B in ISR */ - iu_state.isr |= dbk_ists; - } - break; - case CR_STOP_BRK: - sim_debug(EXECUTE_MSG, &tto_dev, - "PORT %d Command: STOP BREAK. loopback=%d\n", - PORTNO(port), LOOPBACK(port)); - if (LOOPBACK(port)) { - /* Set "Delta Break" bit A or B in ISR */ - iu_state.isr |= dbk_ists; - } - break; - } - - UPDATE_IRQ; -} - -/* - * Initiate DMA transfer or continue one already in progress. - */ -void iu_dma_console(uint8 channel, uint32 service_address) -{ - uint8 data; - uint32 addr; - t_stat status = SCPE_OK; - dma_channel *chan = &dma_state.channels[channel]; - IU_PORT *port = &iu_console; - - /* If we're doing DMA and we're done, end it */ - if (iu_console.dma && chan->wcount_c < 0) { - sim_debug(EXECUTE_MSG, &tto_dev, - "iu_svc_tto: DMA Complete.\n"); - iu_console.dma = FALSE; - dma_state.mask |= (1 << DMA_IUA_CHAN); - dma_state.status |= (1 << DMA_IUA_CHAN); - SET_DMA_INT; - return; - } - - /* Mark that IUA is in DMA */ - port->dma = TRUE; - - switch (DMA_XFER(DMA_IUA_CHAN)) { - case DMA_XFER_READ: - addr = dma_address(channel, chan->ptr); - chan->addr_c = addr; - data = pread_b(addr, BUS_PER); - status = iu_tx(port, data); - if (status == SCPE_OK) { - chan->ptr++; - chan->wcount_c--; - } - break; - default: - sim_debug(EXECUTE_MSG, &tto_dev, - "iu_dma_console: Error, transfer type %d not supported\n", - DMA_XFER(DMA_IUA_CHAN)); - break; - } -} - -void iu_dma_contty(uint8 channel, uint32 service_address) -{ - uint8 data; - uint32 addr; - t_stat status = SCPE_OK; - dma_channel *chan = &dma_state.channels[channel]; - IU_PORT *port = &iu_contty; - - /* If we're doing DMA and we're done, end it */ - if (iu_contty.dma && chan->wcount_c < 0) { - sim_debug(EXECUTE_MSG, &contty_dev, - "iu_svc_contty_xmt: DMA Complete.\n"); - iu_contty.dma = FALSE; - dma_state.mask |= (1 << DMA_IUB_CHAN); - dma_state.status |= (1 << DMA_IUB_CHAN); - SET_DMA_INT; - return; - } - - /* Mark that IUB is in DMA */ - port->dma = TRUE; - - switch (DMA_XFER(DMA_IUB_CHAN)) { - case DMA_XFER_READ: - addr = dma_address(channel, chan->ptr); - chan->addr_c = addr; - data = pread_b(addr, BUS_PER); - status = iu_tx(port, data); - if (status == SCPE_OK) { - chan->ptr++; - chan->wcount_c--; - } - break; - default: - sim_debug(EXECUTE_MSG, &contty_dev, - "iu_dma_contty: Error, transfer type %d not supported\n", - DMA_XFER(DMA_IUB_CHAN)); - break; - } -} +/* 3b2_iu.c: SCN2681A Dual UART + + Copyright (c) 2017-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#include "3b2_iu.h" + +#include "sim_tmxr.h" + +#include "3b2_cpu.h" +#include "3b2_csr.h" +#include "3b2_dmac.h" +#include "3b2_mem.h" +#include "3b2_stddev.h" +#include "3b2_timer.h" + +/* + * The 3B2/400 and 3B2/700 both have two on-board serial ports, + * labeled CONSOLE and CONTTY. The CONSOLE port is the system console. + * The CONTTY port serves as a secondary serial port for one + * additional terminal. + * + * These lines are driven by an SCN2681A Dual UART, with two receivers + * and two transmitters. + * + * In addition to the two TX/RX ports, the SCN27681A also has one + * programmable timer that is used in the 3B2 for various one-shot + * timing tasks. + * + * The SCN2681A UART is represented here by four devices: + * + * - Console TTI (Console Input, port A) + * - Console TTO (Console Output, port A) + * - CONTTY (I/O, port B. Terminal multiplexer with one line) + * - IU Timer + */ + +#if defined(REV3) +#define DMA_INT INT_UART_DMA +#else +#define DMA_INT INT_DMA +#endif + +#define UPDATE_IRQ do { \ + if (iu_state.imr & iu_state.isr) { \ + CPU_SET_INT(INT_UART); \ + SET_CSR(CSRUART); \ + } else { \ + CPU_CLR_INT(INT_UART); \ + CLR_CSR(CSRUART); \ + } \ + } while (0) + +#define SET_DMA_INT do { \ + CPU_SET_INT(DMA_INT); \ + SET_CSR(CSRDMA); \ + } while (0) + +#define CLR_DMA_INT do { \ + CPU_CLR_INT(DMA_INT); \ + CLR_CSR(CSRDMA); \ + } while (0) + +#define LOOPBACK(P) (((P)->mode[1] & 0xc0) == 0x80) +#define TX_ENABLED(P) ((P).conf & TX_EN) +#define PORTNO(P) ((P) == &iu_console ? PORT_A : PORT_B) + +/* Static function declarations */ +static void iu_w_cmd(IU_PORT *port, uint8 val); +static t_stat iu_tx(IU_PORT *port, uint8 val); +static void iu_rx(IU_PORT *port, uint8 val); +static uint8 iu_rx_getc(IU_PORT *port); + +/* + * Registers + */ + +/* The IU state shared between A and B */ +IU_STATE iu_state; + +/* The tx/rx state for ports A and B */ +IU_PORT iu_console; +IU_PORT iu_contty; + +/* The timer state */ +IU_TIMER_STATE iu_timer_state; + +/* Flags for incrementing mode pointers */ +t_bool iu_increment_a = FALSE; +t_bool iu_increment_b = FALSE; + +double iu_timer_multiplier = IU_TIMER_MULTIPLIER; + +BITFIELD sr_bits[] = { + BIT(RXRDY), + BIT(FFULL), + BIT(TXRDY), + BIT(TXEMT), + BIT(OVRN_E), + BIT(PRTY_E), + BIT(FRM_E), + BIT(BRK), + ENDBITS +}; + +BITFIELD isr_bits[] = { + BIT(TXRDYA), + BIT(RXRDY_FFA), + BIT(DLTA_BRKA), + BIT(CTR_RDY), + BIT(TXRDYB), + BIT(RXRDY_FFB), + BIT(DLTA_BRKB), + BIT(IPC), + ENDBITS +}; + +BITFIELD acr_bits[] = { + BIT(BRG_SET), + BITFFMT(TMR_MODE,3,%d), + BIT(DLTA_IP3), + BIT(DLTA_IP2), + BIT(DLTA_IP1), + BIT(DLTA_IP0), + ENDBITS +}; + +BITFIELD conf_bits[] = { + BIT(TX_EN), + BIT(RX_EN), + ENDBITS +}; + +/* TTI (Console) data structures */ + +UNIT tti_unit = { UDATA(&iu_svc_tti, UNIT_IDLE|TT_MODE_8B, 0), SERIAL_IN_WAIT }; + +REG tti_reg[] = { + { HRDATADF(SRA, iu_console.sr, 8, "Status", sr_bits) }, + { HRDATADF(CONF, iu_console.conf, 8, "Config", conf_bits) }, + { BRDATAD(DATA, iu_console.rxbuf, 16, 8, IU_BUF_SIZE, "Data") }, + { DRDATAD(POS, tti_unit.pos, T_ADDR_W, "number of characters input"), PV_LEFT }, + { DRDATAD(TIME, tti_unit.wait, 24, "input polling interval"), PV_LEFT }, + { NULL } +}; + +DEVICE tti_dev = { + "TTI", &tti_unit, tti_reg, NULL, + 1, 8, 32, 1, 8, 8, + NULL, NULL, &tti_reset, + NULL, NULL, NULL, NULL, + DEV_DEBUG, 0, sys_deb_tab +}; + +/* TTO (Console) data structures */ + +UNIT tto_unit = { UDATA(&iu_svc_tto, UNIT_IDLE|TT_MODE_8B, 0), SERIAL_OUT_WAIT }; + +REG tto_reg[] = { + { HRDATADF(SRA, iu_console.sr, 8, "Status Register", sr_bits) }, + { HRDATADF(ISR, iu_state.isr, 8, "Interrupt Status", isr_bits) }, + { HRDATAD(IMR, iu_state.imr, 8, "Interrupt Mask") }, + { HRDATADF(ACR, iu_state.acr, 8, "Aux. Control Register", acr_bits) }, + { NULL } +}; + +DEVICE tto_dev = { + "TTO", &tto_unit, tto_reg, NULL, + 1, 8, 32, 1, 8, 8, + NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + DEV_DEBUG, 0, sys_deb_tab +}; + +/* CONTTY data structures */ + +/* + * The CONTTY "multiplexer" is a bit unusual in that it serves only a + * single line, representing the built-in CONTTY port. On a real + * 3B2/400, the system board's dual UART serves both CONSOLE and + * CONTTY lines, giving support for two terminals. In the simulator, + * the CONSOLE is served by TTI and TTO devices, whereas the CONTTY is + * served by a TMXR multiplexer. + */ + +TMLN contty_ldsc[1] = { 0 }; +TMXR contty_desc = { 1, 0, 0, contty_ldsc }; /* One fixed line */ + +UNIT contty_unit[2] = { + { UDATA(&iu_svc_contty, UNIT_IDLE|UNIT_ATTABLE|TT_MODE_8B, 0), SERIAL_IN_WAIT }, + { UDATA(&iu_svc_contty_xmt, UNIT_IDLE|UNIT_DIS, 0), SERIAL_OUT_WAIT } +}; + +REG contty_reg[] = { + { HRDATADF(SRB, iu_contty.sr, 8, "Status Register", sr_bits) }, + { HRDATADF(CONF, iu_contty.conf, 8, "Config", conf_bits) }, + { BRDATAD(RXDATA, iu_contty.rxbuf, 16, 8, IU_BUF_SIZE, "RX Data") }, + { HRDATADF(ISR, iu_state.isr, 8, "Interrupt Status", isr_bits) }, + { HRDATAD(IMR, iu_state.imr, 8, "Interrupt Mask") }, + { HRDATADF(ACR, iu_state.acr, 8, "Auxiliary Control Register", acr_bits) }, + { DRDATAD(TIME, contty_unit[1].wait, 24, "output character delay"), PV_LEFT }, + { NULL } +}; + +MTAB contty_mod[] = { + { UNIT_ATT, UNIT_ATT, "summary", NULL, + NULL, &tmxr_show_summ, (void *)&contty_desc, "Display a summary of line state" }, + { MTAB_XTD|MTAB_VDV|MTAB_NMO, 1, "CONNECTIONS", NULL, + NULL, &tmxr_show_cstat, (void *)&contty_desc, "Display current connection" }, + { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "STATISTICS", NULL, + NULL, &tmxr_show_cstat, (void *)&contty_desc, "Display CONTTY statistics" } +}; + +CONST char *brg_rates[IU_SPEED_REGS][IU_SPEEDS] = { + {"50", "110", "134.5", "200", + "300", "600", "1200", "1050", + "2400", "4800", "7200", "9600", + "38400", NULL, NULL, NULL}, + {"75", "110", "134.5", "150", + "300", "600", "1200", "2000", + "2400", "4800", "1800", "9600", + "19200", NULL, NULL, NULL} +}; + +CONST char *parity[3] = {"O", "E", "N"}; + +DEBTAB contty_deb_tab[] = { + {"EXEC", EXECUTE_MSG, "Execute"}, + {"XMT", TMXR_DBG_XMT, "Transmitted Data"}, + {"RCV", TMXR_DBG_RCV, "Received Data"}, + {"MDM", TMXR_DBG_MDM, "Modem Signals"}, + {"CON", TMXR_DBG_CON, "connection activities"}, + {"TRC", TMXR_DBG_TRC, "trace routine calls"}, + {"ASY", TMXR_DBG_ASY, "Asynchronous Activities"}, + {0} +}; + +DEVICE contty_dev = { + "CONTTY", contty_unit, contty_reg, contty_mod, + 2, 8, 32, 1, 8, 8, + &tmxr_ex, &tmxr_dep, &contty_reset, + NULL, &contty_attach, &contty_detach, + NULL, DEV_DISABLE|DEV_DEBUG|DEV_MUX, + 0, contty_deb_tab, NULL, NULL, + NULL, NULL, + (void *)&contty_desc, + NULL +}; + +/* IU Timer data structures */ + +MTAB iu_timer_mod[] = { + { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "MULT", "MULT={1|2|3|4}", + &iu_timer_set_mult, &iu_timer_show_mult, NULL, "Timer Multiplier" } +}; + +REG iu_timer_reg[] = { + { HRDATAD(CTR_SET, iu_timer_state.c_set, 16, "Counter Setting") }, + { NULL } +}; + +UNIT iu_timer_unit = { UDATA(&iu_svc_timer, UNIT_IDLE, 0) }; + +DEVICE iu_timer_dev = { + "IUTIMER", &iu_timer_unit, iu_timer_reg, iu_timer_mod, + 1, 8, 32, 1, 8, 8, + NULL, NULL, &iu_timer_reset, + NULL, NULL, NULL, NULL, + DEV_DEBUG, 0, sys_deb_tab +}; + +t_stat iu_timer_show_mult(FILE *st, UNIT *uptr, int val, const void *desc) +{ + fprintf(st, "mult=%d", (int) iu_timer_multiplier); + return SCPE_OK; +} + +t_stat iu_timer_set_mult(UNIT *uptr, int32 val, const char *cptr, void *desc) +{ + t_stat r; + t_value v; + v = get_uint(cptr, 10, 8, &r); + if (r != SCPE_OK) { + return r; + } + if (v < 1 || v > 4) { + return SCPE_ARG; + } + iu_timer_multiplier = (uint32) v; + return SCPE_OK; + +} + +uint8 brg_reg = 0; /* Selected baud-rate generator register */ +uint8 brg_clk = 11; /* Selected baud-rate generator clock */ +uint8 parity_sel = 1; /* Selected parity */ +uint8 bits_per_char = 7; + +t_stat contty_attach(UNIT *uptr, CONST char *cptr) +{ + t_stat r; + TMLN *lp; + char line_config[16]; + + /* Set initial line speed */ + brg_reg = 0; + brg_clk = BRG_DEFAULT; + parity_sel = IU_PARITY_EVEN; + bits_per_char = 7; + + sprintf(line_config, "%s-%d%s1", + brg_rates[brg_reg][brg_clk], + bits_per_char, + parity[parity_sel]); + + tmxr_set_config_line(&contty_ldsc[0], line_config); + + if ((sim_switches & SWMASK('M'))) { + tmxr_set_modem_control_passthru(&contty_desc); + } + + tmxr_set_line_output_unit(&contty_desc, 0, &contty_unit[1]); + + r = tmxr_attach(&contty_desc, uptr, cptr); + if (r != SCPE_OK) { + tmxr_clear_modem_control_passthru(&contty_desc); + return r; + } + + lp = &contty_ldsc[0]; + tmxr_set_get_modem_bits(lp, TMXR_MDM_DTR|TMXR_MDM_RTS, 0, NULL); + + return SCPE_OK; +} + +t_stat contty_detach(UNIT *uptr) +{ + t_stat r = tmxr_detach(&contty_desc, uptr); + tmxr_clear_modem_control_passthru(&contty_desc); + + return r; +} + +void increment_modep_a() +{ + iu_increment_a = FALSE; + iu_console.modep++; + + if (iu_console.modep > 1) { + iu_console.modep = 0; + } +} + +void increment_modep_b() +{ + iu_increment_b = FALSE; + iu_contty.modep++; + + if (iu_contty.modep > 1) { + iu_contty.modep = 0; + } +} + +t_stat tti_reset(DEVICE *dptr) +{ + memset(&iu_state, 0, sizeof(IU_STATE)); + memset(&iu_console, 0, sizeof(IU_PORT)); + + /* Input Port is active low */ + iu_state.inprt = ~IU_DCDA; + iu_state.ipcr = IU_DCDA_CH | (0xf & ~IU_DCDA); + + tmxr_set_console_units(&tti_unit, &tto_unit); + + /* Start the Console TTI polling loop */ + sim_activate_after(&tti_unit, tti_unit.wait); + + return SCPE_OK; +} + +t_stat contty_reset(DEVICE *dtpr) +{ + sim_set_uname(&contty_unit[0], "CONTTY-RCV"); + sim_set_uname(&contty_unit[1], "CONTTY-XMT"); + + tmxr_set_port_speed_control(&contty_desc); + + memset(&iu_contty, 0, sizeof(IU_PORT)); + + if (contty_unit[0].flags & UNIT_ATT) { + sim_activate_after(&contty_unit[0], contty_unit[0].wait); + } else { + sim_cancel(&contty_unit[0]); + } + + return SCPE_OK; +} + +static void iu_rx(IU_PORT *port, uint8 val) +{ + if (port->conf & RX_EN) { + if (!(port->sr & STS_FFL)) { + /* If not full, we're reading into the FIFO */ + port->rxbuf[port->w_p] = val; + port->w_p = (port->w_p + 1) % IU_BUF_SIZE; + if (port->w_p == port->r_p) { + port->sr |= STS_FFL; + } + } else { + /* FIFO is full, and now we're going to have to hold a + * character in the shift register until space is + * available */ + + /* If the register already had data, it's going to be + * overwritten, so we have to set the overflow flag */ + if (port->rxr_full) { + port->sr |= STS_OER; + } + + /* Save the character */ + port->rxr = val; + port->rxr_full = TRUE; + } + + port->sr |= STS_RXR; + iu_state.isr |= (port == &iu_console) ? ISTS_RXRA : ISTS_RXRB; + } + + UPDATE_IRQ; +} + +static uint8 iu_rx_getc(IU_PORT *port) +{ + uint8 val = 0; + + if (port->conf & RX_EN) { + val = port->rxbuf[port->r_p]; + port->r_p = (port->r_p + 1) % IU_BUF_SIZE; + /* No longer full */ + port->sr &= ~STS_FFL; + if (port->r_p == port->w_p) { + /* Empty FIFO, nothing left to read */ + port->sr &= ~STS_RXR; + iu_state.isr &= (port == &iu_console) ? ~ISTS_RXRA : ~ISTS_RXRB; + } + + if (port->rxr_full) { + /* Need to shift data from the Receive Shift Register into + the space that we just freed up */ + port->rxbuf[port->w_p] = port->rxr; + port->w_p = (port->w_p + 1) % IU_BUF_SIZE; + /* FIFO can logically never be full here, since we just + * freed up space above. */ + port->rxr_full = FALSE; + } + + if (!(port->mode[0] & 0x20)) { + /* Receiver is in "Char Error" mode, so reset SR error + bits on read */ + port->sr &= ~(STS_RXB|STS_FER|STS_PER); + } + } + + UPDATE_IRQ; + + return val; +} + +t_stat iu_timer_reset(DEVICE *dptr) +{ + memset(&iu_timer_state, 0, sizeof(IU_TIMER_STATE)); + + return SCPE_OK; +} + +/* Service routines */ + +t_stat iu_svc_tti(UNIT *uptr) +{ + int32 c; + + tmxr_clock_coschedule(uptr, tmxr_poll); + + if ((c = sim_poll_kbd()) < SCPE_KFLAG) { + return c; + } + + iu_rx(&iu_console, (uint8) c); + + return SCPE_OK; +} + + +t_stat iu_svc_tto(UNIT *uptr) +{ + t_stat result; + dma_channel *chan = &dma_state.channels[DMA_IUA_CHAN]; + + /* Check for data in the transmitter shift register that's ready + * to go out to the TX line */ + if (iu_console.tx_state & T_XMIT) { + if (LOOPBACK(&iu_console)) { + /* Handle loopback mode if set */ + sim_debug(EXECUTE_MSG, &tto_dev, "iu_svc_tto: CONSOLE is in loopback.\n"); + iu_console.tx_state &= ~T_XMIT; + + iu_rx(&iu_console, iu_console.txr); + + if (TX_ENABLED(iu_console) && !(iu_console.tx_state & T_HOLD)) { + iu_console.sr |= STS_TXE; + } + } else { + /* Direct mode, no loopback */ + result = sim_putchar_s(iu_console.txr); + if (result == SCPE_STALL) { + sim_debug(EXECUTE_MSG, &tto_dev, "iu_svc_tto: CONSOLE PUTC STALL\n"); + sim_activate_after(uptr, 1000); + return SCPE_OK; + } else { + iu_console.tx_state &= ~T_XMIT; + if (TX_ENABLED(iu_console) && !(iu_console.tx_state & T_HOLD)) { + iu_console.sr |= STS_TXE; + } + } + } + } + + /* Check for data in the holding register that's ready to go + * out to the transmitter shift register */ + if (iu_console.tx_state & T_HOLD) { + iu_console.tx_state &= ~T_HOLD; + iu_console.tx_state |= T_XMIT; + iu_console.txr = iu_console.thr; + /* If the transmitter is currently enabled, we need to update + * the TxRDY and TxEMT flags */ + if (TX_ENABLED(iu_console)) { + iu_console.sr &= ~STS_TXE; + iu_console.sr |= STS_TXR; + iu_state.isr |= ISTS_TXRA; + /* DRQ is always tied to TxRDY */ + iu_console.drq = TRUE; + } + + sim_activate_after_abs(uptr, uptr->wait); + } + + UPDATE_IRQ; + + /* If we're done transmitting and there's more DMA to do, + * do it. */ + if (!iu_console.tx_state && + chan->wcount_c >= 0 && + ((dma_state.mask >> DMA_IUA_CHAN) & 0x1) == 0) { + sim_debug(EXECUTE_MSG, &tto_dev, + "iu_svc_tto: Triggering next DMA\n"); + iu_dma_console(DMA_IUA_CHAN, IUBASE+IUA_DATA_REG); + } + + return SCPE_OK; +} + +t_stat iu_svc_contty(UNIT *uptr) +{ + int32 c; + + if ((uptr->flags & UNIT_ATT) == 0) { + return SCPE_OK; + } + + /* Check for connect of our single line */ + if (tmxr_poll_conn(&contty_desc) == 0) { + contty_ldsc[0].rcve = 1; + + iu_state.inprt &= ~IU_DCDB; + iu_state.ipcr &= ~IU_DCDB; + iu_state.ipcr |= IU_DCDB_CH; + + UPDATE_IRQ; + } + + tmxr_poll_tx(&contty_desc); + tmxr_poll_rx(&contty_desc); + + /* Check for disconnect */ + if (!contty_ldsc[0].conn && contty_ldsc[0].rcve) { + contty_ldsc[0].rcve = 0; + iu_state.inprt |= IU_DCDB; + iu_state.ipcr |= (IU_DCDB_CH|IU_DCDB); + + UPDATE_IRQ; + } + + /* Check for RX */ + if ((iu_contty.conf & RX_EN) && contty_ldsc[0].conn) { + c = tmxr_getc_ln(&contty_ldsc[0]); + if (c && !(c & SCPE_BREAK)) { + iu_rx(&iu_contty, (uint8) c); + } + } + + tmxr_clock_coschedule(uptr, tmxr_poll); + + return SCPE_OK; +} + +t_stat iu_svc_contty_xmt(UNIT *uptr) +{ + dma_channel *chan = &dma_state.channels[DMA_IUB_CHAN]; + TMLN *lp = &contty_ldsc[0]; + t_stat result; + + /* Check for data in the transmitter shift register that's ready + * to go out to the TX line */ + if (iu_contty.tx_state & T_XMIT) { + if (LOOPBACK(&iu_contty)) { + /* Handle loopback mode if set */ + sim_debug(EXECUTE_MSG, &contty_dev, "iu_svc_contty: CONTTY is in loopback.\n"); + iu_contty.tx_state &= ~T_XMIT; + + iu_rx(&iu_contty, iu_contty.txr); + + if (TX_ENABLED(iu_contty) && !(iu_contty.tx_state & T_HOLD)) { + iu_contty.sr |= STS_TXE; + } + } else { + /* Direct mode, no loopback */ + result = tmxr_putc_ln(lp, iu_contty.txr); + if (result == SCPE_STALL) { + sim_debug(EXECUTE_MSG, &contty_dev, "iu_svc_contty: CONTTY PUTC STALL: %d\n", result); + sim_activate_after(uptr, 1000); + return SCPE_OK; + } else { + tmxr_poll_tx(&contty_desc); + iu_contty.tx_state &= ~T_XMIT; + if (TX_ENABLED(iu_contty) && !(iu_contty.tx_state & T_HOLD)) { + iu_contty.sr |= STS_TXE; + } + } + } + } + + /* Check for data in the holding register that's ready to go + * out to the transmitter shift register */ + if (iu_contty.tx_state & T_HOLD) { + sim_debug(EXECUTE_MSG, &contty_dev, + "THRB->TXRB: 0x%02x (%c)\n", + iu_contty.thr, PCHAR(iu_contty.thr)); + iu_contty.tx_state &= ~T_HOLD; + iu_contty.tx_state |= T_XMIT; + iu_contty.txr = iu_contty.thr; + /* If the transmitter is currently enabled, we need to update + * the TxRDY and TxEMT flags */ + if (TX_ENABLED(iu_contty)) { + iu_contty.sr &= ~STS_TXE; + iu_contty.sr |= STS_TXR; + iu_state.isr |= ISTS_TXRB; + /* DRQ is always tied to TxRDY */ + iu_contty.drq = TRUE; + } + + sim_activate_after_abs(uptr, uptr->wait); + } + + UPDATE_IRQ; + + /* If we're done transmitting and there's more DMA to do, + * do it. */ + if (!iu_contty.tx_state && + chan->wcount_c >= 0 && + ((dma_state.mask >> DMA_IUB_CHAN) & 0x1) == 0) { + sim_debug(EXECUTE_MSG, &tto_dev, + "iu_svc_contty_xmt: Triggering next DMA\n"); + iu_dma_contty(DMA_IUB_CHAN, IUBASE+IUB_DATA_REG); + } + + return SCPE_OK; +} + +t_stat iu_svc_timer(UNIT *uptr) +{ + iu_state.isr |= ISTS_CRI; + + sim_debug(EXECUTE_MSG, &iu_timer_dev, + "[iu_svc_timer] IMR=%02x ISR=%02x => %02x\n", + iu_state.imr, iu_state.isr, (iu_state.imr & iu_state.isr)); + + UPDATE_IRQ; + return SCPE_OK; +} + +/* + * Reg | Name (Read) | Name (Write) + * -----+-------------------------+---------------------------- + * 0 | Mode Register 1/2 A | Mode Register 1/2 A + * 1 | Status Register A | Clock Select Register A + * 2 | BRG Test | Command Register A + * 3 | Rx Holding Register A | Tx Holding Register A + * 4 | Input Port Change Reg. | Aux. Control Register + * 5 | Interrupt Status Reg. | Interrupt Mask Register + * 6 | Counter/Timer Upper Val | C/T Upper Preset Val. + * 7 | Counter/Timer Lower Val | C/T Lower Preset Val. + * 8 | Mode Register B | Mode Register B + * 9 | Status Register B | Clock Select Register B + * 10 | 1X/16X Test | Command Register B + * 11 | Rx Holding Register B | Tx Holding Register B + * 12 | *Reserved* | *Reserved* + * 13 | Input Ports IP0 to IP6 | Output Port Conf. Reg. + * 14 | Start Counter Command | Set Output Port Bits Cmd. + * 15 | Stop Counter Command | Reset Output Port Bits Cmd. + */ + + +uint32 iu_read(uint32 pa, size_t size) +{ + uint8 reg, modep; + uint32 data = 0; + + reg = (uint8) (pa - IUBASE); + + switch (reg) { + case MR12A: + modep = iu_console.modep; + data = iu_console.mode[modep]; + iu_increment_a = TRUE; + break; + case SRA: + data = iu_console.sr; + break; + case RHRA: + data = iu_rx_getc(&iu_console); + break; + case IPCR: + data = iu_state.ipcr; + /* Reading the port resets the top 4 bits */ + iu_state.ipcr &= 0xf; + break; + case ISR: + data = iu_state.isr; + break; + case CTU: + data = (iu_timer_state.c_set >> 8) & 0xff; + break; + case CTL: + data = iu_timer_state.c_set & 0xff; + break; + case MR12B: + modep = iu_contty.modep; + data = iu_contty.mode[modep]; + iu_increment_b = TRUE; + break; + case SRB: + data = iu_contty.sr; + break; + case RHRB: + data = iu_rx_getc(&iu_contty); + break; + case INPRT: + data = iu_state.inprt; + break; + case START_CTR: + data = 0; + iu_state.isr &= ~ISTS_CRI; + sim_debug(EXECUTE_MSG, &iu_timer_dev, + "ACR=%02x : Activating IU Timer in %d ticks / %d microseconds\n", + iu_state.acr, iu_timer_state.c_set, (int32)(iu_timer_state.c_set * iu_timer_multiplier)); + sim_activate_after(&iu_timer_unit, (int32)(iu_timer_state.c_set * iu_timer_multiplier)); + break; + case STOP_CTR: + data = 0; + iu_state.isr &= ~ISTS_CRI; + UPDATE_IRQ; + sim_cancel(&iu_timer_unit); + break; + case 17: /* Clear DMA interrupt */ + data = 0; + CLR_DMA_INT; + break; + default: + break; + } + + return data; +} + +void iu_write(uint32 pa, uint32 val, size_t size) +{ + uint8 reg; + uint8 modep; + uint8 bval = (uint8) val; + char line_config[16]; + + reg = (uint8) (pa - IUBASE); + + switch (reg) { + case MR12A: + modep = iu_console.modep; + iu_console.mode[modep] = bval; + iu_increment_a = TRUE; + break; + case CSRA: + /* Nothing supported */ + break; + case CRA: /* Command A */ + iu_w_cmd(&iu_console, bval); + break; + case THRA: /* TX/RX Buf A */ + iu_tx(&iu_console, bval); + break; + case ACR: /* Auxiliary Control Register */ + iu_state.acr = bval; + brg_reg = (bval >> 7) & 1; + break; + case IMR: + iu_state.imr = bval; + UPDATE_IRQ; + break; + case CTUR: /* Counter/Timer Upper Preset Value */ + /* Clear out high byte */ + iu_timer_state.c_set &= 0x00ff; + /* Set high byte */ + iu_timer_state.c_set |= ((uint16) bval << 8); + break; + case CTLR: /* Counter/Timer Lower Preset Value */ + /* Clear out low byte */ + iu_timer_state.c_set &= 0xff00; + /* Set low byte */ + iu_timer_state.c_set |= bval; + break; + case MR12B: + modep = iu_contty.modep; + iu_contty.mode[modep] = bval; + sim_debug(EXECUTE_MSG, &tto_dev, "MR12B: Page %d Mode = %02x\n", modep, bval); + iu_increment_b = TRUE; + if (modep == 0) { + if ((bval >> 4) & 1) { + /* No parity */ + parity_sel = IU_PARITY_NONE; + } else { + /* Parity enabled */ + if (bval & 4) { + parity_sel = IU_PARITY_ODD; + } else { + parity_sel = IU_PARITY_EVEN; + } + } + + bits_per_char = (bval & 3) + 5; + } + break; + case CRB: + iu_w_cmd(&iu_contty, bval); + break; + case CSRB: + brg_clk = (bval >> 4) & 0xf; + + if (brg_rates[brg_reg][brg_clk] != NULL) { + sprintf(line_config, "%s-%d%s1", + brg_rates[brg_reg][brg_clk], + bits_per_char, + parity[parity_sel]); + + sim_debug(EXECUTE_MSG, &contty_dev, + "Setting CONTTY line to %s\n", + line_config); + + tmxr_set_config_line(&contty_ldsc[0], line_config); + } + + break; + case THRB: + iu_tx(&iu_contty, bval); + break; + case OPCR: + iu_state.opcr = bval; + break; + case SOPR: +#if defined (REV2) + /* Bit 2 of the IU output register is used as a soft power + * switch. When set, the machine will power down + * immediately. */ + if (bval & IU_KILLPWR) { + stop_reason = STOP_POWER; + } +#endif + break; + case ROPR: + break; + case 17: /* Clear DMA interrupt */ + sim_debug(EXECUTE_MSG, &tto_dev, + "[WRITE] Clear DMA interrupt in UART\n"); + CLR_DMA_INT; + break; + default: + break; + } +} + +/* + * Transmit a single character + */ +t_stat iu_tx(IU_PORT *port, uint8 val) +{ + int32 c; + uint8 tx_ists = (port == &iu_console) ? ISTS_TXRA : ISTS_TXRB; + UNIT *uptr = (port == &iu_console) ? &tto_unit : &contty_unit[1]; + + sim_debug(EXECUTE_MSG, &tto_dev, + "iu_tx PORT=%d CHAR=%02x (%c)\n", + PORTNO(port), val, PCHAR(val)); + + if (!(port->conf & TX_EN) || !(port->sr & STS_TXR)) { + sim_debug(EXECUTE_MSG, &tto_dev, + ">>> IGNORING TRANSMIT, NOT ENABLED OR NOT READY!!!\n"); + return SCPE_INCOMP; + } + + c = sim_tt_outcvt(val, TTUF_MODE_8B); + + if (c >= 0) { + port->tx_state |= T_HOLD; + port->sr &= ~(STS_TXR|STS_TXE); + port->drq = FALSE; + iu_state.isr &= ~(tx_ists); + port->thr = c; + sim_activate_after(uptr, uptr->wait); + } + + return SCPE_OK; +} + +static void iu_w_cmd(IU_PORT *port, uint8 cmd) +{ + uint8 tx_ists = (port == &iu_console) ? ISTS_TXRA : ISTS_TXRB; + uint8 dbk_ists = (port == &iu_console) ? ISTS_DBA : ISTS_DBB; + + /* Enable or disable transmitter */ + /* Disable always wins, if both are set */ + if (cmd & CMD_DTX) { + port->conf &= ~TX_EN; + port->sr &= ~(STS_TXR|STS_TXE); + port->drq = FALSE; + iu_state.isr &= ~tx_ists; + UPDATE_IRQ; + sim_debug(EXECUTE_MSG, &tto_dev, + "DISABLE TX, PORT %d\n", PORTNO(port)); + } else if (cmd & CMD_ETX) { + if (!(port->conf & TX_EN)) { + /* TXE and TXR are always set by an ENABLE if prior state + was DISABLED */ + port->sr |= (STS_TXR|STS_TXE); + port->drq = TRUE; + } + port->conf |= TX_EN; + iu_state.isr |= tx_ists; + UPDATE_IRQ; + sim_debug(EXECUTE_MSG, &tto_dev, + "ENABLE TX, PORT %d\n", PORTNO(port)); + } + + /* Enable or disable receiver. */ + /* Disable always wins, if both are set */ + if (cmd & CMD_DRX) { + port->conf &= ~RX_EN; + port->sr &= ~STS_RXR; + } else if (cmd & CMD_ERX) { + port->conf |= RX_EN; + } + + /* Command register bits 6-4 have special meaning */ + switch ((cmd >> CMD_MISC_SHIFT) & CMD_MISC_MASK) { + case CR_RST_MR: + /* Causes the Channel A MR pointer to point to MR1. */ + port->modep = 0; + break; + case CR_RST_RX: + sim_debug(EXECUTE_MSG, &tto_dev, + "PORT %d Command: RESET RX\n", PORTNO(port)); + /* Reset receiver. Resets the Channel's receiver as if a + hardware reset had been applied. The receiver is disabled + and the FIFO is flushed. */ + port->sr &= ~STS_RXR; + port->conf &= ~RX_EN; + port->w_p = 0; + port->r_p = 0; + break; + case CR_RST_TX: + sim_debug(EXECUTE_MSG, &tto_dev, + "PORT %d Command: RESET TX\n", PORTNO(port)); + /* Reset transmitter. Resets the Channel's transmitter as if a + hardware reset had been applied. */ + port->sr &= ~STS_TXR; + port->sr &= ~STS_TXE; + port->drq = FALSE; /* drq is tied to TXR */ + port->conf &= ~TX_EN; + break; + case CR_RST_ERR: + /* Reset error status. Clears the Channel's Received Break, + Parity Error, Framing Error, and Overrun Error bits in the + status register (SRn[7:4]). */ + sim_debug(EXECUTE_MSG, &tto_dev, + "PORT %d Command: RESET ERROR\n", PORTNO(port)); + port->sr &= ~(STS_RXB|STS_FER|STS_PER|STS_OER); + break; + case CR_RST_BRK: + /* Reset Channel's break change interrupt. Causes the Channel + A break detect change bit in the interrupt status register + (ISR[2] for Chan. A, ISR[6] for Chan. B) to be cleared to + zero. */ + sim_debug(EXECUTE_MSG, &tto_dev, + "PORT %d Command: RESET BREAK IRQ\n", PORTNO(port)); + iu_state.isr &= ~dbk_ists; + break; + case CR_START_BRK: + sim_debug(EXECUTE_MSG, &tto_dev, + "PORT %d Command: START BREAK. loopback=%d\n", + PORTNO(port), LOOPBACK(port)); + if (LOOPBACK(port)) { + /* Set "Received Break" and "Parity Error" bits in + SRA/SRB */ + port->sr |= (STS_RXB|STS_PER); + /* Set "Delta Break" bit A or B in ISR */ + iu_state.isr |= dbk_ists; + } + break; + case CR_STOP_BRK: + sim_debug(EXECUTE_MSG, &tto_dev, + "PORT %d Command: STOP BREAK. loopback=%d\n", + PORTNO(port), LOOPBACK(port)); + if (LOOPBACK(port)) { + /* Set "Delta Break" bit A or B in ISR */ + iu_state.isr |= dbk_ists; + } + break; + } + + UPDATE_IRQ; +} + +/* + * Initiate DMA transfer or continue one already in progress. + */ +void iu_dma_console(uint8 channel, uint32 service_address) +{ + uint8 data; + uint32 addr; + t_stat status = SCPE_OK; + dma_channel *chan = &dma_state.channels[channel]; + IU_PORT *port = &iu_console; + + /* If we're doing DMA and we're done, end it */ + if (iu_console.dma && chan->wcount_c < 0) { + sim_debug(EXECUTE_MSG, &tto_dev, + "iu_svc_tto: DMA Complete.\n"); + iu_console.dma = FALSE; + dma_state.mask |= (1 << DMA_IUA_CHAN); + dma_state.status |= (1 << DMA_IUA_CHAN); + SET_DMA_INT; + return; + } + + /* Mark that IUA is in DMA */ + port->dma = TRUE; + + switch (DMA_XFER(DMA_IUA_CHAN)) { + case DMA_XFER_READ: + addr = dma_address(channel, chan->ptr); + chan->addr_c = addr; + data = pread_b(addr, BUS_PER); + status = iu_tx(port, data); + if (status == SCPE_OK) { + chan->ptr++; + chan->wcount_c--; + } + break; + default: + sim_debug(EXECUTE_MSG, &tto_dev, + "iu_dma_console: Error, transfer type %d not supported\n", + DMA_XFER(DMA_IUA_CHAN)); + break; + } +} + +void iu_dma_contty(uint8 channel, uint32 service_address) +{ + uint8 data; + uint32 addr; + t_stat status = SCPE_OK; + dma_channel *chan = &dma_state.channels[channel]; + IU_PORT *port = &iu_contty; + + /* If we're doing DMA and we're done, end it */ + if (iu_contty.dma && chan->wcount_c < 0) { + sim_debug(EXECUTE_MSG, &contty_dev, + "iu_svc_contty_xmt: DMA Complete.\n"); + iu_contty.dma = FALSE; + dma_state.mask |= (1 << DMA_IUB_CHAN); + dma_state.status |= (1 << DMA_IUB_CHAN); + SET_DMA_INT; + return; + } + + /* Mark that IUB is in DMA */ + port->dma = TRUE; + + switch (DMA_XFER(DMA_IUB_CHAN)) { + case DMA_XFER_READ: + addr = dma_address(channel, chan->ptr); + chan->addr_c = addr; + data = pread_b(addr, BUS_PER); + status = iu_tx(port, data); + if (status == SCPE_OK) { + chan->ptr++; + chan->wcount_c--; + } + break; + default: + sim_debug(EXECUTE_MSG, &contty_dev, + "iu_dma_contty: Error, transfer type %d not supported\n", + DMA_XFER(DMA_IUB_CHAN)); + break; + } +} diff --git a/3B2/3b2_iu.h b/3B2/3b2_iu.h index 9017a8bd..2fb61887 100644 --- a/3B2/3b2_iu.h +++ b/3B2/3b2_iu.h @@ -1,234 +1,234 @@ -/* 3b2_iu.h: SCN2681A Dual UART - - Copyright (c) 2017-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#ifndef __3B2_IU_H__ -#define __3B2_IU_H__ - -#include "3b2_defs.h" - -#define CMD_ERX 0x01 /* Enable receiver */ -#define CMD_DRX 0x02 /* Disable receiver */ -#define CMD_ETX 0x04 /* Enable transmitter */ -#define CMD_DTX 0x08 /* Disable transmitter */ -#define CMD_MISC_SHIFT 4 /* Command */ -#define CMD_MISC_MASK 0x7 - -#define IU_SPEED_REGS 2 /* Two speed select registers, */ -#define IU_SPEEDS 16 /* with 16 speeds each */ - -#define IU_PARITY_ODD 0 -#define IU_PARITY_EVEN 1 -#define IU_PARITY_NONE 2 - -#define STS_RXR 0x01 /* Receiver ready */ -#define STS_FFL 0x02 /* FIFO full */ -#define STS_TXR 0x04 /* Transmitter ready */ -#define STS_TXE 0x08 /* Transmitter empty */ -#define STS_OER 0x10 /* Overrun error */ -#define STS_PER 0x20 /* Parity error */ -#define STS_FER 0x40 /* Framing error */ -#define STS_RXB 0x80 /* Received break */ - -#define ISTS_TXRA 0x01 /* Transmitter ready A */ -#define ISTS_RXRA 0x02 /* Receiver ready A */ -#define ISTS_DBA 0x04 /* Delta Break A */ -#define ISTS_CRI 0x08 /* Counter ready */ -#define ISTS_TXRB 0x10 /* Transmitter ready B */ -#define ISTS_RXRB 0x20 /* Receiver ready B */ -#define ISTS_DBB 0x40 /* Delta Break B */ -#define ISTS_IPC 0x80 /* Interrupt port change */ - -#define MODE_V_CHM 6 /* Channel mode */ -#define MODE_M_CHM 0x3 - -/* Transmitter State bits */ -#define T_HOLD 1 -#define T_XMIT 2 - -/* Used by the DMAC */ -#define IUA_DATA_REG 3 -#define IUB_DATA_REG 11 - -/* Registers - Read */ -#define SRA 1 -#define RHRA 3 -#define IPCR 4 -#define ISR 5 -#define CTU 6 -#define CTL 7 -#define SRB 9 -#define RHRB 11 -#define INPRT 13 -#define START_CTR 14 -#define STOP_CTR 15 - -/* Registers - Write */ -#define CSRA 1 -#define CRA 2 -#define THRA 3 -#define ACR 4 -#define IMR 5 -#define CTUR 6 -#define CTLR 7 -#define CSRB 9 -#define CRB 10 -#define THRB 11 -#define OPCR 13 -#define SOPR 14 -#define ROPR 15 - -/* Registers - R/W */ -#define MR12A 0 -#define MR12B 8 - -/* Port configuration */ -#define TX_EN 1 -#define RX_EN 2 - -/* Control Register commands */ -#define CR_RST_MR 1 -#define CR_RST_RX 2 -#define CR_RST_TX 3 -#define CR_RST_ERR 4 -#define CR_RST_BRK 5 -#define CR_START_BRK 6 -#define CR_STOP_BRK 7 - -/* IMR bits */ -#define IMR_TXRA 0x01 -#define IMR_RXRA 0x02 -#define IMR_CTR 0x08 -#define IMR_TXRB 0x10 -#define IMR_RXRB 0x20 - -/* Power-off bit */ -#define IU_KILLPWR 0x04 - -#define PORT_A 0 -#define PORT_B 1 - -#define IU_MODE(x) ((x & UM_MASK) >> UM_SHIFT) - -#define IUBASE 0x49000 -#define IUSIZE 0x100 - -#define IU_BUF_SIZE 3 - -/* Data Carrier Detect inputs and input change bits */ -#if defined(REV3) -#define IU_DCDB_CH 0x80 -#define IU_DCDA_CH 0x40 -#define IU_DCDB 0x08 -#define IU_DCDA 0x04 -#else -#define IU_DCDB_CH 0x20 -#define IU_DCDA_CH 0x10 -#define IU_DCDB 0x02 -#define IU_DCDA 0x01 -#endif - -/* Default baud rate generator (9600 baud) */ -#define BRG_DEFAULT 11 - -/* The 2681 DUART includes a 16-bit timer/counter that can be used to - * trigger an interrupt after a certain amount of time has passed. - * - * The 2681 uses a crystal with a frequency of 3.686400 MHz, and the - * timer/counter uses this frequency divided by 16, giving a final - * timer/counter frequency of 230,400 Hz. There are therefore 4.34 - * microseconds of wall time per tick of the timer. - * - * The multiplier defined below is a default that can be adjusted to - * make IU timing faster, but less accurate, if desired */ - -#define IU_TIMER_MULTIPLIER 4 - -typedef struct iu_port { - uint8 cmd; /* Command */ - uint8 mode[2]; /* Two mode buffers */ - uint8 modep; /* Point to mode[0] or mode[1] */ - uint8 conf; /* Configuration bits */ - uint8 sr; /* Status Register */ - uint8 thr; /* Transmit Holding Register */ - uint8 txr; /* Transmit Shift Register */ - uint8 rxr; /* Receive Shift Register */ - uint8 rxbuf[IU_BUF_SIZE]; /* Receive Holding Register (3 bytes) */ - uint8 w_p; /* Receive Buffer Write Pointer */ - uint8 r_p; /* Receive Buffer Read Pointer */ - uint8 tx_state; /* Transmitting state flags (HOLD, XMIT) */ - t_bool dma; /* DMA currently active */ - t_bool drq; /* DMA request enabled */ - t_bool rxr_full; /* Receive Shift Register is full */ -} IU_PORT; - -typedef struct iu_state { - uint8 isr; /* Interrupt Status Register */ - uint8 imr; /* Interrupt Mask Register */ - uint8 acr; /* Aux. Control Register */ - uint8 opcr; /* Output Port Configuration */ - uint8 inprt; /* Input Port Data */ - uint8 ipcr; /* Input Port Change Register */ -} IU_STATE; - -typedef struct iu_timer_state { - uint16 c_set; - t_bool c_en; -} IU_TIMER_STATE; - -/* Function prototypes */ -t_stat contty_attach(UNIT *uptr, CONST char *cptr); -t_stat contty_detach(UNIT *uptr); -t_stat tti_reset(DEVICE *dptr); -t_stat contty_reset(DEVICE *dptr); -t_stat iu_timer_reset(DEVICE *dptr); -t_stat iu_timer_set_mult(UNIT *uptr, int32 val, CONST char *cptr, void *desc); -t_stat iu_timer_show_mult(FILE *st, UNIT *uptr, int val, CONST void *desc); -t_stat iu_svc_tti(UNIT *uptr); -t_stat iu_svc_tto(UNIT *uptr); -t_stat iu_svc_contty(UNIT *uptr); -t_stat iu_svc_contty_xmt(UNIT *uptr); -t_stat iu_svc_timer(UNIT *uptr); -uint32 iu_read(uint32 pa, size_t size); -void iu_write(uint32 pa, uint32 val, size_t size); -void iua_drq_handled(); -void iub_drq_handled(); -void iu_txrdy_a_irq(); -void iu_txrdy_b_irq(); -void iu_dma_console(uint8 channel, uint32 service_address); -void iu_dma_contty(uint8 channel, uint32 service_address); -void increment_modep_a(); -void increment_modep_b(); - -extern IU_PORT iu_console; -extern IU_PORT iu_contty; -extern t_bool iu_increment_a; -extern t_bool iu_increment_b; - -#endif +/* 3b2_iu.h: SCN2681A Dual UART + + Copyright (c) 2017-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#ifndef __3B2_IU_H__ +#define __3B2_IU_H__ + +#include "3b2_defs.h" + +#define CMD_ERX 0x01 /* Enable receiver */ +#define CMD_DRX 0x02 /* Disable receiver */ +#define CMD_ETX 0x04 /* Enable transmitter */ +#define CMD_DTX 0x08 /* Disable transmitter */ +#define CMD_MISC_SHIFT 4 /* Command */ +#define CMD_MISC_MASK 0x7 + +#define IU_SPEED_REGS 2 /* Two speed select registers, */ +#define IU_SPEEDS 16 /* with 16 speeds each */ + +#define IU_PARITY_ODD 0 +#define IU_PARITY_EVEN 1 +#define IU_PARITY_NONE 2 + +#define STS_RXR 0x01 /* Receiver ready */ +#define STS_FFL 0x02 /* FIFO full */ +#define STS_TXR 0x04 /* Transmitter ready */ +#define STS_TXE 0x08 /* Transmitter empty */ +#define STS_OER 0x10 /* Overrun error */ +#define STS_PER 0x20 /* Parity error */ +#define STS_FER 0x40 /* Framing error */ +#define STS_RXB 0x80 /* Received break */ + +#define ISTS_TXRA 0x01 /* Transmitter ready A */ +#define ISTS_RXRA 0x02 /* Receiver ready A */ +#define ISTS_DBA 0x04 /* Delta Break A */ +#define ISTS_CRI 0x08 /* Counter ready */ +#define ISTS_TXRB 0x10 /* Transmitter ready B */ +#define ISTS_RXRB 0x20 /* Receiver ready B */ +#define ISTS_DBB 0x40 /* Delta Break B */ +#define ISTS_IPC 0x80 /* Interrupt port change */ + +#define MODE_V_CHM 6 /* Channel mode */ +#define MODE_M_CHM 0x3 + +/* Transmitter State bits */ +#define T_HOLD 1 +#define T_XMIT 2 + +/* Used by the DMAC */ +#define IUA_DATA_REG 3 +#define IUB_DATA_REG 11 + +/* Registers - Read */ +#define SRA 1 +#define RHRA 3 +#define IPCR 4 +#define ISR 5 +#define CTU 6 +#define CTL 7 +#define SRB 9 +#define RHRB 11 +#define INPRT 13 +#define START_CTR 14 +#define STOP_CTR 15 + +/* Registers - Write */ +#define CSRA 1 +#define CRA 2 +#define THRA 3 +#define ACR 4 +#define IMR 5 +#define CTUR 6 +#define CTLR 7 +#define CSRB 9 +#define CRB 10 +#define THRB 11 +#define OPCR 13 +#define SOPR 14 +#define ROPR 15 + +/* Registers - R/W */ +#define MR12A 0 +#define MR12B 8 + +/* Port configuration */ +#define TX_EN 1 +#define RX_EN 2 + +/* Control Register commands */ +#define CR_RST_MR 1 +#define CR_RST_RX 2 +#define CR_RST_TX 3 +#define CR_RST_ERR 4 +#define CR_RST_BRK 5 +#define CR_START_BRK 6 +#define CR_STOP_BRK 7 + +/* IMR bits */ +#define IMR_TXRA 0x01 +#define IMR_RXRA 0x02 +#define IMR_CTR 0x08 +#define IMR_TXRB 0x10 +#define IMR_RXRB 0x20 + +/* Power-off bit */ +#define IU_KILLPWR 0x04 + +#define PORT_A 0 +#define PORT_B 1 + +#define IU_MODE(x) ((x & UM_MASK) >> UM_SHIFT) + +#define IUBASE 0x49000 +#define IUSIZE 0x100 + +#define IU_BUF_SIZE 3 + +/* Data Carrier Detect inputs and input change bits */ +#if defined(REV3) +#define IU_DCDB_CH 0x80 +#define IU_DCDA_CH 0x40 +#define IU_DCDB 0x08 +#define IU_DCDA 0x04 +#else +#define IU_DCDB_CH 0x20 +#define IU_DCDA_CH 0x10 +#define IU_DCDB 0x02 +#define IU_DCDA 0x01 +#endif + +/* Default baud rate generator (9600 baud) */ +#define BRG_DEFAULT 11 + +/* The 2681 DUART includes a 16-bit timer/counter that can be used to + * trigger an interrupt after a certain amount of time has passed. + * + * The 2681 uses a crystal with a frequency of 3.686400 MHz, and the + * timer/counter uses this frequency divided by 16, giving a final + * timer/counter frequency of 230,400 Hz. There are therefore 4.34 + * microseconds of wall time per tick of the timer. + * + * The multiplier defined below is a default that can be adjusted to + * make IU timing faster, but less accurate, if desired */ + +#define IU_TIMER_MULTIPLIER 4 + +typedef struct iu_port { + uint8 cmd; /* Command */ + uint8 mode[2]; /* Two mode buffers */ + uint8 modep; /* Point to mode[0] or mode[1] */ + uint8 conf; /* Configuration bits */ + uint8 sr; /* Status Register */ + uint8 thr; /* Transmit Holding Register */ + uint8 txr; /* Transmit Shift Register */ + uint8 rxr; /* Receive Shift Register */ + uint8 rxbuf[IU_BUF_SIZE]; /* Receive Holding Register (3 bytes) */ + uint8 w_p; /* Receive Buffer Write Pointer */ + uint8 r_p; /* Receive Buffer Read Pointer */ + uint8 tx_state; /* Transmitting state flags (HOLD, XMIT) */ + t_bool dma; /* DMA currently active */ + t_bool drq; /* DMA request enabled */ + t_bool rxr_full; /* Receive Shift Register is full */ +} IU_PORT; + +typedef struct iu_state { + uint8 isr; /* Interrupt Status Register */ + uint8 imr; /* Interrupt Mask Register */ + uint8 acr; /* Aux. Control Register */ + uint8 opcr; /* Output Port Configuration */ + uint8 inprt; /* Input Port Data */ + uint8 ipcr; /* Input Port Change Register */ +} IU_STATE; + +typedef struct iu_timer_state { + uint16 c_set; + t_bool c_en; +} IU_TIMER_STATE; + +/* Function prototypes */ +t_stat contty_attach(UNIT *uptr, CONST char *cptr); +t_stat contty_detach(UNIT *uptr); +t_stat tti_reset(DEVICE *dptr); +t_stat contty_reset(DEVICE *dptr); +t_stat iu_timer_reset(DEVICE *dptr); +t_stat iu_timer_set_mult(UNIT *uptr, int32 val, CONST char *cptr, void *desc); +t_stat iu_timer_show_mult(FILE *st, UNIT *uptr, int val, CONST void *desc); +t_stat iu_svc_tti(UNIT *uptr); +t_stat iu_svc_tto(UNIT *uptr); +t_stat iu_svc_contty(UNIT *uptr); +t_stat iu_svc_contty_xmt(UNIT *uptr); +t_stat iu_svc_timer(UNIT *uptr); +uint32 iu_read(uint32 pa, size_t size); +void iu_write(uint32 pa, uint32 val, size_t size); +void iua_drq_handled(); +void iub_drq_handled(); +void iu_txrdy_a_irq(); +void iu_txrdy_b_irq(); +void iu_dma_console(uint8 channel, uint32 service_address); +void iu_dma_contty(uint8 channel, uint32 service_address); +void increment_modep_a(); +void increment_modep_b(); + +extern IU_PORT iu_console; +extern IU_PORT iu_contty; +extern t_bool iu_increment_a; +extern t_bool iu_increment_b; + +#endif diff --git a/3B2/3b2_mau.c b/3B2/3b2_mau.c index 7c10bc74..fc14b27c 100644 --- a/3B2/3b2_mau.c +++ b/3B2/3b2_mau.c @@ -1,3595 +1,3595 @@ -/* 3b2_mau.c: WE32106 and WE32206 Math Accelerator Unit - - Copyright (c) 2021-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. - - --------------------------------------------------------------------- - - This file is part of a simulation of the WE 32106 and WE 32206 Math - Acceleration Units. The WE 32106 and WE 32206 are IEEE-754 - compabitle floating point hardware math accelerators that were - available as an optional component on the AT&T 3B2/310 and 3B2/400, - and as a standard component on the 3B2/500, 3B2/600, 3B2/700, and - 3B2/1000. - - Unimplemented Features - ====================== - - All features of the WE 32106 MAU have been implemented, but there - remain some features of the WE 32206 that are not yet implemented. - Neither System V UNIX nor the Version 3 MAU diagnostics appear to - use these features in any way, but there is no guarantee that other - software does not use them. They are: - - - The FE, UW, and WF bits of the Auxiliary Status Register - - The new operand registers f4 through f7 - - The register bank select feature of the Command Register - - The RC and RCS bits of the Command Register - - The new WE 32206 instructions: - 1. ATAN - 2. COS - 3. PI - 4. SIN - - --------------------------------------------------------------------- - - Portions of this code are derived from the SoftFloat 2c library by - John R. Hauser. Functions derived from SoftFloat 2c are clearly - marked in the comments. - - Legal Notice - ============ - - SoftFloat was written by John R. Hauser. Release 2c of SoftFloat - was made possible in part by the International Computer Science - Institute, located at Suite 600, 1947 Center Street, Berkeley, - California 94704. Funding was partially provided by the National - Science Foundation under grant MIP-9311980. The original version - of this code was written as part of a project to build a - fixed-point vector processor in collaboration with the University - of California at Berkeley, overseen by Profs. Nelson Morgan and - John Wawrzynek. - - THIS SOFTWARE IS DISTRIBUTED AS IS, FOR FREE. Although reasonable - effort has been made to avoid it, THIS SOFTWARE MAY CONTAIN FAULTS - THAT WILL AT TIMES RESULT IN INCORRECT BEHAVIOR. USE OF THIS - SOFTWARE IS RESTRICTED TO PERSONS AND ORGANIZATIONS WHO CAN AND - WILL TOLERATE ALL LOSSES, COSTS, OR OTHER PROBLEMS THEY INCUR DUE - TO THE SOFTWARE WITHOUT RECOMPENSE FROM JOHN HAUSER OR THE - INTERNATIONAL COMPUTER SCIENCE INSTITUTE, AND WHO FURTHERMORE - EFFECTIVELY INDEMNIFY JOHN HAUSER AND THE INTERNATIONAL COMPUTER - SCIENCE INSTITUTE (possibly via similar legal notice) AGAINST ALL - LOSSES, COSTS, OR OTHER PROBLEMS INCURRED BY THEIR CUSTOMERS AND - CLIENTS DUE TO THE SOFTWARE, OR INCURRED BY ANYONE DUE TO A - DERIVATIVE WORK THEY CREATE USING ANY PART OF THE SOFTWARE. - - The following are expressly permitted, even for commercial - purposes: - - (1) distribution of SoftFloat in whole or in part, as long as this - and other legal notices remain and are prominent, and provided also - that, for a partial distribution, prominent notice is given that it - is a subset of the original; and - - (2) inclusion or use of SoftFloat in whole or in part in a - derivative work, provided that the use restrictions above are met - and the minimal documentation requirements stated in the source - code are satisfied. - --------------------------------------------------------------------- -*/ - -#include "3b2_mau.h" - -#include - -#include "3b2_cpu.h" -#include "3b2_mem.h" -#include "3b2_mmu.h" - -#define MAU_ID 0 /* Coprocessor ID of MAU */ - -#define TININESS_BEFORE_ROUNDING TRUE - -/* Static function declarations */ -static SIM_INLINE void mau_case_div_zero(XFP *op1, XFP *op2, XFP *result); - -static SIM_INLINE void mau_exc(uint32 flag, uint32 mask); -static SIM_INLINE void abort_on_fault(); -static SIM_INLINE void mau_decode(uint32 cmd, uint32 src, uint32 dst); -static SIM_INLINE t_bool le_128(t_uint64 a0, t_uint64 a1, t_uint64 b0, t_uint64 b1); -static SIM_INLINE t_bool eq_128(t_uint64 a0, t_uint64 a1, t_uint64 b0, t_uint64 b1); -static SIM_INLINE t_bool lt_128(t_uint64 a0, t_uint64 a1, t_uint64 b0, t_uint64 b1); -static uint8 leading_zeros(uint32 val); -static uint8 leading_zeros_64(t_int64 val); -static void shift_right_32_jamming(uint32 val, int16 count, uint32 *result); -static void shift_right_64_jamming(t_uint64 val, int16 count, t_uint64 *result); -static void shift_right_extra_64_jamming(t_uint64 val_a, t_uint64 val_b, int16 count, - t_uint64 *r_a, t_uint64 *r_b); -static void shift_right_128_jamming(t_uint64 val_a, t_uint64 val_b, int16 count, - t_uint64 *r_a, t_uint64 *r_b); -static void short_shift_left_128(t_uint64 val_a, t_uint64 val_b, int16 count, - t_uint64 *r_a, t_uint64 *r_b); -static void shift_right_128(t_uint64 val_a, t_uint64 val_b, int16 count, - t_uint64 *r_a, t_uint64 *r_b); -static void add_128(t_uint64 a0, t_uint64 a1, - t_uint64 b0, t_uint64 b1, - t_uint64 *r_low, t_uint64 *r_high); -static void sub_128(t_uint64 a0, t_uint64 a1, - t_uint64 b0, t_uint64 b1, - t_uint64 *r_low, t_uint64 *r_high); -static void mul_64_to_128(t_uint64 a, t_uint64 b, t_uint64 *r_low, t_uint64 *r_high); -static void mul_64_by_shifted_32_to_128(t_uint64 a, uint32 b, t_mau_128 *result); -static t_uint64 estimate_div_128_to_64(t_uint64 a0, t_uint64 a1, t_uint64 b); -static uint32 round_pack_int(t_bool sign, t_uint64 frac, RM rounding_mode); -static t_int64 round_pack_int64(t_bool sign, - t_uint64 abs_0, t_uint64 abs_1, - RM rounding_mode); - -static SFP round_pack_sfp(t_bool sign, int16 exp, - uint32 frac, RM rounding_mode); -static DFP round_pack_dfp(t_bool sign, int16 exp, t_uint64 frac, - t_bool xfp_sticky, RM rounding_mode); -static void round_pack_xfp(t_bool sign, int32 exp, - t_uint64 frac_a, t_uint64 frac_b, - RM rounding_mode, XFP *result); -static void propagate_xfp_nan(XFP *a, XFP *b, XFP *result); -static void propagate_xfp_nan_128(XFP* a, XFP* b, t_mau_128* result); -static void normalize_round_pack_xfp(t_bool sign, int32 exp, - t_uint64 frac_0, t_uint64 frac_1, - RM rounding_mode, XFP *result); -static void normalize_sfp_subnormal(uint32 in_frac, int16 *out_exp, uint32 *out_frac); -static void normalize_dfp_subnormal(t_uint64 in_frac, int16 *out_exp, t_uint64 *out_frac); -static void normalize_xfp_subnormal(t_uint64 in_frac, int32 *out_exp, t_uint64 *out_frac); - -static T_NAN sfp_to_common_nan(SFP val); -static T_NAN dfp_to_common_nan(DFP val); -static T_NAN xfp_to_common_nan(XFP *val); -static SFP common_nan_to_sfp(T_NAN nan); -static DFP common_nan_to_dfp(T_NAN nan); -static void common_nan_to_xfp(T_NAN nan, XFP *result); - -static void sfp_to_xfp(SFP val, XFP *result); -static void dfp_to_xfp(DFP val, XFP *result); -static SFP xfp_to_sfp(XFP *val, RM rounding_mode); -static DFP xfp_to_dfp(XFP *val, RM rounding_mode); - -static uint32 xfp_eq(XFP *a, XFP *b); -static uint32 xfp_lt(XFP *a, XFP *b); - -static void xfp_cmp(XFP *a, XFP *b); -static void xfp_cmpe(XFP *a, XFP *b); -static void xfp_cmps(XFP *a, XFP *b); -static void xfp_cmpes(XFP *a, XFP *b); -static void xfp_add(XFP *a, XFP *b, XFP *result, RM rounding_mode); -static void xfp_sub(XFP *a, XFP *b, XFP *result, RM rounding_mode); -static void xfp_mul(XFP *a, XFP *b, XFP *result, RM rounding_mode); -static void xfp_div(XFP *a, XFP *b, XFP *result, RM rounding_mode); -static void xfp_sqrt(XFP *a, XFP *result, RM rounding_mode); -static void xfp_remainder(XFP *a, XFP *b, XFP *result, RM rounding_mode); - -static void load_src_op(uint8 op, XFP *xfp); -static void load_op1_decimal(DEC *d); -static void store_op3_int(uint32 val); -static void store_op3_decimal(DEC *d); -static void store_op3(XFP *xfp); - -static void mau_rdasr(); -static void mau_wrasr(); -static void mau_move(); -static void mau_cmp(); -static void mau_cmps(); -static void mau_cmpe(); -static void mau_cmpes(); -static void mau_ldr(); -static void mau_erof(); -static void mau_rtoi(); -static void mau_ftoi(); -static void mau_dtof(); -static void mau_ftod(); -static void mau_add(); -static void mau_sub(); -static void mau_mul(); -static void mau_div(); -static void mau_neg(); -static void mau_abs(); -static void mau_sqrt(); -static void mau_itof(); -static void mau_remainder(); - -static void mau_execute(); - -UNIT mau_unit = { UDATA(NULL, 0, 0) }; - -MAU_STATE mau_state; - -BITFIELD asr_bits[] = { -#if defined(REV3) - BIT(FE), - BITFFMT(VER,3,%d), - BIT(UW), -#else - BITNCF(5), -#endif - BIT(PR), - BIT(QS), - BIT(US), - BIT(OS), - BIT(IS), - BIT(PM), - BIT(QM), - BIT(UM), - BIT(OM), - BIT(IM), -#if defined(REV3) - BIT(WF), -#else - BITNCF(1), -#endif - BIT(UO), - BIT(CSC), - BIT(PS), - BIT(IO), - BIT(Z), - BIT(N), - BITFFMT(RC,2,%d), - BIT(NTNC), - BIT(ECP), - BITNCF(5), - BIT(RA), - ENDBITS -}; - -REG mau_reg[] = { - { HRDATAD (CMD, mau_state.cmd, 32, "Command Word") }, - { HRDATADF (ASR, mau_state.asr, 32, "ASR", asr_bits) }, - { HRDATAD (OPCODE, mau_state.opcode, 8, "Opcode") }, - { HRDATAD (OP1, mau_state.op1, 8, "Operand 1") }, - { HRDATAD (OP2, mau_state.op2, 8, "Operand 2") }, - { HRDATAD (OP3, mau_state.op3, 8, "Operand 3") }, - { NULL } -}; - -MTAB mau_mod[] = { - { UNIT_EXBRK, UNIT_EXBRK, "Break on exceptions", "EXBRK", - NULL, NULL, NULL, "Enables break on floating point exceptions" }, - { UNIT_EXBRK, 0, "No break on exceptions", "NOEXBRK", - NULL, NULL, NULL, "Disables break on floating point exceptions" }, - { 0 } -}; - -static DEBTAB mau_debug[] = { - { "DECODE", DECODE_DBG, "Decode" }, - { "TRACE", TRACE_DBG, "Call Trace" }, - { NULL } -}; - -DEVICE mau_dev = { - "MAU", /* name */ - &mau_unit, /* units */ - mau_reg, /* registers */ - mau_mod, /* modifiers */ - 1, /* #units */ - 16, /* address radix */ - 32, /* address width */ - 1, /* address incr. */ - 16, /* data radix */ - 8, /* data width */ - NULL, /* examine routine */ - NULL, /* deposit routine */ - &mau_reset, /* reset routine */ - NULL, /* boot routine */ - NULL, /* attach routine */ - NULL, /* detach routine */ - NULL, /* context */ -#ifdef REV3 - DEV_DEBUG, /* Rev 3 flags: Always required */ -#else - DEV_DISABLE|DEV_DEBUG, /* Rev 2 flags: Can be disabled */ -#endif - 0, /* debug control flags */ - mau_debug, /* debug flag names */ - NULL, /* memory size change */ - NULL, /* logical name */ - NULL, /* help routine */ - NULL, /* attach help routine */ - NULL, /* help context */ - &mau_description /* device description */ -}; - -XFP INF = { - 0x7fff, - 0x0000000000000000ull, - 0 -}; - -XFP TRAPPING_NAN = { - 0x7fff, - 0x7fffffffffffffffull, - 0 -}; - -/* Generated Non-Trapping NaN - * p. 2-8 "When the MAU generates a nontrapping NaN, J+fraction - * contains all 1s. The MAU never generates a trapping NaN." - */ -XFP GEN_NONTRAPPING_NAN = { - 0x7fff, - 0xffffffffffffffffull, - 0 -}; - -CONST char *mau_op_names[32] = { - "0x00", "0x01", "ADD", "SUB", - "DIV", "REM", "MUL", "MOVE", - "RDASR", "WRASR", "CMP", "CMPE", - "ABS", "SQRT", "RTOI", "FTOI", - "ITOF", "DTOF", "FTOD", "NOP", - "EROF", "0x15", "0x16", "NEG", - "LDR", "0x19", "CMPS", "CMPES", -#if defined(REV3) - "SIN", "COS", "ATAN", "PI" -#else - "0x1C", "0x1D", "0x1E", "0x1F" -#endif -}; - -CONST char *src_op_names[8] = { - "F0", "F1", "F2", "F3", - "MEM S", "MEM D", "MEM X", "N/A" -}; - -CONST char *dst_op_names[16] = { - "F0 S", "F1 S", "F2 S", "F3 S", - "F0 D", "F1 D", "F2 D", "F3 D", - "F0 X", "F1 X", "F2 X", "F3 X", - "MEM S", "MEM D", "MEM X", "N/A" -}; - -/* - * Special Cases - * ------------- - * - * The handling of combinations of special input values is specified - * in the "WE32106 Math Acceleration Unit Information Manual" - * pp. 5-3--5-5. - * - * Each of these "special case" routines can be called by math - * functions based on a combination of the input values. - * - * (At the moment, only divide-by-zero is explicitly called out here - * as a special case) - */ - -static SIM_INLINE void mau_case_div_zero(XFP *op1, XFP *op2, XFP *result) -{ - mau_state.asr |= MAU_ASR_QS; - - if (mau_state.asr & MAU_ASR_QM) { - mau_state.asr |= MAU_ASR_ECP; - PACK_XFP(0, 0x7fff, 0x8000000000000000ull, result); - } else { - if (XFP_SIGN(op1) ^ XFP_SIGN(op2)) { - PACK_XFP(1, INF.sign_exp, INF.frac, result); - } else { - PACK_XFP(0, INF.sign_exp, INF.frac, result); - } - } -} - -static SIM_INLINE void mau_exc(uint32 flag, uint32 mask) -{ - sim_debug(TRACE_DBG, &mau_dev, - "[mau_exc] asr=%08x flag=%08x mask=%08x\n", - mau_state.asr, flag, mask); - - mau_state.asr |= flag; - - /* - * page 2-14: NTNC bit is checked if an Invalid Operation - * exception occurs while the Invalid Operation Mask bit is - * clear. If NTNC is set to 1, an exception occurs and bit 9 - * (IS) is set. If NTNC is set to 0, no exception occurs, - * and a nontraping NaN is generated. - */ - if (flag == MAU_ASR_IS && (mau_state.asr & MAU_ASR_IM) == 0) { - if (mau_state.asr & MAU_ASR_NTNC) { - mau_state.asr |= MAU_ASR_ECP; - } else { - mau_state.ntnan = TRUE; - } - return; - } - - if (mau_state.asr & mask) { - mau_state.asr |= MAU_ASR_ECP; - } -} - -/* - * Returns true if an exceptional condition is present. - */ -static SIM_INLINE t_bool mau_exception_present() -{ - - return mau_state.asr & MAU_ASR_ECP && - (((mau_state.asr & MAU_ASR_IS) && ((mau_state.asr & MAU_ASR_IM) || - (mau_state.asr & MAU_ASR_NTNC))) || - ((mau_state.asr & MAU_ASR_US) && (mau_state.asr & MAU_ASR_UM)) || - ((mau_state.asr & MAU_ASR_OS) && (mau_state.asr & MAU_ASR_OM)) || - ((mau_state.asr & MAU_ASR_PS) && (mau_state.asr & MAU_ASR_PM)) || - ((mau_state.asr & MAU_ASR_QS) && (mau_state.asr & MAU_ASR_QM))); -} - -static SIM_INLINE void abort_on_fault() -{ - switch(mau_state.opcode) { - case M_NOP: - case M_RDASR: - case M_WRASR: - case M_EROF: - case M_LDR: - return; - default: - /* - * Integer overflow is non-maskable in the MAU, but generates an Integer - * Overflow exception to be handled by the WE32100 CPU (if not masked - * in the CPU's PSW). - */ - if ((mau_state.asr & MAU_ASR_IO) && (R[NUM_PSW] & PSW_OE_MASK)) { - if (mau_unit.flags & UNIT_EXBRK) { - stop_reason = STOP_EX; - } - sim_debug(TRACE_DBG, &mau_dev, - "[abort_on_fault] Aborting on un-maskable overflow fault. ASR=%08x\n", - mau_state.asr); - cpu_abort(NORMAL_EXCEPTION, INTEGER_OVERFLOW); - } - - /* Otherwise, check for other exceptions. */ - if (mau_exception_present()) { - if (mau_unit.flags & UNIT_EXBRK) { - stop_reason = STOP_EX; - } - sim_debug(TRACE_DBG, &mau_dev, - "[abort_on_fault] Aborting on ECP fault. ASR=%08x\n", - mau_state.asr); - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); - } - - break; - } -} - -/* - * Clears N and Z flags in the ASR if appropriate. - */ -static void clear_asr() -{ - mau_state.ntnan = FALSE; - - switch(mau_state.opcode) { - case M_NOP: - case M_RDASR: - case M_WRASR: - case M_EROF: - return; - default: - mau_state.asr &= ~(MAU_ASR_Z|MAU_ASR_N|MAU_ASR_ECP); - break; - } -} - -/* - * Returns true if the 'nz' flags should be set. - * - * Note: There is an undocumented feature of the WE32106 expressed - * here. If an exception has occured, the Z and N flags are not to be - * set! - */ -static t_bool set_nz() -{ - switch(mau_state.opcode) { - case M_NOP: - case M_RDASR: - case M_WRASR: - case M_EROF: - return FALSE; - default: - return (mau_state.asr & MAU_ASR_ECP) == 0; - } -} - -t_stat mau_reset(DEVICE *dptr) -{ - memset(&mau_state, 0, sizeof(MAU_STATE)); -#if defined(REV3) - mau_state.asr |= MAU_ASR_VER; /* Version 1 MAU */ -#endif - return SCPE_OK; -} - -/************************************************************************* - * Utility Functions - ************************************************************************/ - -/* - * Compare two 128-bit values a and b. Rturns true if a <= b - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static SIM_INLINE t_bool le_128(t_uint64 a0, t_uint64 a1, t_uint64 b0, t_uint64 b1) -{ - return (a0 < b0) || ((a0 == b0) && (a1 <= b1)); -} - -/* - * Compare two 128-bit values a and b. Returns true if a = b - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static SIM_INLINE t_bool eq_128(t_uint64 a0, t_uint64 a1, t_uint64 b0, t_uint64 b1) -{ - return (a0 == b0) && (a1 == b1); -} - -/* - * Compare two 128-bit values a and b. Returns true if a < b - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static SIM_INLINE t_bool lt_128(t_uint64 a0, t_uint64 a1, t_uint64 b0, t_uint64 b1) -{ - return (a0 < b0) || ((a0 == b0) && (a1 < b1)); -} - -/* - * Return the number of leading binary zeros in an unsigned 32-bit - * value. - * - * Algorithm couresty of "Hacker's Delight" by Henry S. Warren. - */ -static uint8 leading_zeros(uint32 val) -{ - unsigned n = 0; - - if (val <= 0x0000ffff) { - n += 16; - val <<= 16; - } - if (val <= 0x00ffffff) { - n += 8; - val <<= 8; - } - if (val <= 0x0fffffff) { - n += 4; - val <<= 4; - } - if (val <= 0x3fffffff) { - n += 2; - val <<= 2; - } - if (val <= 0x7fffffff) { - n++; - } - - return n; -} - -/* - * Return the number of leading binary zeros in a signed 64-bit - * value. - */ -static uint8 leading_zeros_64(t_int64 val) -{ - uint8 n = 0; - - if (val == 0) { - return 64; - } - - while (1) { - if (val < 0) break; - - n++; - - val <<= 1; - } - - return n; -} - -/* - * Shift a 32-bit unsigned value, 'val', right by 'count' bits. If any - * non-zero bits are shifted off, they are "jammed" into the least - * significant bit of the result by setting the least significant bit - * to 1. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static void shift_right_32_jamming(uint32 val, int16 count, uint32 *result) -{ - uint32 tmp; - - if (count == 0) { - tmp = val; - } else if (count < 32) { - tmp = (val >> count) | ((val << ((-count) & 31)) != 0); - } else { - tmp = (val != 0); - } - - *result = tmp; -} - -/* - * Shift a 64-bit unsigned value, 'val', right by 'count' bits. If any - * non-zero bits are shifted off, they are "jammed" into the least - * significant bit of the result by setting the least significant bit - * to 1. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static void shift_right_64_jamming(t_uint64 val, int16 count, t_uint64 *result) -{ - t_uint64 tmp; - - if (count == 0) { - tmp = val; - } else if (count < 64) { - tmp = (val >> count) | ((val << ((-count) & 63)) != 0); - } else { - tmp = (val != 0); - } - - *result = tmp; -} - -/* - * Shifts the 128-bit value formed by concatenating val_a and val_b - * right by 64 _plus_ the number of bits given in 'count'. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static void shift_right_extra_64_jamming(t_uint64 val_a, t_uint64 val_b, int16 count, - t_uint64 *r_a, t_uint64 *r_b) -{ - t_uint64 a, b; - int8 neg_count = (-count) & 63; - - if (count == 0) { - b = val_b; - a = val_a; - } else if (count < 64) { - b = (val_a << neg_count) | (val_b != 0); - a = val_a >> count; - } else { - if (count == 64) { - b = val_a | (val_b != 0); - } else { - b = ((val_a | val_b) != 0); - } - a = 0; - } - - *r_a = a; - *r_b = b; -} - -/* - * Shift the 128-bit value formed by val_a and val_b right by - * 64 plus the number of bits given in count. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static void shift_right_128_jamming(t_uint64 val_a, t_uint64 val_b, int16 count, - t_uint64 *r_a, t_uint64 *r_b) -{ - t_uint64 tmp_a, tmp_b; - int8 neg_count = (-count) & 63; - - if (count == 0) { - tmp_a = val_a; - tmp_b = val_b; - } else if (count < 64) { - tmp_a = (val_a >> count); - tmp_b = (val_a << neg_count) | (val_b != 0); - } else { - if (count == 64) { - tmp_b = val_a | (val_b != 0); - } else { - tmp_b = ((val_a | val_b) != 0); - } - tmp_a = 0; - } - - *r_a = tmp_a; - *r_b = tmp_b; -} - -/* - * Shifts the 128-bit value formed by val_a and val_b left by the - * number of bits given in count. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static void short_shift_left_128(t_uint64 val_a, t_uint64 val_b, int16 count, - t_uint64 *r_a, t_uint64 *r_b) -{ - *r_b = val_b << count; - if (count == 0) { - *r_a = val_a; - } else { - *r_a = (val_a << count) | (val_b >> ((-count) & 63)); - } -} - -/* - * Shifts the 128-bit value formed by val_a and val_b right by the - * number of bits given ihn 'count'. Any bits shifted off are lost. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static void shift_right_128(t_uint64 val_a, t_uint64 val_b, int16 count, - t_uint64 *r_a, t_uint64 *r_b) -{ - t_uint64 tmp_a, tmp_b; - int8 neg_count; - - neg_count = (- count) & 63; - - if (count == 0) { - tmp_a = val_a; - tmp_b = val_b; - } else if (count < 64) { - tmp_a = val_a >> count; - tmp_b = (val_a << neg_count) | (val_b >> count); - } else { - tmp_a = 0; - tmp_b = (count < 128) ? (val_a >> (count & 63)) : 0; - } - - *r_a = tmp_a; - *r_b = tmp_b; -} - -/* - * Add two 128-bit values. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static void add_128(t_uint64 a0, t_uint64 a1, - t_uint64 b0, t_uint64 b1, - t_uint64 *r_low, t_uint64 *r_high) -{ - t_uint64 tmp; - - tmp = a1 + b1; - *r_high = tmp; - *r_low = a0 + b0 + (tmp < a1); -} - -/* - * Subract two 128-bit values. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static void sub_128(t_uint64 a0, t_uint64 a1, - t_uint64 b0, t_uint64 b1, - t_uint64 *r_low, t_uint64 *r_high) -{ - *r_high = a1 - b1; - *r_low = a0 - b0 - (a1 < b1); -} - -/* - * Multiplies a by b to obtain a 128-bit product. The product is - * broken into two 64-bit pieces which are stored at r_low and r_high. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static void mul_64_to_128(t_uint64 a, t_uint64 b, t_uint64 *r_low, t_uint64 *r_high) -{ - uint32 a_high, a_low, b_high, b_low; - t_uint64 rl, rm_a, rm_b, rh; - - a_low = (uint32)a; - a_high = a >> 32; - - b_low = (uint32)b; - b_high = b >> 32; - - rh = ((t_uint64) a_low) * b_low; - rm_a = ((t_uint64) a_low) * b_high; - rm_b = ((t_uint64) a_high) * b_low; - rl = ((t_uint64) a_high) * b_high; - - rm_a += rm_b; - - rl += (((t_uint64)(rm_a < rm_b)) << 32) + (rm_a >> 32); - rm_a <<= 32; - rh += rm_a; - rl += (rh < rm_a); - - *r_high = rh; - *r_low = rl; -} - -/* - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static void mul_64_by_shifted_32_to_128(t_uint64 a, uint32 b, t_mau_128 *result) -{ - t_uint64 mid; - - mid = (t_uint64)(uint32) a * b; - result->low = mid << 32; - result->high = (t_uint64)(uint32)(a >> 32) * b + (mid >> 32); -} - -/* - * Returns an approximation of the 64-bit integer value obtained by - * dividing 'b' into the 128-bit value 'a0' and 'a1'. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static t_uint64 estimate_div_128_to_64(t_uint64 a0, t_uint64 a1, t_uint64 b) -{ - t_uint64 b0, b1; - t_uint64 rem0, rem1, term0, term1; - t_uint64 z; - - if (b <= a0) { - return 0xffffffffffffffffull; - } - - b0 = b >> 32; - z = (b0 << 32 <= a0) ? 0xffffffff00000000ull : (a0 / b0) << 32; - - mul_64_to_128( b, z, &term0, &term1 ); - - sub_128( a0, a1, term0, term1, &rem0, &rem1 ); - - while (((int64_t)rem0) < 0) { - z -= 0x100000000ull; - b1 = b << 32; - add_128(rem0, rem1, b0, b1, &rem0, &rem1); - } - - rem0 = (rem0 << 32) | (rem1 >> 32); - z |= (b0<<32 <= rem0) ? 0xffffffff : rem0 / b0; - - return z; -} - -static uint32 approx_recip_sqrt_32(uint32 oddExpA, uint32 a) -{ - int index; - uint16 eps, r0; - uint32 ESqrR0; - uint32 sigma0; - uint32 r; - uint32 sqrSigma0; - - static const uint16 softfloat_approxRecipSqrt_1k0s[16] = { - 0xB4C9, 0xFFAB, 0xAA7D, 0xF11C, 0xA1C5, 0xE4C7, 0x9A43, 0xDA29, - 0x93B5, 0xD0E5, 0x8DED, 0xC8B7, 0x88C6, 0xC16D, 0x8424, 0xBAE1 - }; - static const uint16 softfloat_approxRecipSqrt_1k1s[16] = { - 0xA5A5, 0xEA42, 0x8C21, 0xC62D, 0x788F, 0xAA7F, 0x6928, 0x94B6, - 0x5CC7, 0x8335, 0x52A6, 0x74E2, 0x4A3E, 0x68FE, 0x432B, 0x5EFD - }; - - index = (a>>27 & 0xE) + oddExpA; - eps = (uint16) (a>>12); - r0 = softfloat_approxRecipSqrt_1k0s[index] - - ((softfloat_approxRecipSqrt_1k1s[index] * (uint32) eps) - >>20); - ESqrR0 = (uint32) r0 * r0; - if ( ! oddExpA ) ESqrR0 <<= 1; - sigma0 = ~(uint32) (((uint32) ESqrR0 * (t_uint64) a)>>23); - r = ((uint32) r0<<16) + ((r0 * (t_uint64) sigma0)>>25); - sqrSigma0 = ((t_uint64) sigma0 * sigma0)>>32; - r += ((uint32) ((r>>1) + (r>>3) - ((uint32) r0<<14)) - * (t_uint64) sqrSigma0) - >>48; - if ( ! (r & 0x80000000) ) r = 0x80000000; - return r; -} - -/* - * Return the properly rounded 32-bit integer corresponding to 'sign' - * and 'frac'. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static uint32 round_pack_int(t_bool sign, t_uint64 frac, RM rounding_mode) -{ - int8 round_increment, round_bits; - int32 result; - - round_increment = 0x40; - - if (!(rounding_mode == ROUND_NEAREST)) { - if (rounding_mode == ROUND_ZERO) { - round_increment = 0; - } else { - round_increment = 0x7f; - if (sign) { - if (rounding_mode == ROUND_PLUS_INF) { - round_increment = 0; - } - } else { - if (rounding_mode == ROUND_MINUS_INF) { - round_increment = 0; - } - } - } - } - - round_bits = frac & 0x7f; - frac = (frac + round_increment) >> 7; - frac &= ~((t_uint64)((round_bits ^ 0x40) == 0) & - (t_uint64)(rounding_mode == ROUND_NEAREST)); - - result = (int32)frac; - - if (sign) { - result = -result; - } - - if ((frac >> 32) || (result && ((result < 0) ^ sign))) { - mau_exc(MAU_ASR_IO, MAU_ASR_OM); /* Integer overflow */ - mau_exc(MAU_ASR_PS, MAU_ASR_PM); /* Inexact */ - return sign ? (int32) 0x80000000 : 0x7fffffff; - } - - if (round_bits) { - mau_exc(MAU_ASR_PS, MAU_ASR_PM); - } - - return result; -} - -/* - * Return the properly rounded 64-bit integer corresponding to 'sign' - * and 'frac'. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static t_int64 round_pack_int64(t_bool sign, - t_uint64 abs_0, t_uint64 abs_1, - RM rounding_mode) -{ - t_bool increment; - int64_t z; - - increment = (t_int64)abs_1 < 0; - - if (rounding_mode != ROUND_NEAREST) { - if (rounding_mode == ROUND_ZERO) { - increment = 0; - } else { - if (sign) { - increment = (rounding_mode == ROUND_MINUS_INF) && abs_1; - } else { - increment = (rounding_mode == ROUND_PLUS_INF) && abs_1; - } - } - } - - if (increment) { - ++abs_0; - if (abs_0 == 0) { - /* Overflow */ - mau_exc(MAU_ASR_OS, MAU_ASR_OM); - return sign ? 0x8000000000000000ull : 0x7fffffffffffffffull; - } - abs_0 &= ~((t_uint64)((abs_1 << 1) == 0) & - (t_uint64)(rounding_mode == ROUND_NEAREST)); - } - - z = abs_0; - if (sign) { - z = -z; - } - if (z && ((z < 0) ^ sign)) { - /* Overflow */ - mau_exc(MAU_ASR_OS, MAU_ASR_OM); - return sign ? 0x8000000000000000ull : 0x7fffffffffffffffull; - } - - if (abs_1) { - mau_exc(MAU_ASR_PS, MAU_ASR_PM); - } - - return z; -} - -/* - * Return a properly rounded 32-bit floating point value, given a sign - * bit, exponent, fractional part, and a rounding mode. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static SFP round_pack_sfp(t_bool sign, int16 exp, uint32 frac, RM rounding_mode) -{ - int8 round_increment, round_bits; - uint8 is_tiny; - - is_tiny = 0; - round_increment = 0x40; - - if (rounding_mode != ROUND_NEAREST) { - if (rounding_mode == ROUND_ZERO) { - round_increment = 0; - } else { - if (sign) { - if (rounding_mode == ROUND_PLUS_INF) { - round_increment = 0; - } - } else { - if (rounding_mode == ROUND_MINUS_INF) { - round_increment = 0; - } - } - } - } - - round_bits = frac & 0x7f; - - if (0xfd <= (uint16) exp) { - if ((0xfd < exp) || - (exp == 0xfd && (int32)(frac + round_increment) < 0)) { - mau_exc(MAU_ASR_OS, MAU_ASR_OM); - mau_exc(MAU_ASR_PS, MAU_ASR_PM); - return PACK_SFP(sign, 0xff, 0) - (round_increment == 0); - } - if (exp < 0) { - is_tiny = (TININESS_BEFORE_ROUNDING || - ((exp < -1) || - (frac + round_increment < 0x80000000))); - shift_right_32_jamming(frac, -exp, &frac); - exp = 0; - round_bits = frac & 0x7f; - if (is_tiny && round_bits) { - mau_exc(MAU_ASR_US, MAU_ASR_UM); - } - } - } - - if (round_bits) { - mau_exc(MAU_ASR_PS, MAU_ASR_PM); - } - - frac = (frac + round_increment) >> 7; - frac &= ~((t_uint64)((round_bits ^ 0x40) == 0) & - (t_uint64)(rounding_mode == ROUND_NEAREST)); - if (frac == 0) { - exp = 0; - } - - return PACK_SFP(sign, exp, frac); -} - -/* - * Return a properly rounded 64-bit floating point value, given a sign - * bit, exponent, fractional part, and a rounding mode. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static DFP round_pack_dfp(t_bool sign, int16 exp, t_uint64 frac, - t_bool xfp_sticky, RM rounding_mode) -{ - int16 round_increment, round_bits; - t_bool lsb, round, sticky; - uint8 is_tiny; - - is_tiny = 0; - round_increment = 0; - - if (rounding_mode != ROUND_NEAREST) { - if (rounding_mode == ROUND_ZERO) { - round_increment = 0; - } else { - round_increment = 0x7ff; - if (sign) { - if (rounding_mode == ROUND_PLUS_INF) { - round_increment = 0; - } - } else { - if (rounding_mode == ROUND_MINUS_INF) { - round_increment = 0; - } - } - } - } - - round_bits = frac & 0x7ff; - - if (0x7fd <= (uint16) exp) { - if (exp < 0) { - is_tiny = (TININESS_BEFORE_ROUNDING || - (exp < -1) || - ((frac + round_increment) < 0x8000000000000000ull)); - shift_right_64_jamming(frac, -exp, &frac); - exp = 0; - round_bits = frac & 0x7ff; - if (is_tiny && round_bits) { - mau_exc(MAU_ASR_US, MAU_ASR_UM); - } - } else if (0x7fd < exp) { - mau_exc(MAU_ASR_OS, MAU_ASR_OM); - mau_exc(MAU_ASR_PS, MAU_ASR_PM); - return (PACK_DFP(sign, 0x7ff, 0) - (round_increment == 0)); - } - } - - if (round_bits) { - mau_exc(MAU_ASR_PS, MAU_ASR_PM); - } - - if (rounding_mode == ROUND_NEAREST) { - frac >>= 11; - lsb = (frac & 1) != 0; - round = (round_bits & 0x400) != 0; - sticky = ((round_bits & 0x3ff) != 0) | xfp_sticky; - if (round & (sticky || lsb)) { - frac++; - if (frac == 0) { - exp++; - } - } - } else { - frac = (frac + round_increment) >> 11; - lsb = !((t_bool)(round_bits ^ 0x200)); - frac &= ~((t_uint64)lsb); - } - - return PACK_DFP(sign, exp, frac); -} - -/* - * Return a properly rounded 80-bit floating point value, given a sign - * bit, exponent, fractional part, and a rounding mode. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static void round_pack_xfp(t_bool sign, int32 exp, - t_uint64 frac_a, t_uint64 frac_b, - RM rounding_mode, XFP *result) -{ - uint8 is_tiny; - t_int64 round_mask; - - if (0x7ffd <= (uint32)(exp - 1)) { - if (0x7ffe < exp) { - round_mask = 0; - mau_exc(MAU_ASR_OS, MAU_ASR_OM); - mau_exc(MAU_ASR_PS, MAU_ASR_PM); - if ((rounding_mode == ROUND_ZERO) || - (sign && (rounding_mode == ROUND_PLUS_INF)) || - (!sign && (rounding_mode == ROUND_MINUS_INF))) { - PACK_XFP(sign, 0x7ffe, ~round_mask, result); - return; - } - PACK_XFP(sign, 0x7fff, 0x8000000000000000ull, result); - return; - } - if (exp <= 0) { - is_tiny = (TININESS_BEFORE_ROUNDING || - (exp < 0) || - (frac_a < 0xffffffffffffffffull)); - shift_right_extra_64_jamming(frac_a, frac_b, (int16)(1 - exp), &frac_a, &frac_b); - exp = 0; - if (is_tiny && frac_b) { - mau_exc(MAU_ASR_US, MAU_ASR_UM); - } - if (frac_b) { - mau_exc(MAU_ASR_PS, MAU_ASR_PM); - } - PACK_XFP(sign, exp, frac_a, result); - return; - } - } - if (frac_b) { - mau_exc(MAU_ASR_PS, MAU_ASR_PM); - } - if (frac_a == 0) { - exp = 0; - } - PACK_XFP_S(sign, exp, frac_a, frac_b, result); -} - -/* - * Given two 80-bit floating point values 'a' and 'b', one of which is - * a NaN, return the appropriate NaN result. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static void propagate_xfp_nan(XFP *a, XFP *b, XFP *result) -{ - uint8 a_is_nan, a_is_signaling_nan; - uint8 b_is_nan, b_is_signaling_nan; - - a_is_nan = XFP_IS_NAN(a); - a_is_signaling_nan = XFP_IS_TRAPPING_NAN(a); - b_is_nan = XFP_IS_NAN(b); - b_is_signaling_nan = XFP_IS_TRAPPING_NAN(b); - - a->frac |= 0xc000000000000000ull; - b->frac |= 0xc000000000000000ull; - - if (a_is_signaling_nan | b_is_signaling_nan) { - mau_exc(MAU_ASR_IS, MAU_ASR_IM); - } - - if (a_is_nan) { - if (a_is_signaling_nan & b_is_nan) { - result->sign_exp = b->sign_exp; - result->frac = b->frac; - } else { - result->sign_exp = a->sign_exp; - result->frac = a->frac; - } - } else { - result->sign_exp = b->sign_exp; - result->frac = b->frac; - } -} - -/* - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static void propagate_xfp_nan_128(XFP* a, XFP* b, t_mau_128* result) -{ - t_bool is_sig_nan_a, is_sig_nan_b; - t_uint64 non_frac_a_low, non_frac_b_low; - uint16 mag_a, mag_b; - - is_sig_nan_a = XFP_IS_TRAPPING_NAN(a); - is_sig_nan_b = XFP_IS_TRAPPING_NAN(b); - - non_frac_a_low = a->frac & 0xC000000000000000ull; - non_frac_b_low = b->frac & 0xC000000000000000ull; - - if (is_sig_nan_a | is_sig_nan_b) { - /* Invalid */ - mau_exc(MAU_ASR_IS, MAU_ASR_IM); - if (is_sig_nan_a) { - if (is_sig_nan_b) goto return_larger_mag; - if (XFP_IS_NAN(b)) goto return_b; - goto return_a; - } else { - if (XFP_IS_NAN(a)) goto return_a; - goto return_b; - } - } - - return_larger_mag: - mag_a = a->frac & 0x7fff; - mag_b = b->frac & 0x7fff; - if (mag_a < mag_b) goto return_b; - if (mag_b < mag_a) goto return_a; - if (a->frac < b->frac) goto return_b; - if (b->frac < a->frac) goto return_a; - if (a->sign_exp < b->sign_exp) goto return_a; - return_b: - result->high = b->sign_exp; - result->low = non_frac_b_low; - return; - return_a: - result->high = a->sign_exp; - result->low = non_frac_a_low; - return; -} - -/* - * Normalize and round an extended-precision floating point value. - * - * Partially derived from the SoftFloat 2c package (see copyright - * notice above) - */ -static void normalize_round_pack_xfp(t_bool sign, int32 exp, - t_uint64 frac_0, t_uint64 frac_1, - RM rounding_mode, XFP *result) -{ - int8 shift_count; - - if (frac_0 == 0) { - frac_0 = frac_1; - frac_1 = 0; - exp -= 64; - } - - shift_count = leading_zeros_64(frac_0); - short_shift_left_128(frac_0, frac_1, shift_count, &frac_0, &frac_1); - exp -= shift_count; - - round_pack_xfp(sign, exp, frac_0, frac_1, rounding_mode, result); -} - - -/* - * Normalize the subnormal 80-bit floating point value represented by - * the denormalized input fractional comonent. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static void normalize_sfp_subnormal(uint32 in_frac, int16 *out_exp, uint32 *out_frac) -{ - int8 shift_count; - - shift_count = leading_zeros(in_frac) - 8; - - if (shift_count < 0) { - /* There was invalid input, there's nothing we can do. */ - *out_frac = in_frac; - *out_exp = 0; - return; - } - - *out_frac = in_frac << shift_count; - *out_exp = (uint16)(1 - shift_count); -} - -/* - * Normalize the subnormal 64-bit floating point value represented by - * the denormalized input fractional comonent. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static void normalize_dfp_subnormal(t_uint64 in_frac, int16 *out_exp, t_uint64 *out_frac) -{ - int8 shift_count; - - shift_count = leading_zeros_64(in_frac) - 11; - - if (shift_count < 0) { - /* There was invalid input, there's nothing we can do. */ - *out_frac = in_frac; - *out_exp = 0; - return; - } - - *out_frac = in_frac << shift_count; - *out_exp = 1 - shift_count; -} - -/* - * Normalize the subnormal 32-bit floating point value represented by - * the denormalized input fractional comonent. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static void normalize_xfp_subnormal(t_uint64 in_frac, int32 *out_exp, t_uint64 *out_frac) -{ - int8 shift_count; - - shift_count = leading_zeros_64(in_frac); - if (shift_count < 64) { - *out_frac = in_frac << shift_count; - } else { - *out_frac = 0; - } - *out_exp = 1 - shift_count; -} - -/* - * Returns the result of converting the 32-bit floating point NaN - * value to the canonincal NaN format. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static T_NAN sfp_to_common_nan(SFP val) -{ - T_NAN nan = {0}; - - if (SFP_IS_TRAPPING_NAN(val)) { - mau_state.trapping_nan = TRUE; - } - - nan.sign = val >> 31; - nan.low = 0; - nan.high = ((t_uint64) val) << 41; - - return nan; -} - -/* - * Returns the result of converting the 64-bit floating point NaN - * value to the canonincal NaN format. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static T_NAN dfp_to_common_nan(DFP val) -{ - T_NAN nan = {0}; - - if (DFP_IS_TRAPPING_NAN(val)) { - mau_state.trapping_nan = TRUE; - } - - nan.sign = (val >> 63) & 1; - nan.low = 0; - nan.high = (t_uint64)(val << 12); - - return nan; -} - -/* - * Returns the result of converting the 80-bit floating point NaN - * value to the canonincal NaN format. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static T_NAN xfp_to_common_nan(XFP *val) -{ - T_NAN nan = {0}; - - if (XFP_IS_TRAPPING_NAN(val)) { - mau_state.trapping_nan = TRUE; - } - - nan.sign = val->sign_exp >> 15; - nan.low = 0; - nan.high = val->frac << 1; - - return nan; -} - -/* - * Returns the result of converting a canonical NAN format value to a - * 32-bit floating point format. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static SFP common_nan_to_sfp(T_NAN nan) -{ - return ((((uint32)nan.sign) << 31) - | 0x7fc00000 - | (nan.high >> 41)); -} - -/* - * Returns the result of converting a canonical NAN format value to a - * 64-bit floating point format. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static DFP common_nan_to_dfp(T_NAN nan) -{ - return ((((t_uint64)nan.sign) << 63) - | 0x7ff8000000000000ull - | (nan.high >> 12)); -} - -/* - * Returns the result of converting a canonical NAN format value to an - * 80-bit floating point format. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static void common_nan_to_xfp(T_NAN nan, XFP *result) -{ - result->frac = 0xc000000000000000ull | (nan.high >> 1); - result->sign_exp = (((uint16)nan.sign) << 15) | 0x7fff; -} - -/* - * Convert a 32-bit floating point value to an 80-bit floating point - * value. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static void sfp_to_xfp(SFP val, XFP *result) -{ - t_bool sign; - int16 exp; - uint32 frac; - - sign = SFP_SIGN(val); - exp = SFP_EXP(val); - frac = SFP_FRAC(val); - - if (exp == 0xff) { - if (frac) { - common_nan_to_xfp(sfp_to_common_nan(val), result); - return; - } - } - - if (exp == 0) { - if (frac == 0) { - PACK_XFP(sign, 0, 0, result); - return; - } - normalize_sfp_subnormal(frac, &exp, &frac); - } - - frac |= 0x800000; - - PACK_XFP(sign, exp + 0x3f80, ((t_uint64) frac) << 40, result); -} - -/* - * Convert a 64-bit floating point value to an 80-bit floating point value. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -void dfp_to_xfp(DFP val, XFP *result) -{ - t_bool sign; - int16 exp; - t_uint64 frac; - - sign = DFP_SIGN(val); - exp = DFP_EXP(val); - frac = DFP_FRAC(val); - - if (exp == 0x7ff) { - if (sign) { - common_nan_to_xfp(dfp_to_common_nan(val), result); - } - - PACK_XFP(sign, 0xff, 0, result); - return; - } - if (exp == 0) { - if (frac == 0) { - PACK_XFP(sign, 0, 0, result); - return; - } - normalize_dfp_subnormal(frac, &exp, &frac); - } - - PACK_XFP(sign, - exp + 0x3c00, - 0x8000000000000000ull | (frac << 11), - result); -} - -/* - * Convert an 80-bit floating point value to a 32-bit floating point - * value. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static SFP xfp_to_sfp(XFP *val, RM rounding_mode) -{ - t_bool sign; - int32 exp; - t_uint64 frac; - uint32 dst_frac; - - sign = XFP_SIGN(val); - exp = XFP_EXP(val); - frac = XFP_FRAC(val); - - if (exp == 0x7fff) { - if ((t_uint64)(frac << 1)) { - return common_nan_to_sfp(xfp_to_common_nan(val)); - } - return PACK_SFP(sign, 0xff, 0); - } - - shift_right_64_jamming(frac, 33, &frac); - - dst_frac = (uint32)frac; - - if (exp || frac) { - exp -= 0x3f81; - } - - return round_pack_sfp(sign, exp, dst_frac, rounding_mode); -} - -/* - * Convert an 80-bit floating point value to a 64-bit floating point - * value. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static DFP xfp_to_dfp(XFP *val, RM rounding_mode) -{ - t_bool sign; - int32 exp; - t_uint64 frac; - - sign = XFP_SIGN(val); - exp = XFP_EXP(val); - frac = XFP_FRAC(val); - - sim_debug(TRACE_DBG, &mau_dev, - "[xfp_to_dfp] input=%04x%016llx input_exp=%04x packed_exp=%04x\n", - val->sign_exp, val->frac, (uint16)exp, (uint16)(exp - 0x3c01)); - - if (exp == 0x7fff) { - if ((t_uint64)(frac << 1)) { - return common_nan_to_dfp(xfp_to_common_nan(val)); - } - return PACK_DFP(sign, 0x7ff, 0); - } - - if (exp || frac) { - exp -= 0x3c01; - } - - return round_pack_dfp(sign, exp, frac, val->s, rounding_mode); -} - -/***************************************************************************** - * Comparison Functions - ****************************************************************************/ - -/* - * Returns true if the two 80-bit floating point values are equal. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static uint32 xfp_eq(XFP *a, XFP *b) -{ - if (((XFP_EXP(a) == 0x7fff) && (t_uint64)(XFP_FRAC(a) << 1)) || - ((XFP_EXP(b) == 0x7fff) && (t_uint64)(XFP_FRAC(b) << 1))) { - - /* Check for NAN and raise invalid exception */ - if (XFP_IS_TRAPPING_NAN(a) || XFP_IS_TRAPPING_NAN(b)) { - mau_exc(MAU_ASR_IS, MAU_ASR_IM); - } - - return 0; - } - - return ((a->frac == b->frac) && - ((a->sign_exp == b->sign_exp) || - ((a->frac == 0) && ((uint16)((a->sign_exp|b->sign_exp) << 1) == 0)))); -} - -/* - * Returns true if the 80-bit floating point value 'a' is less than - * the 80-bit floating point value 'b'. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static uint32 xfp_lt(XFP *a, XFP *b) -{ - uint32 a_sign, b_sign; - - if (((XFP_EXP(a) == 0x7fff) && (t_uint64)(XFP_FRAC(a) << 1)) || - ((XFP_EXP(b) == 0x7fff) && (t_uint64)(XFP_FRAC(b) << 1))) { - return 0; - } - - a_sign = XFP_SIGN(a); - b_sign = XFP_SIGN(b); - - if (a_sign != b_sign) { - return(a_sign && - ((((uint16)((a->sign_exp|b->sign_exp) << 1)) | a->frac | b->frac) != 0)); - } - - if (a_sign) { - return (b->sign_exp < a->sign_exp) || ((b->sign_exp == a->sign_exp) && (b->frac < a->frac)); - } else { - return (a->sign_exp < b->sign_exp) || ((a->sign_exp == b->sign_exp) && (a->frac < b->frac)); - } -} - -/***************************************************************************** - * Conversion Functions - ****************************************************************************/ - -/* - * Convert a 32-bit signed integer value to an IEEE-754 extended - * precion (80-bit) floating point value. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -void mau_int_to_xfp(int32 val, XFP *result) -{ - int32 shift_width; - t_bool sign; - uint32 abs_val; - uint16 sign_exp = 0; - t_uint64 frac = 0; - - if (val) { - sign = (val < 0); - abs_val = (uint32)(sign ? -val : val); - shift_width = leading_zeros(abs_val); - sign_exp = (sign << 15) | (0x401e - shift_width); - frac = (t_uint64) (abs_val << shift_width) << 32; - } - - result->sign_exp = sign_exp; - result->frac = frac; - result->s = 0; - - if (sign_exp & 0x8000) { - mau_state.asr |= MAU_ASR_N; - } - - if ((sign_exp & 0x7fff) == 0 && frac == 0) { - mau_state.asr |= MAU_ASR_Z; - } -} - -/* - * Convert a floating point value to a 64-bit integer. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -t_int64 xfp_to_int64(XFP *val, RM rounding_mode) -{ - t_bool sign; - int32 exp, shift_count; - t_uint64 frac, frac_extra; - - sign = XFP_SIGN(val); - exp = XFP_EXP(val); - frac = XFP_FRAC(val); - shift_count = 0x403e - exp; - if (shift_count <= 0) { - if (shift_count) { - mau_exc(MAU_ASR_IS, MAU_ASR_IM); - if (!sign || ((exp == 0x7fff) && (frac != 0x8000000000000000ull))) { - return 0x7fffffffffffffffull; - } - return 0x8000000000000000ull; - } - frac_extra = 0; - } else { - shift_right_extra_64_jamming(frac, 0, shift_count, &frac, &frac_extra); - } - - return round_pack_int64(sign, frac, frac_extra, rounding_mode); -} - -void mau_int64_to_xfp(t_uint64 val, XFP *result) -{ - t_bool sign; - t_uint64 abs; - int8 shift_count; - - if (val == 0) { - PACK_XFP(0, 0, 0, result); - return; - } - - sign = (val & 0x8000000000000000ull) != 0ull; - abs = val & 0x7fffffffffffffffull; - shift_count = leading_zeros_64(abs); - if (shift_count < 64) { - abs = abs << shift_count; - } else { - abs = 0; - } - PACK_XFP(sign, 0x403e - shift_count, abs, result); -} - -/* - * Convert a float value to a decimal value. - */ -void xfp_to_decimal(XFP *a, DEC *d, RM rounding_mode) -{ - t_int64 tmp; - int i; - t_bool sign; - uint16 digits[19] = {0}; - - tmp = xfp_to_int64(a, rounding_mode); - - if (tmp < 0) { - sign = 0xb; - } else { - sign = 0xa; - } - - for (i = 0; i < 19; i++) { - digits[i] = tmp % 10; - tmp /= 10; - } - - d->l = sign; - d->l |= (t_uint64)digits[0] << 4; - d->l |= (t_uint64)digits[1] << 8; - d->l |= (t_uint64)digits[2] << 12; - d->l |= (t_uint64)digits[3] << 16; - d->l |= (t_uint64)digits[4] << 20; - d->l |= (t_uint64)digits[5] << 24; - d->l |= (t_uint64)digits[6] << 28; - d->l |= (t_uint64)digits[7] << 32; - d->l |= (t_uint64)digits[8] << 36; - d->l |= (t_uint64)digits[9] << 40; - d->l |= (t_uint64)digits[10] << 44; - d->l |= (t_uint64)digits[11] << 48; - d->l |= (t_uint64)digits[12] << 52; - d->l |= (t_uint64)digits[13] << 56; - d->l |= (t_uint64)digits[14] << 60; - d->h = (uint32)digits[15]; - d->h |= (uint32)digits[15] << 4; - d->h |= (uint32)digits[15] << 8; - - sim_debug(TRACE_DBG, &mau_dev, - "[xfp_to_decimal] " - "Digits: %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d 0x%x\n", - digits[17], digits[16], digits[15], digits[14], digits[13], digits[12], - digits[11], digits[10], digits[9], digits[8], digits[7], digits[6], - digits[5], digits[4], digits[3], digits[2], digits[1], digits[0], - sign); -} - -/* - * Convert a decimal value to a float value. - */ -void mau_decimal_to_xfp(DEC *d, XFP *a) -{ - int i; - t_bool sign; - uint16 digits[18] = {0}; - t_uint64 multiplier = 1; - t_uint64 tmp; - t_int64 signed_tmp; - - sim_debug(TRACE_DBG, &mau_dev, - "[mau_decimal_to_xfp] DEC input: %08x %08x %08x\n", - d->h, (uint32)(d->l >> 32), (uint32)(d->l)); - - sign = (d->l) & 15; - digits[0] = (d->l >> 4) & 15; - digits[1] = (d->l >> 8) & 15; - digits[2] = (d->l >> 12) & 15; - digits[3] = (d->l >> 16) & 15; - digits[4] = (d->l >> 20) & 15; - digits[5] = (d->l >> 24) & 15; - digits[6] = (d->l >> 28) & 15; - digits[7] = (d->l >> 32) & 15; - digits[8] = (d->l >> 36) & 15; - digits[9] = (d->l >> 40) & 15; - digits[10] = (d->l >> 44) & 15; - digits[11] = (d->l >> 48) & 15; - digits[12] = (d->l >> 52) & 15; - digits[13] = (d->l >> 56) & 15; - digits[14] = (d->l >> 60) & 15; - digits[15] = (d->h) & 15; - digits[16] = (d->h >> 4) & 15; - digits[17] = (d->h >> 8) & 15; - - sim_debug(TRACE_DBG, &mau_dev, - "[mau_decimal_to_xfp] " - "Digits: %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d 0x%x\n", - digits[17], digits[16], digits[15], digits[14], digits[13], digits[12], - digits[11], digits[10], digits[9], digits[8], digits[7], digits[6], - digits[5], digits[4], digits[3], digits[2], digits[1], digits[0], - sign); - - tmp = 0; - - for (i = 0; i < 18; i++) { - tmp += digits[i] * multiplier; - multiplier *= 10; - } - - switch (sign) { - case 0xd: - case 0xb: - /* Negative number */ - signed_tmp = -((t_int64) tmp); - break; - /* TODO: HANDLE NAN AND INFINITY */ - default: - signed_tmp = (t_int64) tmp; - } - - sim_debug(TRACE_DBG, &mau_dev, - "[mau_decimal_to_xfp] tmp val = %lld\n", - signed_tmp); - - mau_int64_to_xfp((t_uint64) signed_tmp, a); - - sim_debug(TRACE_DBG, &mau_dev, - "[mau_decimal_to_xfp] XFP = %04x%016llx\n", - a->sign_exp, a->frac); - -} - -/* - * Convert a floating point value to a 32-bit integer. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -uint32 xfp_to_int(XFP *val, RM rounding_mode) -{ - t_bool sign; - int32 exp, shift_count; - t_uint64 frac; - - sign = XFP_SIGN(val); - exp = XFP_EXP(val); - frac = XFP_FRAC(val); - - if ((exp == 0x7fff) && (t_uint64)(frac << 1)) { - sign = 0; - } - - shift_count = 0x4037 - exp; - - if (shift_count <= 0) { - shift_count = 1; - } - - shift_right_64_jamming(frac, shift_count, &frac); - - return round_pack_int(sign, frac, rounding_mode); -} - -/* - * Round an 80-bit extended precission floating-point value - * to an integer. - * - * Derived from the SoftFloat 2c library (see copyright notice above) - */ -void mau_round_xfp_to_int(XFP *val, XFP *result, RM rounding_mode) -{ - t_bool sign; - int32 exp; - t_uint64 last_bit_mask, round_bits_mask; - - exp = XFP_EXP(val); - - if (0x403e <= exp) { - if ((exp == 0x7fff) && (t_uint64)(XFP_FRAC(val) << 1)) { - propagate_xfp_nan(val, val, result); - return; - } - result->sign_exp = val->sign_exp; - result->frac = val->frac; - return; - } - if (exp < 0x3ff) { - if ((exp == 0) && ((t_uint64)(XFP_FRAC(val) << 1) == 0)) { - result->sign_exp = val->sign_exp; - result->frac = val->frac; - return; - } - mau_exc(MAU_ASR_PS, MAU_ASR_PM); - sign = XFP_SIGN(val); - switch (rounding_mode) { - case ROUND_NEAREST: - if (exp == 0x3ffe && (t_uint64)(XFP_FRAC(val) << 1)) { - PACK_XFP(sign, 0x3fff, 0x8000000000000000ull, result); - return; - } - break; - case ROUND_MINUS_INF: - if (sign) { - PACK_XFP(1, 0x3fff, 0x8000000000000000ull, result); - } else { - PACK_XFP(0, 0, 0, result); - } - return; - case ROUND_PLUS_INF: - if (sign) { - PACK_XFP(1, 0, 0, result); - } else { - PACK_XFP(0, 0x3fff, 0x8000000000000000ull, result); - } - return; - default: - /* Do nothing */ - break; - } - PACK_XFP(sign, 0, 0, result); - return; - } - - last_bit_mask = 1; - last_bit_mask <<= 0x403e - exp; - round_bits_mask = last_bit_mask - 1; - - result->sign_exp = val->sign_exp; - result->frac = val->frac; - - if (rounding_mode == ROUND_NEAREST) { - result->frac += last_bit_mask >> 1; - if ((result->frac & round_bits_mask) == 0) { - result->frac &= ~last_bit_mask; - } - } else if (rounding_mode != ROUND_ZERO) { - if (XFP_SIGN(result) ^ (rounding_mode == ROUND_PLUS_INF)) { - result->frac += round_bits_mask; - } - } - - result->frac &= ~round_bits_mask; - if (result->frac == 0) { - ++result->sign_exp; - result->frac = 0x8000000000000000ull; - } - - if (result->frac != val->frac) { - mau_exc(MAU_ASR_PS, MAU_ASR_PM); - } -} - -/***************************************************************************** - * Math Functions - ****************************************************************************/ - -/* - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static void xfp_add_fracs(XFP *a, XFP *b, t_bool sign, XFP *result, RM rounding_mode) -{ - int32 a_exp, b_exp, r_exp; - t_uint64 a_frac, b_frac, r_frac_0, r_frac_1; - int32 exp_diff; - - sim_debug(TRACE_DBG, &mau_dev, - "[ADD_FRACS] a=%04x%016llx b=%04x%016llx\n", - a->sign_exp, a->frac, - b->sign_exp, b->frac); - - a_exp = XFP_EXP(a); - a_frac = XFP_FRAC(a); - b_exp = XFP_EXP(b); - b_frac = XFP_FRAC(b); - - exp_diff = a_exp - b_exp; - if (0 < exp_diff) { - if (a_exp == 0x7fff) { - if ((t_uint64) (a_frac << 1)) { - propagate_xfp_nan(a, b, result); - return; - } - result->sign_exp = a->sign_exp; - result->frac = a->frac; - return; - } - if (b_exp == 0) { - --exp_diff; - } - shift_right_extra_64_jamming(b_frac, 0, exp_diff, &b_frac, &r_frac_1); - r_exp = a_exp; - } else if (exp_diff < 0) { - if (b_exp == 0x7fff) { - if ((t_uint64) (b_frac << 1)) { - propagate_xfp_nan(a, b, result); - return; - } - PACK_XFP(sign, 0x7fff, 0x8000000000000000ull, result); - return; - } - if (a_exp == 0) { - ++exp_diff; - } - - shift_right_extra_64_jamming(a_frac, 0, -exp_diff, &a_frac, &r_frac_1); - r_exp = b_exp; - } else { - if (a_exp == 0x7fff) { - if ((t_uint64)((a_frac | b_frac) << 1)) { - propagate_xfp_nan(a, b, result); - return; - } - result->sign_exp = a->sign_exp; - result->frac = a->frac; - return; - } - r_frac_1 = 0; - r_frac_0 = a_frac + b_frac; - if (a_exp == 0) { - normalize_xfp_subnormal(r_frac_0, &r_exp, &r_frac_0); - - round_pack_xfp(sign, r_exp, r_frac_0, r_frac_1, rounding_mode, result); - return; - } - r_exp = a_exp; - shift_right_extra_64_jamming(r_frac_0, r_frac_1, 1, &r_frac_0, &r_frac_1); - r_frac_0 |= 0x8000000000000000ull; - ++r_exp; - round_pack_xfp(sign, r_exp, r_frac_0, r_frac_1, rounding_mode, result); - return; - } - r_frac_0 = a_frac + b_frac; - if (((t_int64) r_frac_0) < 0) { - round_pack_xfp(sign, r_exp, r_frac_0, r_frac_1, rounding_mode, result); - return; - } - shift_right_extra_64_jamming(r_frac_0, r_frac_1, 1, &r_frac_0, &r_frac_1); - r_frac_0 |= 0x8000000000000000ull; - ++r_exp; - round_pack_xfp(sign, r_exp, r_frac_0, r_frac_1, rounding_mode, result); - return; -} - -/* - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static void xfp_sub_fracs(XFP *a, XFP *b, t_bool sign, XFP *result, RM rounding_mode) -{ - int32 a_exp, b_exp, r_exp; - t_uint64 a_frac, b_frac, r_frac_0, r_frac_1; - int32 exp_diff; - - a_exp = XFP_EXP(a); - a_frac = XFP_FRAC(a); - b_exp = XFP_EXP(b); - b_frac = XFP_FRAC(b); - exp_diff = a_exp - b_exp; - - if (0 < exp_diff) { - /* aExpBigger */ - if (a_exp == 0x7fff) { - if ((t_uint64)(a_frac << 1)) { - propagate_xfp_nan(a, b, result); - return; - } - result->sign_exp = a->sign_exp; - result->frac = a->frac; - return; - } - if (b_exp == 0) { - --exp_diff; - } - shift_right_128_jamming(b_frac, 0, exp_diff, &b_frac, &r_frac_1); - /* aBigger */ - sub_128(a_frac, 0, b_frac, r_frac_1, &r_frac_0, &r_frac_1); - r_exp = a_exp; - /* normalizeRoundAndPack */ - normalize_round_pack_xfp(sign, r_exp, r_frac_0, r_frac_1, rounding_mode, result); - return; - } - if (exp_diff < 0) { - /* bExpBigger */ - if (b_exp == 0x7fff) { - if ((t_uint64)(b_frac << 1)) { - propagate_xfp_nan(a, b, result); - return; - } - PACK_XFP(sign ? 0 : 1, 0x7fff, 0x8000000000000000ull, result); - return; - } - if (a_exp == 0) { - ++exp_diff; - } - shift_right_128_jamming(a_frac, 0, -exp_diff, &a_frac, &r_frac_1); - /* bBigger */ - sub_128(b_frac, 0, a_frac, r_frac_1, &r_frac_0, &r_frac_1); - r_exp = b_exp; - sign = sign ? 0 : 1; - /* normalizeRoundAndPack */ - normalize_round_pack_xfp(sign, r_exp, - r_frac_0, r_frac_1, - rounding_mode, result); - return; - } - if (a_exp == 0x7fff) { - if ((t_uint64)((a_frac | b_frac) << 1)) { - propagate_xfp_nan(a, b, result); - return; - } - mau_exc(MAU_ASR_IS, MAU_ASR_IM); /* Invalid */ - result->sign_exp = DEFAULT_XFP_NAN_SIGN_EXP; - result->frac = DEFAULT_XFP_NAN_FRAC; - return; - } - if (a_exp == 0) { - a_exp = 1; - b_exp = 1; - } - r_frac_1 = 0; - if (b_frac < a_frac) { - /* aBigger */ - sub_128(a_frac, 0, b_frac, r_frac_1, &r_frac_0, &r_frac_1); - r_exp = a_exp; - /* normalizeRoundAndPack */ - normalize_round_pack_xfp(sign, r_exp, - r_frac_0, r_frac_1, - rounding_mode, result); - return; - } - if (a_frac < b_frac) { - /* bBigger */ - sub_128(b_frac, 0, a_frac, r_frac_1, &r_frac_0, &r_frac_1); - r_exp = b_exp; - sign ^= 1; - - /* normalizeRoundAndPack */ - normalize_round_pack_xfp(sign, r_exp, - r_frac_0, r_frac_1, - rounding_mode, result); - return; - } - - PACK_XFP(rounding_mode == ROUND_MINUS_INF, 0, 0, result); -} - -/************************************************************************* - * - * MAU-specific functions - * - *************************************************************************/ - -/* - * Set condition flags based on comparison of the two values A and B. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static void xfp_cmp(XFP *a, XFP *b) -{ - mau_state.asr &= ~(MAU_ASR_N|MAU_ASR_Z|MAU_ASR_UO); - - /* Page 5-9: - * - * "An invalid operation exception condition exists if either or - * both source operands are trapping NaNs. If the exception is - * masked then the UO flag would be set. However, if this - * exception is enabled, and, if Op1 is a trapping NaN, it is - * converted to double-extended precision and stored in DR. Else, - * Op2 (converted to double-extended precision, if necessary) is - * stored in DR." - */ - - if (XFP_IS_NAN(a) || XFP_IS_NAN(b)) { - if ((mau_state.asr & MAU_ASR_IM) == 0) { - mau_state.asr |= MAU_ASR_UO; - } else if (XFP_IS_NAN(a)) { - mau_state.dr.sign_exp = a->sign_exp; - mau_state.dr.frac = a->frac; - } else { - mau_state.dr.sign_exp = b->sign_exp; - mau_state.dr.frac = b->frac; - } - return; - } - - if (xfp_lt(a, b)) { - mau_state.asr |= MAU_ASR_N; - } - - if (xfp_eq(a, b)) { - mau_state.asr |= MAU_ASR_Z; - } -} - -static void xfp_cmpe(XFP *a, XFP *b) -{ - mau_state.asr &= ~(MAU_ASR_N|MAU_ASR_Z|MAU_ASR_UO); - - /* Page 5-10: - * - * "When two unordered values are compared, then, in additon to - * the response specified below, the invalid operation exception - * sticky flag (ASR = 1) is set and the trap invoked if the - * invalid operation exceptionis enabled."" - */ - - if ((XFP_IS_NAN(a) || XFP_IS_NAN(b)) && (mau_state.asr & MAU_ASR_IM)) { - mau_state.asr |= MAU_ASR_UO; - return; - } - - if (xfp_lt(a, b)) { - mau_state.asr |= MAU_ASR_N; - } - - if (xfp_eq(a, b)) { - mau_state.asr |= MAU_ASR_Z; - } -} - -static void xfp_cmps(XFP *a, XFP *b) -{ - mau_state.asr &= ~(MAU_ASR_N|MAU_ASR_Z|MAU_ASR_UO); - - if (XFP_IS_NAN(a) || XFP_IS_NAN(b)) { - if ((mau_state.asr & MAU_ASR_IM) == 0) { - mau_state.asr |= MAU_ASR_UO; - } else if (XFP_IS_NAN(a)) { - mau_state.dr.sign_exp = a->sign_exp; - mau_state.dr.frac = a->frac; - } else { - mau_state.dr.sign_exp = b->sign_exp; - mau_state.dr.frac = b->frac; - } - return; - } - - if (xfp_lt(a, b)) { - mau_state.asr |= MAU_ASR_Z; - } else if (xfp_eq(a, b)) { - mau_state.asr |= MAU_ASR_N; - } -} - -static void xfp_cmpes(XFP *a, XFP *b) -{ - mau_state.asr &= ~(MAU_ASR_N|MAU_ASR_Z|MAU_ASR_UO); - - if ((XFP_IS_NAN(a) || XFP_IS_NAN(b)) && (mau_state.asr & MAU_ASR_IM)) { - mau_state.asr |= MAU_ASR_UO; - return; - } - - if (xfp_lt(a, b)) { - mau_state.asr |= MAU_ASR_Z; - } - - if (xfp_eq(a, b)) { - mau_state.asr |= MAU_ASR_N; - } -} - -static void xfp_add(XFP *a, XFP *b, XFP *result, RM rounding_mode) -{ - uint32 a_sign, b_sign; - - a_sign = XFP_SIGN(a); - b_sign = XFP_SIGN(b); - - if (a_sign == b_sign) { - xfp_add_fracs(a, b, a_sign, result, rounding_mode); - } else { - xfp_sub_fracs(a, b, a_sign, result, rounding_mode); - } -} - -static void xfp_sub(XFP *a, XFP *b, XFP *result, RM rounding_mode) -{ - uint32 a_sign, b_sign; - - a_sign = XFP_SIGN(a); - b_sign = XFP_SIGN(b); - - if (a_sign == b_sign) { - xfp_sub_fracs(a, b, a_sign, result, rounding_mode); - } else { - xfp_add_fracs(a, b, a_sign, result, rounding_mode); - } -} - -/* - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static void xfp_mul(XFP *a, XFP *b, XFP *result, RM rounding_mode) -{ - uint32 a_sign, b_sign, r_sign; - int32 a_exp, b_exp, r_exp; - t_uint64 a_frac, b_frac, r_frac_0, r_frac_1; - - sim_debug(TRACE_DBG, &mau_dev, - "[MUL] op1=%04x%016llx op2=%04x%016llx\n", - a->sign_exp, a->frac, - b->sign_exp, b->frac); - - a_sign = XFP_SIGN(a); - a_exp = XFP_EXP(a); - a_frac = XFP_FRAC(a); - b_sign = XFP_SIGN(b); - b_exp = XFP_EXP(b); - b_frac = XFP_FRAC(b); - - r_sign = a_sign ^ b_sign; - - if (a_exp == 0x7fff) { - if ((t_uint64)(a_frac << 1) || ((b_exp == 0x7fff) && (t_uint64)(b_frac << 1))) { - propagate_xfp_nan(a, b, result); - return; - } - if ((b_exp | b_frac) == 0) { - /* invalid */ - mau_exc(MAU_ASR_IS, MAU_ASR_IM); - result->sign_exp = DEFAULT_XFP_NAN_SIGN_EXP; - result->frac = DEFAULT_XFP_NAN_FRAC; - return; - } - PACK_XFP(r_sign, 0x7fff, 0x8000000000000000ull, result); - return; - } - - if (b_exp == 0x7fff) { - if ((t_uint64)(b_frac << 1)) { - propagate_xfp_nan(a, b, result); - return; - } - if ((a_exp | a_frac) == 0) { - /* invalid */ - mau_exc(MAU_ASR_IS, MAU_ASR_IM); - result->sign_exp = DEFAULT_XFP_NAN_SIGN_EXP; - result->frac = DEFAULT_XFP_NAN_FRAC; - return; - } - PACK_XFP(r_sign, 0x7fff, 0x8000000000000000ull, result); - return; - } - - if (a_exp == 0) { - if (a_frac == 0) { - PACK_XFP(r_sign, 0, 0, result); - return; - } - normalize_xfp_subnormal(a_frac, &a_exp, &a_frac); - } - - if (b_exp == 0) { - if (b_frac == 0) { - PACK_XFP(r_sign, 0, 0, result); - return; - } - normalize_xfp_subnormal(b_frac, &b_exp, &b_frac); - } - - r_exp = a_exp + b_exp - 0x3ffe; - mul_64_to_128(a_frac, b_frac, &r_frac_0, &r_frac_1); - if (0 < (t_int64)r_frac_0) { - short_shift_left_128(r_frac_0, r_frac_1, 1, - &r_frac_0, &r_frac_1); - --r_exp; - } - - round_pack_xfp(r_sign, r_exp, r_frac_0, - r_frac_1, rounding_mode, result); -} - -/* - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static void xfp_div(XFP *a, XFP *b, XFP *result, RM rounding_mode) -{ - t_bool a_sign, b_sign, r_sign; - int32 a_exp, b_exp, r_exp; - t_uint64 a_frac, b_frac, r_frac0, r_frac1; - t_uint64 rem0, rem1, rem2, term0, term1, term2; - - sim_debug(TRACE_DBG, &mau_dev, - "[DIV] op1=%04x%016llx op2=%04x%016llx\n", - b->sign_exp, b->frac, a->sign_exp, a->frac); - - a_sign = XFP_SIGN(a); - a_exp = XFP_EXP(a); - a_frac = XFP_FRAC(a); - - b_sign = XFP_SIGN(b); - b_exp = XFP_EXP(b); - b_frac = XFP_FRAC(b); - - r_sign = a_sign ^ b_sign; - - if (a_exp == 0x7fff) { - if ((t_uint64)(a_frac << 1)) { - propagate_xfp_nan(a, b, result); - return; - } - - if (b_exp == 0x7fff) { - if ((t_uint64)(b_frac << 1)) { - propagate_xfp_nan(a, b, result); - return; - } - /* Invalid */ - mau_exc(MAU_ASR_IS, MAU_ASR_IM); - result->sign_exp = DEFAULT_XFP_NAN_SIGN_EXP; - result->frac = DEFAULT_XFP_NAN_FRAC; - return; - } - - PACK_XFP(r_sign, 0x7fff, 0x8000000000000000ull, result); - return; - } - - if (b_exp == 0x7fff) { - if ((t_uint64) (b_frac << 1)) { - propagate_xfp_nan(a, b, result); - return; - } - - PACK_XFP(r_sign, 0, 0, result); - return; - } - - if (b_exp == 0) { - if (b_frac == 0) { - if ((a_exp | b_frac) == 0) { - /* Invalid */ - mau_exc(MAU_ASR_IS, MAU_ASR_IM); - result->sign_exp = DEFAULT_XFP_NAN_SIGN_EXP; - result->frac = DEFAULT_XFP_NAN_FRAC; - return; - } - /* Divide by zero - SPECIAL CASE 4 */ - sim_debug(TRACE_DBG, &mau_dev, - "[DIV] Divide by zero detected.\n"); - mau_case_div_zero(a, b, result); - return; - } - normalize_xfp_subnormal(b_frac, &b_exp, &b_frac); - } - - if (a_exp == 0) { - if (a_frac == 0) { - PACK_XFP(r_sign, 0, 0, result); - return; - } - normalize_xfp_subnormal(a_frac, &a_exp, &a_frac); - } - - r_exp = a_exp - b_exp + 0x3ffe; - rem1 = 0; - if (b_frac <= a_frac) { - shift_right_128(a_frac, 0, 1, &a_frac, &rem1); - ++r_exp; - } - - r_frac0 = estimate_div_128_to_64(a_frac, rem1, b_frac); - mul_64_to_128(b_frac, r_frac0, &term0, &term1); - sub_128(a_frac, rem1, term0, term1, &rem0, &rem1); - - while ((t_int64) rem0 < 0) { - --r_frac0; - add_128(rem0, rem1, 0, b_frac, &rem0, &rem1); - } - - r_frac1 = estimate_div_128_to_64(rem1, 0, b_frac); - if ((t_uint64)(r_frac1 << 1) <= 8) { - mul_64_to_128(b_frac, r_frac1, &term1, &term2); - sub_128(rem1, 0, term1, term2, &rem1, &rem2); - while ((t_int64) rem1 < 0) { - --r_frac1; - add_128(rem1, rem2, 0, b_frac, &rem1, &rem2); - } - r_frac1 |= ((rem1 | rem2) != 0); - } - - round_pack_xfp(r_sign, r_exp, r_frac0, r_frac1, rounding_mode, result); -} - -/* - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static void xfp_sqrt(XFP *a, XFP *result, RM rounding_mode) -{ - XFP zero = {0, 0, 0}; - t_bool a_sign; - int32 a_exp, norm_exp, r_exp; - uint32 a_frac_32, sqrt_recip_32, r_frac_32; - t_uint64 a_frac, norm_frac, q, x64, z_frac, z_frac_extra; - t_mau_128 nan_128, rem, y, term; - - sim_debug(TRACE_DBG, &mau_dev, - "[SQRT] op1=%04x%016llx\n", - a->sign_exp, a->frac); - - a_sign = XFP_SIGN(a); - a_exp = XFP_EXP(a); - a_frac = XFP_FRAC(a); - - if (a_exp == 0x7fff) { - if ( a_frac & 0x7fffffffffffffffull ) { - propagate_xfp_nan_128(a, &zero, &nan_128); - result->sign_exp = (uint32) nan_128.high; - result->frac = nan_128.low; - return; - } - if ( ! a_sign ) { - result->sign_exp = a->sign_exp; - result->frac = a->frac; - } - /* Invalid */ - mau_exc(MAU_ASR_IS, MAU_ASR_IM); - result->sign_exp = DEFAULT_XFP_NAN_SIGN_EXP; - result->frac = DEFAULT_XFP_NAN_FRAC; - return; - } - - if (a_sign) { - if (!a_frac) { - PACK_XFP(a_sign, 0, 0, result); - return; - } - mau_exc(MAU_ASR_IS, MAU_ASR_IM); - result->sign_exp = DEFAULT_XFP_NAN_SIGN_EXP; - result->frac = DEFAULT_XFP_NAN_FRAC; - return; - } - - if (!a_exp) { - a_exp = 1; - } - - if (!(a_frac & 0x8000000000000000ull)) { - if (!a_frac) { - PACK_XFP(a_sign, 0, 0, result); - return; - } - normalize_xfp_subnormal(a->frac, &norm_exp, &norm_frac); - a_exp += norm_exp; - a_frac = norm_frac; - } - - /* - * r_frac_32 is guaranteed to be a lower bound on the square root of - * a_frac_32, which makes r_frac_32 also a lower bound on the square - * root of `a_frac'. - */ - r_exp = ((a_exp - 0x3FFF) >> 1) + 0x3FFF; - a_exp &= 1; - a_frac_32 = a_frac >> 32; - sqrt_recip_32 = approx_recip_sqrt_32(a_exp, a_frac_32); - r_frac_32 = ((t_uint64) a_frac_32 * sqrt_recip_32) >> 32; - - if (a_exp) { - r_frac_32 >>= 1; - short_shift_left_128(0, a_frac, 61, &rem.high, &rem.low); - } else { - short_shift_left_128(0, a_frac, 62, &rem.high, &rem.low); - } - - rem.high -= (t_uint64) r_frac_32 * r_frac_32; - - q = ((uint32) (rem.high >> 2) * (t_uint64) sqrt_recip_32) >> 32; - x64 = (t_uint64) r_frac_32 << 32; - z_frac = x64 + (q<<3); - short_shift_left_128(rem.high, rem.low, 29, &y.high, &y.low); - - /* Repeating this loop is a rare occurrence. */ - while(1) { - mul_64_by_shifted_32_to_128(x64 + z_frac, (uint32) q, &term); - sub_128(y.high, y.low, term.high, term.low, &rem.high, &rem.low); - if (!(rem.high & 0x8000000000000000ull)) { - break; - } - --q; - z_frac -= 1<<3; - } - - q = (((rem.high>>2) * sqrt_recip_32)>>32) + 2; - x64 = z_frac; - z_frac = (z_frac<<1) + (q>>25); - z_frac_extra = (t_uint64) (q<<39); - - if ( (q & 0xffffff) <= 2 ) { - q &= ~(t_uint64) 0xffff; - z_frac_extra = (t_uint64) (q<<39); - mul_64_by_shifted_32_to_128(x64 + (q >> 27), (uint32) q, &term); - x64 = (uint32) (q<<5) * (t_uint64) (uint32) q; - add_128(term.high, term.low, 0, x64, &term.high, &term.low); - short_shift_left_128(rem.high, rem.low, 28, &rem.high, &rem.low); - sub_128(rem.high, rem.low, term.high, term.low, &rem.high, &rem.low); - if (rem.high & 0x8000000000000000ull) { - if (!z_frac_extra ) { - --z_frac; - } - --z_frac_extra; - } else { - if (rem.high | rem.low) { - z_frac_extra |= 1; - } - } - } - - round_pack_xfp(0, r_exp, z_frac, z_frac_extra, rounding_mode,result); - return; -} - -static void xfp_remainder(XFP *a, XFP *b, XFP *result, RM rounding_mode) -{ - uint32 a_sign, r_sign; - int32 a_exp, b_exp, exp_diff; - t_uint64 a_frac_0, a_frac_1, b_frac; - t_uint64 q, term_0, term_1, alt_a_frac_0, alt_a_frac_1; - - a_sign = XFP_SIGN(a); - a_exp = XFP_EXP(a); - a_frac_0 = XFP_FRAC(a); - b_exp = XFP_EXP(b); - b_frac = XFP_FRAC(b); - - if (a_exp == 0x7fff) { - if ((t_uint64)(a_frac_0 << 1) || - ((b_exp == 0x7fff) && (t_uint64)(b_frac << 1))) { - propagate_xfp_nan(a, b, result); - return; - } - /* invalid */ - mau_exc(MAU_ASR_IS, MAU_ASR_IM); - result->sign_exp = DEFAULT_XFP_NAN_SIGN_EXP; - result->frac = DEFAULT_XFP_NAN_FRAC; - return; - } - - if (b_exp == 0x7fff) { - if ((t_uint64)(b_frac << 1)) { - propagate_xfp_nan(a, b, result); - } - result->sign_exp = a->sign_exp; - result->frac = a->frac; - return; - } - - if (b_exp == 0) { - if (b_frac == 0) { - /* invalid */ - mau_exc(MAU_ASR_IS, MAU_ASR_IM); - result->sign_exp = DEFAULT_XFP_NAN_SIGN_EXP; - result->frac = DEFAULT_XFP_NAN_FRAC; - return; - } - normalize_xfp_subnormal(b_frac, &b_exp, &b_frac); - } - - if (a_exp == 0) { - if ((t_uint64)(a_frac_0 << 1) == 0) { - result->sign_exp = a->sign_exp; - result->frac = a->frac; - return; - } - normalize_xfp_subnormal(a_frac_0, &a_exp, &a_frac_0); - } - - b_frac |= 0x8000000000000000ull; - r_sign = a_sign; - exp_diff = a_exp - b_exp; - a_frac_1 = 0; - if (exp_diff < 0) { - if (exp_diff < -1) { - result->sign_exp = a->sign_exp; - result->frac = a->frac; - return; - } - shift_right_128(a_frac_0, 0, 1, &a_frac_0, &a_frac_1); - exp_diff = 0; - } - - q = (b_frac <= a_frac_0); - - if (q) { - a_frac_0 -= b_frac; - } - - exp_diff -= 64; - - while (0 < exp_diff) { - q = estimate_div_128_to_64(a_frac_0, a_frac_1, b_frac); - q = (2 < q) ? q - 2 : 0; - mul_64_to_128(b_frac, q, &term_0, &term_1); - sub_128(a_frac_0, a_frac_1, term_0, term_1, &a_frac_0, &a_frac_1); - short_shift_left_128(a_frac_0, a_frac_1, 62, &a_frac_0, &a_frac_1); - exp_diff -= 62; - } - - exp_diff += 64; - - if (0 < exp_diff) { - q = estimate_div_128_to_64(a_frac_0, a_frac_1, b_frac); - q = (2 < q) ? q - 2 : 0; - q >>= 64 - exp_diff; - mul_64_to_128(b_frac, q << (64 - exp_diff), &term_0, &term_1); - sub_128(a_frac_0, a_frac_1, term_0, term_1, &a_frac_0, &a_frac_1); - short_shift_left_128(0, b_frac, 64 - exp_diff, &term_0, &term_1); - while (le_128(term_0, term_1, a_frac_0, a_frac_1)) { - ++q; - sub_128(a_frac_0, a_frac_1, term_0, term_1, &a_frac_0, &a_frac_1); - } - } else { - term_0 = b_frac; - term_1 = 0; - } - - sub_128(term_0, term_1, a_frac_0, a_frac_1, &alt_a_frac_0, &alt_a_frac_1); - - if (lt_128(alt_a_frac_0, alt_a_frac_1, a_frac_0, a_frac_1) || - (eq_128(alt_a_frac_0, alt_a_frac_1, a_frac_0, a_frac_1) && - (q & 1))) { - a_frac_0 = alt_a_frac_0; - a_frac_1 = alt_a_frac_1; - r_sign = r_sign ? 0 : 1; - } - - normalize_round_pack_xfp(r_sign, b_exp + exp_diff, - a_frac_0, a_frac_1, - rounding_mode, result); -} - -/* - * Load an extended precision 80-bit IEE-754 floating point value from - * memory or register, based on the operand's specification. - */ -static void load_src_op(uint8 op, XFP *xfp) -{ - DFP dfp; - SFP sfp; - - switch (op) { - case M_OP_F0: - xfp->sign_exp = mau_state.f0.sign_exp; - xfp->frac = mau_state.f0.frac; - break; - case M_OP_F1: - xfp->sign_exp = mau_state.f1.sign_exp; - xfp->frac = mau_state.f1.frac; - break; - case M_OP_F2: - xfp->sign_exp = mau_state.f2.sign_exp; - xfp->frac = mau_state.f2.frac; - break; - case M_OP_F3: - xfp->sign_exp = mau_state.f3.sign_exp; - xfp->frac = mau_state.f3.frac; - break; - case M_OP_MEM_SINGLE: - sfp = read_w(mau_state.src, ACC_AF, BUS_PER); - sfp_to_xfp(sfp, xfp); - break; - case M_OP_MEM_DOUBLE: - dfp = (t_uint64) read_w(mau_state.src + 4, ACC_AF, BUS_PER); - dfp |= ((t_uint64) read_w(mau_state.src, ACC_AF, BUS_PER)) << 32; - sim_debug(TRACE_DBG, &mau_dev, - "[load_src_op][DOUBLE] Loaded %016llx\n", - dfp); - dfp_to_xfp(dfp, xfp); - sim_debug(TRACE_DBG, &mau_dev, - "[load_src_op][DOUBLE] Expanded To %04x%016llx\n", - xfp->sign_exp, xfp->frac); - break; - case M_OP_MEM_TRIPLE: - xfp->frac = (t_uint64) read_w(mau_state.src + 8, ACC_AF, BUS_PER); - xfp->frac |= ((t_uint64) read_w(mau_state.src + 4, ACC_AF, BUS_PER)) << 32; - xfp->sign_exp = (uint32) read_w(mau_state.src, ACC_AF, BUS_PER); - break; - default: - break; - } -} - -/* - * Load OP1 as a DEC value. - */ -static void load_op1_decimal(DEC *d) -{ - uint32 low, mid, high; - - switch (mau_state.op1) { - case M_OP_MEM_TRIPLE: - low = read_w(mau_state.src + 8, ACC_AF, BUS_PER); - mid = read_w(mau_state.src + 4, ACC_AF, BUS_PER); - high = read_w(mau_state.src, ACC_AF, BUS_PER); - d->l = low; - d->l |= ((t_uint64) mid << 32); - d->h = high; - break; - default: - /* Invalid */ - mau_exc(MAU_ASR_IS, MAU_ASR_IM); - break; - } -} - -static void store_op3_int(uint32 val) -{ - switch(mau_state.op3) { - case M_OP3_F0_SINGLE: - mau_state.f0.sign_exp = 0; - mau_state.f0.frac = (t_uint64)val; - break; - case M_OP3_F1_SINGLE: - mau_state.f1.sign_exp = 0; - mau_state.f1.frac = (t_uint64)val; - break; - case M_OP3_F2_SINGLE: - mau_state.f2.sign_exp = 0; - mau_state.f2.frac = (t_uint64)val; - break; - case M_OP3_F3_SINGLE: - mau_state.f3.sign_exp = 0; - mau_state.f3.frac = (t_uint64)val; - break; - case M_OP3_MEM_SINGLE: - write_w(mau_state.dst, val, BUS_PER); - break; - default: - /* Indeterminate output, unsupported */ - break; - } - - mau_state.dr.sign_exp = 0; - mau_state.dr.frac = (t_uint64)val; -} - -static void store_op3_decimal(DEC *d) -{ - - switch(mau_state.op3) { - case M_OP3_MEM_TRIPLE: - write_w(mau_state.dst, d->h, BUS_PER); - write_w(mau_state.dst + 4, (uint32)((t_uint64)d->l >> 32), BUS_PER); - write_w(mau_state.dst + 8, (uint32)d->l, BUS_PER); - break; - default: - /* Unsupported */ - return; - } - - mau_state.dr.sign_exp = d->h; - mau_state.dr.frac = ((t_uint64)d->l >> 32) | (t_uint64)d->l; -} - -static void store_op3_reg(XFP *xfp, XFP *reg) -{ - DFP dfp; - SFP sfp; - XFP xfp_r; - - if (mau_state.ntnan) { - reg->sign_exp = GEN_NONTRAPPING_NAN.sign_exp; - reg->frac = GEN_NONTRAPPING_NAN.frac; - } else { - switch(mau_state.op3) { - case M_OP3_F0_SINGLE: - case M_OP3_F1_SINGLE: - case M_OP3_F2_SINGLE: - case M_OP3_F3_SINGLE: - sfp = xfp_to_sfp(xfp, MAU_RM); - sfp_to_xfp(sfp, &xfp_r); - reg->sign_exp = xfp_r.sign_exp; - reg->frac = xfp_r.frac; - reg->s = xfp_r.s; - break; - case M_OP3_F0_DOUBLE: - case M_OP3_F1_DOUBLE: - case M_OP3_F2_DOUBLE: - case M_OP3_F3_DOUBLE: - dfp = xfp_to_dfp(xfp, MAU_RM); - dfp_to_xfp(dfp, &xfp_r); - reg->sign_exp = xfp_r.sign_exp; - reg->frac = xfp_r.frac; - reg->s = xfp_r.s; - break; - case M_OP3_F0_TRIPLE: - case M_OP3_F1_TRIPLE: - case M_OP3_F2_TRIPLE: - case M_OP3_F3_TRIPLE: - reg->sign_exp = xfp->sign_exp; - reg->frac = xfp->frac; - reg->s = xfp->s; - break; - } - } - if (set_nz()) { - if (XFP_SIGN(xfp)) { - mau_state.asr |= MAU_ASR_N; - } - if (XFP_EXP(xfp) == 0 && XFP_FRAC(xfp) == 0) { - mau_state.asr |= MAU_ASR_Z; - } - } -} - -static void store_op3(XFP *xfp) -{ - DFP dfp; - SFP sfp; - t_bool store_dr = FALSE; - - sim_debug(TRACE_DBG, &mau_dev, - "[store_op3] op3=%04x%016llx\n", - xfp->sign_exp, - xfp->frac); - - switch (mau_state.opcode) { - case M_ADD: - case M_SUB: - case M_MUL: - case M_DIV: - store_dr = TRUE; - break; - default: - break; - } - - switch (mau_state.op3) { - case M_OP3_F0_SINGLE: - case M_OP3_F0_DOUBLE: - case M_OP3_F0_TRIPLE: - store_op3_reg(xfp, &mau_state.f0); - break; - case M_OP3_F1_SINGLE: - case M_OP3_F1_DOUBLE: - case M_OP3_F1_TRIPLE: - store_op3_reg(xfp, &mau_state.f1); - break; - case M_OP3_F2_SINGLE: - case M_OP3_F2_DOUBLE: - case M_OP3_F2_TRIPLE: - store_op3_reg(xfp, &mau_state.f2); - break; - case M_OP3_F3_SINGLE: - case M_OP3_F3_DOUBLE: - case M_OP3_F3_TRIPLE: - store_op3_reg(xfp, &mau_state.f3); - break; - case M_OP3_MEM_SINGLE: - if (mau_state.ntnan) { - sfp = xfp_to_sfp(&GEN_NONTRAPPING_NAN, MAU_RM); - } else { - sfp = xfp_to_sfp(xfp, MAU_RM); - } - if (set_nz()) { - if (SFP_SIGN(sfp)) { - mau_state.asr |= MAU_ASR_N; - } - if (SFP_EXP(sfp) == 0 && SFP_FRAC(sfp) == 0) { - mau_state.asr |= MAU_ASR_Z; - } - } - write_w(mau_state.dst, (uint32)sfp, BUS_PER); - break; - case M_OP3_MEM_DOUBLE: - if (mau_state.ntnan) { - dfp = xfp_to_dfp(&GEN_NONTRAPPING_NAN, MAU_RM); - } else { - dfp = xfp_to_dfp(xfp, MAU_RM); - } - if (store_dr) { - mau_state.dr.sign_exp = ((uint16)(DFP_SIGN(dfp)) << 15) | (uint16)(DFP_EXP(dfp)); - mau_state.dr.frac = (t_uint64)(DFP_FRAC(dfp)); - if (DFP_EXP(dfp)) { - /* If the number is normalized, add the implicit - normalized bit 52 */ - mau_state.dr.frac |= ((t_uint64)1 << 52); - } - } - if (set_nz()) { - if (DFP_SIGN(dfp)) { - mau_state.asr |= MAU_ASR_N; - } - if (DFP_EXP(dfp) == 0 && DFP_FRAC(dfp) == 0) { - mau_state.asr |= MAU_ASR_Z; - } - } - write_w(mau_state.dst, (uint32)(dfp >> 32), BUS_PER); - write_w(mau_state.dst + 4, (uint32)(dfp), BUS_PER); - break; - case M_OP3_MEM_TRIPLE: - if (mau_state.ntnan) { - write_w(mau_state.dst, (uint32)(GEN_NONTRAPPING_NAN.sign_exp), BUS_PER); - write_w(mau_state.dst + 4, (uint32)(GEN_NONTRAPPING_NAN.frac >> 32), BUS_PER); - write_w(mau_state.dst + 8, (uint32)(GEN_NONTRAPPING_NAN.frac), BUS_PER); - } else { - write_w(mau_state.dst, (uint32)(xfp->sign_exp), BUS_PER); - write_w(mau_state.dst + 4, (uint32)(xfp->frac >> 32), BUS_PER); - write_w(mau_state.dst + 8, (uint32)(xfp->frac), BUS_PER); - } - if (set_nz()) { - if (XFP_SIGN(xfp)) { - mau_state.asr |= MAU_ASR_N; - } - if (XFP_EXP(xfp) == 0 && XFP_FRAC(xfp) == 0) { - mau_state.asr |= MAU_ASR_Z; - } - } - break; - default: - sim_debug(TRACE_DBG, &mau_dev, - "[store_op3] WARNING: Unhandled destination: %02x\n", mau_state.op3); - break; - } -} - -/************************************************************************* - * - * MAU instruction impelementations - * - *************************************************************************/ - -static void mau_rdasr() -{ - switch (mau_state.op3) { - /* Handled */ - case M_OP3_MEM_SINGLE: - write_w(mau_state.dst, mau_state.asr, BUS_PER); - break; - case M_OP3_MEM_DOUBLE: - write_w(mau_state.dst, mau_state.asr, BUS_PER); - write_w(mau_state.dst + 4, mau_state.asr, BUS_PER); - break; - case M_OP3_MEM_TRIPLE: - write_w(mau_state.dst, mau_state.asr, BUS_PER); - write_w(mau_state.dst + 4, mau_state.asr, BUS_PER); - write_w(mau_state.dst + 8, mau_state.asr, BUS_PER); - break; - /* Unhandled */ - default: - sim_debug(TRACE_DBG, &mau_dev, - "[mau_rdasr] WARNING: Unhandled source: %02x\n", - mau_state.op3); - break; - } -} - -static void mau_wrasr() -{ - switch (mau_state.op1) { - /* Handled */ - case M_OP_MEM_SINGLE: - mau_state.asr = read_w(mau_state.src, ACC_AF, BUS_PER); - sim_debug(TRACE_DBG, &mau_dev, - "[WRASR] Writing ASR with: %08x\n", - mau_state.asr); - break; - default: - sim_debug(TRACE_DBG, &mau_dev, - "[mau_wrasr] WARNING: Unhandled source: %02x\n", - mau_state.op3); - break; - } -} - -/* - * OP3 = OP1 - */ -static void mau_move() -{ - XFP xfp = {0}; - - load_src_op(mau_state.op1, &xfp); - store_op3(&xfp); -} - -static void mau_cmp() -{ - XFP a, b; - - load_src_op(mau_state.op1, &a); - load_src_op(mau_state.op2, &b); - xfp_cmp(&a, &b); -} - -static void mau_cmps() -{ - XFP a, b; - - load_src_op(mau_state.op1, &a); - load_src_op(mau_state.op2, &b); - xfp_cmps(&a, &b); -} - -static void mau_cmpe() -{ - XFP a, b; - - load_src_op(mau_state.op1, &a); - load_src_op(mau_state.op2, &b); - xfp_cmpe(&a, &b); -} - -static void mau_cmpes() -{ - XFP a, b; - - load_src_op(mau_state.op1, &a); - load_src_op(mau_state.op2, &b); - xfp_cmpes(&a, &b); -} - -static void mau_ldr() -{ - XFP xfp; - - load_src_op(mau_state.op1, &xfp); - sim_debug(TRACE_DBG, &mau_dev, - "[LDR] Loading DR with %04x%016llx\n", - xfp.sign_exp, xfp.frac); - mau_state.dr.sign_exp = xfp.sign_exp; - mau_state.dr.frac = xfp.frac; -} - -static void mau_erof() -{ - DFP dfp; - SFP sfp; - - switch (mau_state.op3) { - case M_OP3_F0_SINGLE: - case M_OP3_F0_DOUBLE: - case M_OP3_F0_TRIPLE: - mau_state.f0.sign_exp = mau_state.dr.sign_exp; - mau_state.f0.frac = mau_state.dr.frac; - return; - case M_OP3_F1_SINGLE: - case M_OP3_F1_DOUBLE: - case M_OP3_F1_TRIPLE: - mau_state.f1.sign_exp = mau_state.dr.sign_exp; - mau_state.f1.frac = mau_state.dr.frac; - return; - case M_OP3_F2_SINGLE: - case M_OP3_F2_DOUBLE: - case M_OP3_F2_TRIPLE: - mau_state.f2.sign_exp = mau_state.dr.sign_exp; - mau_state.f2.frac = mau_state.dr.frac; - return; - case M_OP3_F3_SINGLE: - case M_OP3_F3_DOUBLE: - case M_OP3_F3_TRIPLE: - mau_state.f3.sign_exp = mau_state.dr.sign_exp; - mau_state.f3.frac = mau_state.dr.frac; - return; - case M_OP3_MEM_SINGLE: - sfp = xfp_to_sfp(&(mau_state.dr), MAU_RM); - write_w(mau_state.dst, (uint32)sfp, BUS_PER); - return; - case M_OP3_MEM_DOUBLE: - dfp = xfp_to_dfp(&(mau_state.dr), MAU_RM); - write_w(mau_state.dst + 4, (uint32)(dfp >> 32), BUS_PER); - write_w(mau_state.dst, (uint32)(dfp), BUS_PER); - return; - case M_OP3_MEM_TRIPLE: - write_w(mau_state.dst, (uint32)(mau_state.dr.sign_exp), BUS_PER); - write_w(mau_state.dst + 4, (uint32)(mau_state.dr.frac >> 32), BUS_PER); - write_w(mau_state.dst + 8, (uint32)(mau_state.dr.frac), BUS_PER); - return; - default: - sim_debug(TRACE_DBG, &mau_dev, - "[mau_erof] WARNING: Unhandled destination: %02x\n", mau_state.op3); - return; - } -} - - -static void mau_rtoi() -{ - XFP a, result; - - load_src_op(mau_state.op1, &a); - mau_round_xfp_to_int(&a, &result, MAU_RM); - store_op3(&result); -} - -static void mau_ftoi() -{ - XFP a; - uint32 result; - - load_src_op(mau_state.op1, &a); - result = xfp_to_int(&a, MAU_RM); - store_op3_int(result); -} - -static void mau_dtof() -{ - DEC d; - XFP result; - - load_op1_decimal(&d); - mau_decimal_to_xfp(&d, &result); - store_op3(&result); -} - -static void mau_ftod() -{ - XFP a; - DEC d; - - load_src_op(mau_state.op1, &a); - xfp_to_decimal(&a, &d, MAU_RM); - store_op3_decimal(&d); -} - -static void mau_add() -{ - XFP a, b, result; - - load_src_op(mau_state.op1, &a); - load_src_op(mau_state.op2, &b); - xfp_add(&a, &b, &result, MAU_RM); - store_op3(&result); -} - -/* - * OP3 = OP2 - OP1 - */ -static void mau_sub() -{ - XFP a, b, result; - - load_src_op(mau_state.op1, &a); - load_src_op(mau_state.op2, &b); - xfp_sub(&b, &a, &result, MAU_RM); - store_op3(&result); -} - -/* - * OP3 = OP1 * OP2 - */ -static void mau_mul() -{ - XFP a, b, result; - - load_src_op(mau_state.op1, &a); - load_src_op(mau_state.op2, &b); - xfp_mul(&b, &a, &result, MAU_RM); - store_op3(&result); -} - -/* - * OP3 = OP1 / OP2 - */ -static void mau_div() -{ - XFP a, b, result; - - load_src_op(mau_state.op1, &a); - load_src_op(mau_state.op2, &b); - sim_debug(TRACE_DBG, &mau_dev, - "[DIV OP2/OP1] OP2=0x%04x%016llx OP1=0x%04x%016llx\n", - b.sign_exp, b.frac, - a.sign_exp, a.frac); - xfp_div(&b, &a, &result, MAU_RM); - store_op3(&result); -} - -static void mau_neg() -{ - XFP a, result; - - load_src_op(mau_state.op1, &a); - result.sign_exp = a.sign_exp; - result.frac = a.frac; - result.sign_exp ^= 0x8000; - result.s = a.s; - store_op3(&result); -} - -static void mau_abs() -{ - XFP a, result; - - load_src_op(mau_state.op1, &a); - result.sign_exp = a.sign_exp; - result.frac = a.frac; - result.sign_exp &= 0x7fff; - result.s = a.s; - store_op3(&result); -} - -/* - * OP3 = sqrt(OP1) - */ -static void mau_sqrt() -{ - XFP a, result; - - load_src_op(mau_state.op1, &a); - xfp_sqrt(&a, &result, MAU_RM); - store_op3(&result); -} - -/* - * OP3 = float(OP1) - * - * If the source operand is more than one word wide, only the last - * word is converted. - */ -static void mau_itof() -{ - XFP xfp; - int32 val = 0; - - mau_state.asr &= ~(MAU_ASR_N|MAU_ASR_Z); - - switch(mau_state.op1) { - case M_OP_F0: - case M_OP_F1: - case M_OP_F2: - case M_OP_F3: - mau_exc(MAU_ASR_IS, MAU_ASR_IM); - return; - case M_OP_MEM_SINGLE: - val = read_w(mau_state.src, ACC_AF, BUS_PER); - break; - case M_OP_MEM_DOUBLE: - val = read_w(mau_state.src + 4, ACC_AF, BUS_PER); - break; - case M_OP_MEM_TRIPLE: - val = read_w(mau_state.src + 8, ACC_AF, BUS_PER); - break; - default: - break; - } - /* Convert */ - mau_int_to_xfp(val, &xfp); - - store_op3(&xfp); -} - -/* - * OP3 = REMAINDER(b/a) - */ -static void mau_remainder() -{ - XFP a, b, result; - - load_src_op(mau_state.op1, &a); - load_src_op(mau_state.op2, &b); - xfp_remainder(&b, &a, &result, MAU_RM); - store_op3(&result); -} - -/* - * Decode the command word into its corresponding parts. Both src and - * dst are optional depending on the WE32100 operand, and may be set - * to any value if not used. - */ -static SIM_INLINE void mau_decode(uint32 cmd, uint32 src, uint32 dst) -{ - mau_state.cmd = cmd; - mau_state.src = src; - mau_state.dst = dst; - mau_state.opcode = (uint8) ((cmd & 0x7c00) >> 10); - mau_state.op1 = (uint8) ((cmd & 0x0380) >> 7); - mau_state.op2 = (uint8) ((cmd & 0x0070) >> 4); - mau_state.op3 = (uint8) (cmd & 0x000f); - sim_debug(DECODE_DBG, &mau_dev, - "opcode=%s (%02x) op1=%s op2=%s op3=%s\n", - mau_op_names[mau_state.opcode], - mau_state.opcode, - src_op_names[mau_state.op1 & 0x7], - src_op_names[mau_state.op2 & 0x7], - dst_op_names[mau_state.op3 & 0xf]); -} - -/* - * Handle a command. - */ -static void mau_execute() -{ - clear_asr(); - - switch(mau_state.opcode) { - case M_NOP: - /* Do nothing */ - break; - case M_ADD: - mau_add(); - break; - case M_SUB: - mau_sub(); - break; - case M_MUL: - mau_mul(); - break; - case M_DIV: - mau_div(); - break; - case M_RDASR: - mau_rdasr(); - break; - case M_WRASR: - mau_wrasr(); - break; - case M_MOVE: - mau_move(); - break; - case M_LDR: - mau_ldr(); - break; - case M_ITOF: - mau_itof(); - break; - case M_EROF: - mau_erof(); - break; - case M_RTOI: - mau_rtoi(); - break; - case M_FTOI: - mau_ftoi(); - break; - case M_CMP: - mau_cmp(); - break; - case M_CMPS: - mau_cmps(); - break; - case M_CMPE: - mau_cmpe(); - break; - case M_CMPES: - mau_cmpes(); - break; - case M_REM: - mau_remainder(); - break; - case M_NEG: - mau_neg(); - break; - case M_ABS: - mau_abs(); - break; - case M_SQRT: - mau_sqrt(); - break; - case M_FTOD: - mau_ftod(); - break; - case M_DTOF: - mau_dtof(); - break; - default: - sim_debug(TRACE_DBG, &mau_dev, - "[execute] unhandled opcode %s [0x%02x]\n", - mau_op_names[mau_state.opcode], - mau_state.opcode); - break; - } - - /* If an error has occured, abort */ - abort_on_fault(); - - /* Copy the N, Z, V and C (from PS) flags over to the CPU's PSW */ - R[NUM_PSW] &= ~(MAU_ASR_N|MAU_ASR_Z|MAU_ASR_IO|MAU_ASR_PS); - R[NUM_PSW] |= (mau_state.asr & (MAU_ASR_N|MAU_ASR_Z|MAU_ASR_IO|MAU_ASR_PS)); - - /* Set the RA and CSC flags in the ASR */ - mau_state.asr |= MAU_ASR_RA; - if (mau_state.opcode != M_RDASR && mau_state.opcode != M_LDR) { - mau_state.asr |= MAU_ASR_CSC; - } -} - -/* - * Receive a broadcast from the CPU, and potentially handle it. - */ -t_stat mau_broadcast(uint32 cmd, uint32 src, uint32 dst) -{ - uint8 id = (uint8) ((cmd & 0xff000000) >> 24); - - /* If the MAU isn't attached, or if this message isn't for us, - * return SCPE_NXM. Otherwise, decode and act on the command. */ - if (id != MAU_ID) { - sim_debug(DECODE_DBG, &mau_dev, - "[broadcast] Message for coprocessor id %d is not for MAU (%d)\n", - id, MAU_ID); - return SCPE_NXM; - } else if (mau_dev.flags & DEV_DIS) { - sim_debug(DECODE_DBG, &mau_dev, - "[broadcast] Message for MAU, but MAU is not attached.\n"); - return SCPE_NOATT; - } else { - mau_decode(cmd, src, dst); - mau_execute(); - return SCPE_OK; - } -} - -CONST char *mau_description(DEVICE *dptr) -{ -#if defined(REV3) - return "WE 32106 MAU"; -#else - return "WE 32206 MAU"; -#endif -} +/* 3b2_mau.c: WE32106 and WE32206 Math Accelerator Unit + + Copyright (c) 2021-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. + + --------------------------------------------------------------------- + + This file is part of a simulation of the WE 32106 and WE 32206 Math + Acceleration Units. The WE 32106 and WE 32206 are IEEE-754 + compabitle floating point hardware math accelerators that were + available as an optional component on the AT&T 3B2/310 and 3B2/400, + and as a standard component on the 3B2/500, 3B2/600, 3B2/700, and + 3B2/1000. + + Unimplemented Features + ====================== + + All features of the WE 32106 MAU have been implemented, but there + remain some features of the WE 32206 that are not yet implemented. + Neither System V UNIX nor the Version 3 MAU diagnostics appear to + use these features in any way, but there is no guarantee that other + software does not use them. They are: + + - The FE, UW, and WF bits of the Auxiliary Status Register + - The new operand registers f4 through f7 + - The register bank select feature of the Command Register + - The RC and RCS bits of the Command Register + - The new WE 32206 instructions: + 1. ATAN + 2. COS + 3. PI + 4. SIN + + --------------------------------------------------------------------- + + Portions of this code are derived from the SoftFloat 2c library by + John R. Hauser. Functions derived from SoftFloat 2c are clearly + marked in the comments. + + Legal Notice + ============ + + SoftFloat was written by John R. Hauser. Release 2c of SoftFloat + was made possible in part by the International Computer Science + Institute, located at Suite 600, 1947 Center Street, Berkeley, + California 94704. Funding was partially provided by the National + Science Foundation under grant MIP-9311980. The original version + of this code was written as part of a project to build a + fixed-point vector processor in collaboration with the University + of California at Berkeley, overseen by Profs. Nelson Morgan and + John Wawrzynek. + + THIS SOFTWARE IS DISTRIBUTED AS IS, FOR FREE. Although reasonable + effort has been made to avoid it, THIS SOFTWARE MAY CONTAIN FAULTS + THAT WILL AT TIMES RESULT IN INCORRECT BEHAVIOR. USE OF THIS + SOFTWARE IS RESTRICTED TO PERSONS AND ORGANIZATIONS WHO CAN AND + WILL TOLERATE ALL LOSSES, COSTS, OR OTHER PROBLEMS THEY INCUR DUE + TO THE SOFTWARE WITHOUT RECOMPENSE FROM JOHN HAUSER OR THE + INTERNATIONAL COMPUTER SCIENCE INSTITUTE, AND WHO FURTHERMORE + EFFECTIVELY INDEMNIFY JOHN HAUSER AND THE INTERNATIONAL COMPUTER + SCIENCE INSTITUTE (possibly via similar legal notice) AGAINST ALL + LOSSES, COSTS, OR OTHER PROBLEMS INCURRED BY THEIR CUSTOMERS AND + CLIENTS DUE TO THE SOFTWARE, OR INCURRED BY ANYONE DUE TO A + DERIVATIVE WORK THEY CREATE USING ANY PART OF THE SOFTWARE. + + The following are expressly permitted, even for commercial + purposes: + + (1) distribution of SoftFloat in whole or in part, as long as this + and other legal notices remain and are prominent, and provided also + that, for a partial distribution, prominent notice is given that it + is a subset of the original; and + + (2) inclusion or use of SoftFloat in whole or in part in a + derivative work, provided that the use restrictions above are met + and the minimal documentation requirements stated in the source + code are satisfied. + --------------------------------------------------------------------- +*/ + +#include "3b2_mau.h" + +#include + +#include "3b2_cpu.h" +#include "3b2_mem.h" +#include "3b2_mmu.h" + +#define MAU_ID 0 /* Coprocessor ID of MAU */ + +#define TININESS_BEFORE_ROUNDING TRUE + +/* Static function declarations */ +static SIM_INLINE void mau_case_div_zero(XFP *op1, XFP *op2, XFP *result); + +static SIM_INLINE void mau_exc(uint32 flag, uint32 mask); +static SIM_INLINE void abort_on_fault(); +static SIM_INLINE void mau_decode(uint32 cmd, uint32 src, uint32 dst); +static SIM_INLINE t_bool le_128(t_uint64 a0, t_uint64 a1, t_uint64 b0, t_uint64 b1); +static SIM_INLINE t_bool eq_128(t_uint64 a0, t_uint64 a1, t_uint64 b0, t_uint64 b1); +static SIM_INLINE t_bool lt_128(t_uint64 a0, t_uint64 a1, t_uint64 b0, t_uint64 b1); +static uint8 leading_zeros(uint32 val); +static uint8 leading_zeros_64(t_int64 val); +static void shift_right_32_jamming(uint32 val, int16 count, uint32 *result); +static void shift_right_64_jamming(t_uint64 val, int16 count, t_uint64 *result); +static void shift_right_extra_64_jamming(t_uint64 val_a, t_uint64 val_b, int16 count, + t_uint64 *r_a, t_uint64 *r_b); +static void shift_right_128_jamming(t_uint64 val_a, t_uint64 val_b, int16 count, + t_uint64 *r_a, t_uint64 *r_b); +static void short_shift_left_128(t_uint64 val_a, t_uint64 val_b, int16 count, + t_uint64 *r_a, t_uint64 *r_b); +static void shift_right_128(t_uint64 val_a, t_uint64 val_b, int16 count, + t_uint64 *r_a, t_uint64 *r_b); +static void add_128(t_uint64 a0, t_uint64 a1, + t_uint64 b0, t_uint64 b1, + t_uint64 *r_low, t_uint64 *r_high); +static void sub_128(t_uint64 a0, t_uint64 a1, + t_uint64 b0, t_uint64 b1, + t_uint64 *r_low, t_uint64 *r_high); +static void mul_64_to_128(t_uint64 a, t_uint64 b, t_uint64 *r_low, t_uint64 *r_high); +static void mul_64_by_shifted_32_to_128(t_uint64 a, uint32 b, t_mau_128 *result); +static t_uint64 estimate_div_128_to_64(t_uint64 a0, t_uint64 a1, t_uint64 b); +static uint32 round_pack_int(t_bool sign, t_uint64 frac, RM rounding_mode); +static t_int64 round_pack_int64(t_bool sign, + t_uint64 abs_0, t_uint64 abs_1, + RM rounding_mode); + +static SFP round_pack_sfp(t_bool sign, int16 exp, + uint32 frac, RM rounding_mode); +static DFP round_pack_dfp(t_bool sign, int16 exp, t_uint64 frac, + t_bool xfp_sticky, RM rounding_mode); +static void round_pack_xfp(t_bool sign, int32 exp, + t_uint64 frac_a, t_uint64 frac_b, + RM rounding_mode, XFP *result); +static void propagate_xfp_nan(XFP *a, XFP *b, XFP *result); +static void propagate_xfp_nan_128(XFP* a, XFP* b, t_mau_128* result); +static void normalize_round_pack_xfp(t_bool sign, int32 exp, + t_uint64 frac_0, t_uint64 frac_1, + RM rounding_mode, XFP *result); +static void normalize_sfp_subnormal(uint32 in_frac, int16 *out_exp, uint32 *out_frac); +static void normalize_dfp_subnormal(t_uint64 in_frac, int16 *out_exp, t_uint64 *out_frac); +static void normalize_xfp_subnormal(t_uint64 in_frac, int32 *out_exp, t_uint64 *out_frac); + +static T_NAN sfp_to_common_nan(SFP val); +static T_NAN dfp_to_common_nan(DFP val); +static T_NAN xfp_to_common_nan(XFP *val); +static SFP common_nan_to_sfp(T_NAN nan); +static DFP common_nan_to_dfp(T_NAN nan); +static void common_nan_to_xfp(T_NAN nan, XFP *result); + +static void sfp_to_xfp(SFP val, XFP *result); +static void dfp_to_xfp(DFP val, XFP *result); +static SFP xfp_to_sfp(XFP *val, RM rounding_mode); +static DFP xfp_to_dfp(XFP *val, RM rounding_mode); + +static uint32 xfp_eq(XFP *a, XFP *b); +static uint32 xfp_lt(XFP *a, XFP *b); + +static void xfp_cmp(XFP *a, XFP *b); +static void xfp_cmpe(XFP *a, XFP *b); +static void xfp_cmps(XFP *a, XFP *b); +static void xfp_cmpes(XFP *a, XFP *b); +static void xfp_add(XFP *a, XFP *b, XFP *result, RM rounding_mode); +static void xfp_sub(XFP *a, XFP *b, XFP *result, RM rounding_mode); +static void xfp_mul(XFP *a, XFP *b, XFP *result, RM rounding_mode); +static void xfp_div(XFP *a, XFP *b, XFP *result, RM rounding_mode); +static void xfp_sqrt(XFP *a, XFP *result, RM rounding_mode); +static void xfp_remainder(XFP *a, XFP *b, XFP *result, RM rounding_mode); + +static void load_src_op(uint8 op, XFP *xfp); +static void load_op1_decimal(DEC *d); +static void store_op3_int(uint32 val); +static void store_op3_decimal(DEC *d); +static void store_op3(XFP *xfp); + +static void mau_rdasr(); +static void mau_wrasr(); +static void mau_move(); +static void mau_cmp(); +static void mau_cmps(); +static void mau_cmpe(); +static void mau_cmpes(); +static void mau_ldr(); +static void mau_erof(); +static void mau_rtoi(); +static void mau_ftoi(); +static void mau_dtof(); +static void mau_ftod(); +static void mau_add(); +static void mau_sub(); +static void mau_mul(); +static void mau_div(); +static void mau_neg(); +static void mau_abs(); +static void mau_sqrt(); +static void mau_itof(); +static void mau_remainder(); + +static void mau_execute(); + +UNIT mau_unit = { UDATA(NULL, 0, 0) }; + +MAU_STATE mau_state; + +BITFIELD asr_bits[] = { +#if defined(REV3) + BIT(FE), + BITFFMT(VER,3,%d), + BIT(UW), +#else + BITNCF(5), +#endif + BIT(PR), + BIT(QS), + BIT(US), + BIT(OS), + BIT(IS), + BIT(PM), + BIT(QM), + BIT(UM), + BIT(OM), + BIT(IM), +#if defined(REV3) + BIT(WF), +#else + BITNCF(1), +#endif + BIT(UO), + BIT(CSC), + BIT(PS), + BIT(IO), + BIT(Z), + BIT(N), + BITFFMT(RC,2,%d), + BIT(NTNC), + BIT(ECP), + BITNCF(5), + BIT(RA), + ENDBITS +}; + +REG mau_reg[] = { + { HRDATAD (CMD, mau_state.cmd, 32, "Command Word") }, + { HRDATADF (ASR, mau_state.asr, 32, "ASR", asr_bits) }, + { HRDATAD (OPCODE, mau_state.opcode, 8, "Opcode") }, + { HRDATAD (OP1, mau_state.op1, 8, "Operand 1") }, + { HRDATAD (OP2, mau_state.op2, 8, "Operand 2") }, + { HRDATAD (OP3, mau_state.op3, 8, "Operand 3") }, + { NULL } +}; + +MTAB mau_mod[] = { + { UNIT_EXBRK, UNIT_EXBRK, "Break on exceptions", "EXBRK", + NULL, NULL, NULL, "Enables break on floating point exceptions" }, + { UNIT_EXBRK, 0, "No break on exceptions", "NOEXBRK", + NULL, NULL, NULL, "Disables break on floating point exceptions" }, + { 0 } +}; + +static DEBTAB mau_debug[] = { + { "DECODE", DECODE_DBG, "Decode" }, + { "TRACE", TRACE_DBG, "Call Trace" }, + { NULL } +}; + +DEVICE mau_dev = { + "MAU", /* name */ + &mau_unit, /* units */ + mau_reg, /* registers */ + mau_mod, /* modifiers */ + 1, /* #units */ + 16, /* address radix */ + 32, /* address width */ + 1, /* address incr. */ + 16, /* data radix */ + 8, /* data width */ + NULL, /* examine routine */ + NULL, /* deposit routine */ + &mau_reset, /* reset routine */ + NULL, /* boot routine */ + NULL, /* attach routine */ + NULL, /* detach routine */ + NULL, /* context */ +#ifdef REV3 + DEV_DEBUG, /* Rev 3 flags: Always required */ +#else + DEV_DISABLE|DEV_DEBUG, /* Rev 2 flags: Can be disabled */ +#endif + 0, /* debug control flags */ + mau_debug, /* debug flag names */ + NULL, /* memory size change */ + NULL, /* logical name */ + NULL, /* help routine */ + NULL, /* attach help routine */ + NULL, /* help context */ + &mau_description /* device description */ +}; + +XFP INF = { + 0x7fff, + 0x0000000000000000ull, + 0 +}; + +XFP TRAPPING_NAN = { + 0x7fff, + 0x7fffffffffffffffull, + 0 +}; + +/* Generated Non-Trapping NaN + * p. 2-8 "When the MAU generates a nontrapping NaN, J+fraction + * contains all 1s. The MAU never generates a trapping NaN." + */ +XFP GEN_NONTRAPPING_NAN = { + 0x7fff, + 0xffffffffffffffffull, + 0 +}; + +CONST char *mau_op_names[32] = { + "0x00", "0x01", "ADD", "SUB", + "DIV", "REM", "MUL", "MOVE", + "RDASR", "WRASR", "CMP", "CMPE", + "ABS", "SQRT", "RTOI", "FTOI", + "ITOF", "DTOF", "FTOD", "NOP", + "EROF", "0x15", "0x16", "NEG", + "LDR", "0x19", "CMPS", "CMPES", +#if defined(REV3) + "SIN", "COS", "ATAN", "PI" +#else + "0x1C", "0x1D", "0x1E", "0x1F" +#endif +}; + +CONST char *src_op_names[8] = { + "F0", "F1", "F2", "F3", + "MEM S", "MEM D", "MEM X", "N/A" +}; + +CONST char *dst_op_names[16] = { + "F0 S", "F1 S", "F2 S", "F3 S", + "F0 D", "F1 D", "F2 D", "F3 D", + "F0 X", "F1 X", "F2 X", "F3 X", + "MEM S", "MEM D", "MEM X", "N/A" +}; + +/* + * Special Cases + * ------------- + * + * The handling of combinations of special input values is specified + * in the "WE32106 Math Acceleration Unit Information Manual" + * pp. 5-3--5-5. + * + * Each of these "special case" routines can be called by math + * functions based on a combination of the input values. + * + * (At the moment, only divide-by-zero is explicitly called out here + * as a special case) + */ + +static SIM_INLINE void mau_case_div_zero(XFP *op1, XFP *op2, XFP *result) +{ + mau_state.asr |= MAU_ASR_QS; + + if (mau_state.asr & MAU_ASR_QM) { + mau_state.asr |= MAU_ASR_ECP; + PACK_XFP(0, 0x7fff, 0x8000000000000000ull, result); + } else { + if (XFP_SIGN(op1) ^ XFP_SIGN(op2)) { + PACK_XFP(1, INF.sign_exp, INF.frac, result); + } else { + PACK_XFP(0, INF.sign_exp, INF.frac, result); + } + } +} + +static SIM_INLINE void mau_exc(uint32 flag, uint32 mask) +{ + sim_debug(TRACE_DBG, &mau_dev, + "[mau_exc] asr=%08x flag=%08x mask=%08x\n", + mau_state.asr, flag, mask); + + mau_state.asr |= flag; + + /* + * page 2-14: NTNC bit is checked if an Invalid Operation + * exception occurs while the Invalid Operation Mask bit is + * clear. If NTNC is set to 1, an exception occurs and bit 9 + * (IS) is set. If NTNC is set to 0, no exception occurs, + * and a nontraping NaN is generated. + */ + if (flag == MAU_ASR_IS && (mau_state.asr & MAU_ASR_IM) == 0) { + if (mau_state.asr & MAU_ASR_NTNC) { + mau_state.asr |= MAU_ASR_ECP; + } else { + mau_state.ntnan = TRUE; + } + return; + } + + if (mau_state.asr & mask) { + mau_state.asr |= MAU_ASR_ECP; + } +} + +/* + * Returns true if an exceptional condition is present. + */ +static SIM_INLINE t_bool mau_exception_present() +{ + + return mau_state.asr & MAU_ASR_ECP && + (((mau_state.asr & MAU_ASR_IS) && ((mau_state.asr & MAU_ASR_IM) || + (mau_state.asr & MAU_ASR_NTNC))) || + ((mau_state.asr & MAU_ASR_US) && (mau_state.asr & MAU_ASR_UM)) || + ((mau_state.asr & MAU_ASR_OS) && (mau_state.asr & MAU_ASR_OM)) || + ((mau_state.asr & MAU_ASR_PS) && (mau_state.asr & MAU_ASR_PM)) || + ((mau_state.asr & MAU_ASR_QS) && (mau_state.asr & MAU_ASR_QM))); +} + +static SIM_INLINE void abort_on_fault() +{ + switch(mau_state.opcode) { + case M_NOP: + case M_RDASR: + case M_WRASR: + case M_EROF: + case M_LDR: + return; + default: + /* + * Integer overflow is non-maskable in the MAU, but generates an Integer + * Overflow exception to be handled by the WE32100 CPU (if not masked + * in the CPU's PSW). + */ + if ((mau_state.asr & MAU_ASR_IO) && (R[NUM_PSW] & PSW_OE_MASK)) { + if (mau_unit.flags & UNIT_EXBRK) { + stop_reason = STOP_EX; + } + sim_debug(TRACE_DBG, &mau_dev, + "[abort_on_fault] Aborting on un-maskable overflow fault. ASR=%08x\n", + mau_state.asr); + cpu_abort(NORMAL_EXCEPTION, INTEGER_OVERFLOW); + } + + /* Otherwise, check for other exceptions. */ + if (mau_exception_present()) { + if (mau_unit.flags & UNIT_EXBRK) { + stop_reason = STOP_EX; + } + sim_debug(TRACE_DBG, &mau_dev, + "[abort_on_fault] Aborting on ECP fault. ASR=%08x\n", + mau_state.asr); + cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); + } + + break; + } +} + +/* + * Clears N and Z flags in the ASR if appropriate. + */ +static void clear_asr() +{ + mau_state.ntnan = FALSE; + + switch(mau_state.opcode) { + case M_NOP: + case M_RDASR: + case M_WRASR: + case M_EROF: + return; + default: + mau_state.asr &= ~(MAU_ASR_Z|MAU_ASR_N|MAU_ASR_ECP); + break; + } +} + +/* + * Returns true if the 'nz' flags should be set. + * + * Note: There is an undocumented feature of the WE32106 expressed + * here. If an exception has occured, the Z and N flags are not to be + * set! + */ +static t_bool set_nz() +{ + switch(mau_state.opcode) { + case M_NOP: + case M_RDASR: + case M_WRASR: + case M_EROF: + return FALSE; + default: + return (mau_state.asr & MAU_ASR_ECP) == 0; + } +} + +t_stat mau_reset(DEVICE *dptr) +{ + memset(&mau_state, 0, sizeof(MAU_STATE)); +#if defined(REV3) + mau_state.asr |= MAU_ASR_VER; /* Version 1 MAU */ +#endif + return SCPE_OK; +} + +/************************************************************************* + * Utility Functions + ************************************************************************/ + +/* + * Compare two 128-bit values a and b. Rturns true if a <= b + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static SIM_INLINE t_bool le_128(t_uint64 a0, t_uint64 a1, t_uint64 b0, t_uint64 b1) +{ + return (a0 < b0) || ((a0 == b0) && (a1 <= b1)); +} + +/* + * Compare two 128-bit values a and b. Returns true if a = b + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static SIM_INLINE t_bool eq_128(t_uint64 a0, t_uint64 a1, t_uint64 b0, t_uint64 b1) +{ + return (a0 == b0) && (a1 == b1); +} + +/* + * Compare two 128-bit values a and b. Returns true if a < b + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static SIM_INLINE t_bool lt_128(t_uint64 a0, t_uint64 a1, t_uint64 b0, t_uint64 b1) +{ + return (a0 < b0) || ((a0 == b0) && (a1 < b1)); +} + +/* + * Return the number of leading binary zeros in an unsigned 32-bit + * value. + * + * Algorithm couresty of "Hacker's Delight" by Henry S. Warren. + */ +static uint8 leading_zeros(uint32 val) +{ + unsigned n = 0; + + if (val <= 0x0000ffff) { + n += 16; + val <<= 16; + } + if (val <= 0x00ffffff) { + n += 8; + val <<= 8; + } + if (val <= 0x0fffffff) { + n += 4; + val <<= 4; + } + if (val <= 0x3fffffff) { + n += 2; + val <<= 2; + } + if (val <= 0x7fffffff) { + n++; + } + + return n; +} + +/* + * Return the number of leading binary zeros in a signed 64-bit + * value. + */ +static uint8 leading_zeros_64(t_int64 val) +{ + uint8 n = 0; + + if (val == 0) { + return 64; + } + + while (1) { + if (val < 0) break; + + n++; + + val <<= 1; + } + + return n; +} + +/* + * Shift a 32-bit unsigned value, 'val', right by 'count' bits. If any + * non-zero bits are shifted off, they are "jammed" into the least + * significant bit of the result by setting the least significant bit + * to 1. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static void shift_right_32_jamming(uint32 val, int16 count, uint32 *result) +{ + uint32 tmp; + + if (count == 0) { + tmp = val; + } else if (count < 32) { + tmp = (val >> count) | ((val << ((-count) & 31)) != 0); + } else { + tmp = (val != 0); + } + + *result = tmp; +} + +/* + * Shift a 64-bit unsigned value, 'val', right by 'count' bits. If any + * non-zero bits are shifted off, they are "jammed" into the least + * significant bit of the result by setting the least significant bit + * to 1. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static void shift_right_64_jamming(t_uint64 val, int16 count, t_uint64 *result) +{ + t_uint64 tmp; + + if (count == 0) { + tmp = val; + } else if (count < 64) { + tmp = (val >> count) | ((val << ((-count) & 63)) != 0); + } else { + tmp = (val != 0); + } + + *result = tmp; +} + +/* + * Shifts the 128-bit value formed by concatenating val_a and val_b + * right by 64 _plus_ the number of bits given in 'count'. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static void shift_right_extra_64_jamming(t_uint64 val_a, t_uint64 val_b, int16 count, + t_uint64 *r_a, t_uint64 *r_b) +{ + t_uint64 a, b; + int8 neg_count = (-count) & 63; + + if (count == 0) { + b = val_b; + a = val_a; + } else if (count < 64) { + b = (val_a << neg_count) | (val_b != 0); + a = val_a >> count; + } else { + if (count == 64) { + b = val_a | (val_b != 0); + } else { + b = ((val_a | val_b) != 0); + } + a = 0; + } + + *r_a = a; + *r_b = b; +} + +/* + * Shift the 128-bit value formed by val_a and val_b right by + * 64 plus the number of bits given in count. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static void shift_right_128_jamming(t_uint64 val_a, t_uint64 val_b, int16 count, + t_uint64 *r_a, t_uint64 *r_b) +{ + t_uint64 tmp_a, tmp_b; + int8 neg_count = (-count) & 63; + + if (count == 0) { + tmp_a = val_a; + tmp_b = val_b; + } else if (count < 64) { + tmp_a = (val_a >> count); + tmp_b = (val_a << neg_count) | (val_b != 0); + } else { + if (count == 64) { + tmp_b = val_a | (val_b != 0); + } else { + tmp_b = ((val_a | val_b) != 0); + } + tmp_a = 0; + } + + *r_a = tmp_a; + *r_b = tmp_b; +} + +/* + * Shifts the 128-bit value formed by val_a and val_b left by the + * number of bits given in count. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static void short_shift_left_128(t_uint64 val_a, t_uint64 val_b, int16 count, + t_uint64 *r_a, t_uint64 *r_b) +{ + *r_b = val_b << count; + if (count == 0) { + *r_a = val_a; + } else { + *r_a = (val_a << count) | (val_b >> ((-count) & 63)); + } +} + +/* + * Shifts the 128-bit value formed by val_a and val_b right by the + * number of bits given ihn 'count'. Any bits shifted off are lost. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static void shift_right_128(t_uint64 val_a, t_uint64 val_b, int16 count, + t_uint64 *r_a, t_uint64 *r_b) +{ + t_uint64 tmp_a, tmp_b; + int8 neg_count; + + neg_count = (- count) & 63; + + if (count == 0) { + tmp_a = val_a; + tmp_b = val_b; + } else if (count < 64) { + tmp_a = val_a >> count; + tmp_b = (val_a << neg_count) | (val_b >> count); + } else { + tmp_a = 0; + tmp_b = (count < 128) ? (val_a >> (count & 63)) : 0; + } + + *r_a = tmp_a; + *r_b = tmp_b; +} + +/* + * Add two 128-bit values. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static void add_128(t_uint64 a0, t_uint64 a1, + t_uint64 b0, t_uint64 b1, + t_uint64 *r_low, t_uint64 *r_high) +{ + t_uint64 tmp; + + tmp = a1 + b1; + *r_high = tmp; + *r_low = a0 + b0 + (tmp < a1); +} + +/* + * Subract two 128-bit values. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static void sub_128(t_uint64 a0, t_uint64 a1, + t_uint64 b0, t_uint64 b1, + t_uint64 *r_low, t_uint64 *r_high) +{ + *r_high = a1 - b1; + *r_low = a0 - b0 - (a1 < b1); +} + +/* + * Multiplies a by b to obtain a 128-bit product. The product is + * broken into two 64-bit pieces which are stored at r_low and r_high. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static void mul_64_to_128(t_uint64 a, t_uint64 b, t_uint64 *r_low, t_uint64 *r_high) +{ + uint32 a_high, a_low, b_high, b_low; + t_uint64 rl, rm_a, rm_b, rh; + + a_low = (uint32)a; + a_high = a >> 32; + + b_low = (uint32)b; + b_high = b >> 32; + + rh = ((t_uint64) a_low) * b_low; + rm_a = ((t_uint64) a_low) * b_high; + rm_b = ((t_uint64) a_high) * b_low; + rl = ((t_uint64) a_high) * b_high; + + rm_a += rm_b; + + rl += (((t_uint64)(rm_a < rm_b)) << 32) + (rm_a >> 32); + rm_a <<= 32; + rh += rm_a; + rl += (rh < rm_a); + + *r_high = rh; + *r_low = rl; +} + +/* + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static void mul_64_by_shifted_32_to_128(t_uint64 a, uint32 b, t_mau_128 *result) +{ + t_uint64 mid; + + mid = (t_uint64)(uint32) a * b; + result->low = mid << 32; + result->high = (t_uint64)(uint32)(a >> 32) * b + (mid >> 32); +} + +/* + * Returns an approximation of the 64-bit integer value obtained by + * dividing 'b' into the 128-bit value 'a0' and 'a1'. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static t_uint64 estimate_div_128_to_64(t_uint64 a0, t_uint64 a1, t_uint64 b) +{ + t_uint64 b0, b1; + t_uint64 rem0, rem1, term0, term1; + t_uint64 z; + + if (b <= a0) { + return 0xffffffffffffffffull; + } + + b0 = b >> 32; + z = (b0 << 32 <= a0) ? 0xffffffff00000000ull : (a0 / b0) << 32; + + mul_64_to_128( b, z, &term0, &term1 ); + + sub_128( a0, a1, term0, term1, &rem0, &rem1 ); + + while (((int64_t)rem0) < 0) { + z -= 0x100000000ull; + b1 = b << 32; + add_128(rem0, rem1, b0, b1, &rem0, &rem1); + } + + rem0 = (rem0 << 32) | (rem1 >> 32); + z |= (b0<<32 <= rem0) ? 0xffffffff : rem0 / b0; + + return z; +} + +static uint32 approx_recip_sqrt_32(uint32 oddExpA, uint32 a) +{ + int index; + uint16 eps, r0; + uint32 ESqrR0; + uint32 sigma0; + uint32 r; + uint32 sqrSigma0; + + static const uint16 softfloat_approxRecipSqrt_1k0s[16] = { + 0xB4C9, 0xFFAB, 0xAA7D, 0xF11C, 0xA1C5, 0xE4C7, 0x9A43, 0xDA29, + 0x93B5, 0xD0E5, 0x8DED, 0xC8B7, 0x88C6, 0xC16D, 0x8424, 0xBAE1 + }; + static const uint16 softfloat_approxRecipSqrt_1k1s[16] = { + 0xA5A5, 0xEA42, 0x8C21, 0xC62D, 0x788F, 0xAA7F, 0x6928, 0x94B6, + 0x5CC7, 0x8335, 0x52A6, 0x74E2, 0x4A3E, 0x68FE, 0x432B, 0x5EFD + }; + + index = (a>>27 & 0xE) + oddExpA; + eps = (uint16) (a>>12); + r0 = softfloat_approxRecipSqrt_1k0s[index] + - ((softfloat_approxRecipSqrt_1k1s[index] * (uint32) eps) + >>20); + ESqrR0 = (uint32) r0 * r0; + if ( ! oddExpA ) ESqrR0 <<= 1; + sigma0 = ~(uint32) (((uint32) ESqrR0 * (t_uint64) a)>>23); + r = ((uint32) r0<<16) + ((r0 * (t_uint64) sigma0)>>25); + sqrSigma0 = ((t_uint64) sigma0 * sigma0)>>32; + r += ((uint32) ((r>>1) + (r>>3) - ((uint32) r0<<14)) + * (t_uint64) sqrSigma0) + >>48; + if ( ! (r & 0x80000000) ) r = 0x80000000; + return r; +} + +/* + * Return the properly rounded 32-bit integer corresponding to 'sign' + * and 'frac'. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static uint32 round_pack_int(t_bool sign, t_uint64 frac, RM rounding_mode) +{ + int8 round_increment, round_bits; + int32 result; + + round_increment = 0x40; + + if (!(rounding_mode == ROUND_NEAREST)) { + if (rounding_mode == ROUND_ZERO) { + round_increment = 0; + } else { + round_increment = 0x7f; + if (sign) { + if (rounding_mode == ROUND_PLUS_INF) { + round_increment = 0; + } + } else { + if (rounding_mode == ROUND_MINUS_INF) { + round_increment = 0; + } + } + } + } + + round_bits = frac & 0x7f; + frac = (frac + round_increment) >> 7; + frac &= ~((t_uint64)((round_bits ^ 0x40) == 0) & + (t_uint64)(rounding_mode == ROUND_NEAREST)); + + result = (int32)frac; + + if (sign) { + result = -result; + } + + if ((frac >> 32) || (result && ((result < 0) ^ sign))) { + mau_exc(MAU_ASR_IO, MAU_ASR_OM); /* Integer overflow */ + mau_exc(MAU_ASR_PS, MAU_ASR_PM); /* Inexact */ + return sign ? (int32) 0x80000000 : 0x7fffffff; + } + + if (round_bits) { + mau_exc(MAU_ASR_PS, MAU_ASR_PM); + } + + return result; +} + +/* + * Return the properly rounded 64-bit integer corresponding to 'sign' + * and 'frac'. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static t_int64 round_pack_int64(t_bool sign, + t_uint64 abs_0, t_uint64 abs_1, + RM rounding_mode) +{ + t_bool increment; + int64_t z; + + increment = (t_int64)abs_1 < 0; + + if (rounding_mode != ROUND_NEAREST) { + if (rounding_mode == ROUND_ZERO) { + increment = 0; + } else { + if (sign) { + increment = (rounding_mode == ROUND_MINUS_INF) && abs_1; + } else { + increment = (rounding_mode == ROUND_PLUS_INF) && abs_1; + } + } + } + + if (increment) { + ++abs_0; + if (abs_0 == 0) { + /* Overflow */ + mau_exc(MAU_ASR_OS, MAU_ASR_OM); + return sign ? 0x8000000000000000ull : 0x7fffffffffffffffull; + } + abs_0 &= ~((t_uint64)((abs_1 << 1) == 0) & + (t_uint64)(rounding_mode == ROUND_NEAREST)); + } + + z = abs_0; + if (sign) { + z = -z; + } + if (z && ((z < 0) ^ sign)) { + /* Overflow */ + mau_exc(MAU_ASR_OS, MAU_ASR_OM); + return sign ? 0x8000000000000000ull : 0x7fffffffffffffffull; + } + + if (abs_1) { + mau_exc(MAU_ASR_PS, MAU_ASR_PM); + } + + return z; +} + +/* + * Return a properly rounded 32-bit floating point value, given a sign + * bit, exponent, fractional part, and a rounding mode. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static SFP round_pack_sfp(t_bool sign, int16 exp, uint32 frac, RM rounding_mode) +{ + int8 round_increment, round_bits; + uint8 is_tiny; + + is_tiny = 0; + round_increment = 0x40; + + if (rounding_mode != ROUND_NEAREST) { + if (rounding_mode == ROUND_ZERO) { + round_increment = 0; + } else { + if (sign) { + if (rounding_mode == ROUND_PLUS_INF) { + round_increment = 0; + } + } else { + if (rounding_mode == ROUND_MINUS_INF) { + round_increment = 0; + } + } + } + } + + round_bits = frac & 0x7f; + + if (0xfd <= (uint16) exp) { + if ((0xfd < exp) || + (exp == 0xfd && (int32)(frac + round_increment) < 0)) { + mau_exc(MAU_ASR_OS, MAU_ASR_OM); + mau_exc(MAU_ASR_PS, MAU_ASR_PM); + return PACK_SFP(sign, 0xff, 0) - (round_increment == 0); + } + if (exp < 0) { + is_tiny = (TININESS_BEFORE_ROUNDING || + ((exp < -1) || + (frac + round_increment < 0x80000000))); + shift_right_32_jamming(frac, -exp, &frac); + exp = 0; + round_bits = frac & 0x7f; + if (is_tiny && round_bits) { + mau_exc(MAU_ASR_US, MAU_ASR_UM); + } + } + } + + if (round_bits) { + mau_exc(MAU_ASR_PS, MAU_ASR_PM); + } + + frac = (frac + round_increment) >> 7; + frac &= ~((t_uint64)((round_bits ^ 0x40) == 0) & + (t_uint64)(rounding_mode == ROUND_NEAREST)); + if (frac == 0) { + exp = 0; + } + + return PACK_SFP(sign, exp, frac); +} + +/* + * Return a properly rounded 64-bit floating point value, given a sign + * bit, exponent, fractional part, and a rounding mode. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static DFP round_pack_dfp(t_bool sign, int16 exp, t_uint64 frac, + t_bool xfp_sticky, RM rounding_mode) +{ + int16 round_increment, round_bits; + t_bool lsb, round, sticky; + uint8 is_tiny; + + is_tiny = 0; + round_increment = 0; + + if (rounding_mode != ROUND_NEAREST) { + if (rounding_mode == ROUND_ZERO) { + round_increment = 0; + } else { + round_increment = 0x7ff; + if (sign) { + if (rounding_mode == ROUND_PLUS_INF) { + round_increment = 0; + } + } else { + if (rounding_mode == ROUND_MINUS_INF) { + round_increment = 0; + } + } + } + } + + round_bits = frac & 0x7ff; + + if (0x7fd <= (uint16) exp) { + if (exp < 0) { + is_tiny = (TININESS_BEFORE_ROUNDING || + (exp < -1) || + ((frac + round_increment) < 0x8000000000000000ull)); + shift_right_64_jamming(frac, -exp, &frac); + exp = 0; + round_bits = frac & 0x7ff; + if (is_tiny && round_bits) { + mau_exc(MAU_ASR_US, MAU_ASR_UM); + } + } else if (0x7fd < exp) { + mau_exc(MAU_ASR_OS, MAU_ASR_OM); + mau_exc(MAU_ASR_PS, MAU_ASR_PM); + return (PACK_DFP(sign, 0x7ff, 0) - (round_increment == 0)); + } + } + + if (round_bits) { + mau_exc(MAU_ASR_PS, MAU_ASR_PM); + } + + if (rounding_mode == ROUND_NEAREST) { + frac >>= 11; + lsb = (frac & 1) != 0; + round = (round_bits & 0x400) != 0; + sticky = ((round_bits & 0x3ff) != 0) | xfp_sticky; + if (round & (sticky || lsb)) { + frac++; + if (frac == 0) { + exp++; + } + } + } else { + frac = (frac + round_increment) >> 11; + lsb = !((t_bool)(round_bits ^ 0x200)); + frac &= ~((t_uint64)lsb); + } + + return PACK_DFP(sign, exp, frac); +} + +/* + * Return a properly rounded 80-bit floating point value, given a sign + * bit, exponent, fractional part, and a rounding mode. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static void round_pack_xfp(t_bool sign, int32 exp, + t_uint64 frac_a, t_uint64 frac_b, + RM rounding_mode, XFP *result) +{ + uint8 is_tiny; + t_int64 round_mask; + + if (0x7ffd <= (uint32)(exp - 1)) { + if (0x7ffe < exp) { + round_mask = 0; + mau_exc(MAU_ASR_OS, MAU_ASR_OM); + mau_exc(MAU_ASR_PS, MAU_ASR_PM); + if ((rounding_mode == ROUND_ZERO) || + (sign && (rounding_mode == ROUND_PLUS_INF)) || + (!sign && (rounding_mode == ROUND_MINUS_INF))) { + PACK_XFP(sign, 0x7ffe, ~round_mask, result); + return; + } + PACK_XFP(sign, 0x7fff, 0x8000000000000000ull, result); + return; + } + if (exp <= 0) { + is_tiny = (TININESS_BEFORE_ROUNDING || + (exp < 0) || + (frac_a < 0xffffffffffffffffull)); + shift_right_extra_64_jamming(frac_a, frac_b, (int16)(1 - exp), &frac_a, &frac_b); + exp = 0; + if (is_tiny && frac_b) { + mau_exc(MAU_ASR_US, MAU_ASR_UM); + } + if (frac_b) { + mau_exc(MAU_ASR_PS, MAU_ASR_PM); + } + PACK_XFP(sign, exp, frac_a, result); + return; + } + } + if (frac_b) { + mau_exc(MAU_ASR_PS, MAU_ASR_PM); + } + if (frac_a == 0) { + exp = 0; + } + PACK_XFP_S(sign, exp, frac_a, frac_b, result); +} + +/* + * Given two 80-bit floating point values 'a' and 'b', one of which is + * a NaN, return the appropriate NaN result. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static void propagate_xfp_nan(XFP *a, XFP *b, XFP *result) +{ + uint8 a_is_nan, a_is_signaling_nan; + uint8 b_is_nan, b_is_signaling_nan; + + a_is_nan = XFP_IS_NAN(a); + a_is_signaling_nan = XFP_IS_TRAPPING_NAN(a); + b_is_nan = XFP_IS_NAN(b); + b_is_signaling_nan = XFP_IS_TRAPPING_NAN(b); + + a->frac |= 0xc000000000000000ull; + b->frac |= 0xc000000000000000ull; + + if (a_is_signaling_nan | b_is_signaling_nan) { + mau_exc(MAU_ASR_IS, MAU_ASR_IM); + } + + if (a_is_nan) { + if (a_is_signaling_nan & b_is_nan) { + result->sign_exp = b->sign_exp; + result->frac = b->frac; + } else { + result->sign_exp = a->sign_exp; + result->frac = a->frac; + } + } else { + result->sign_exp = b->sign_exp; + result->frac = b->frac; + } +} + +/* + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static void propagate_xfp_nan_128(XFP* a, XFP* b, t_mau_128* result) +{ + t_bool is_sig_nan_a, is_sig_nan_b; + t_uint64 non_frac_a_low, non_frac_b_low; + uint16 mag_a, mag_b; + + is_sig_nan_a = XFP_IS_TRAPPING_NAN(a); + is_sig_nan_b = XFP_IS_TRAPPING_NAN(b); + + non_frac_a_low = a->frac & 0xC000000000000000ull; + non_frac_b_low = b->frac & 0xC000000000000000ull; + + if (is_sig_nan_a | is_sig_nan_b) { + /* Invalid */ + mau_exc(MAU_ASR_IS, MAU_ASR_IM); + if (is_sig_nan_a) { + if (is_sig_nan_b) goto return_larger_mag; + if (XFP_IS_NAN(b)) goto return_b; + goto return_a; + } else { + if (XFP_IS_NAN(a)) goto return_a; + goto return_b; + } + } + + return_larger_mag: + mag_a = a->frac & 0x7fff; + mag_b = b->frac & 0x7fff; + if (mag_a < mag_b) goto return_b; + if (mag_b < mag_a) goto return_a; + if (a->frac < b->frac) goto return_b; + if (b->frac < a->frac) goto return_a; + if (a->sign_exp < b->sign_exp) goto return_a; + return_b: + result->high = b->sign_exp; + result->low = non_frac_b_low; + return; + return_a: + result->high = a->sign_exp; + result->low = non_frac_a_low; + return; +} + +/* + * Normalize and round an extended-precision floating point value. + * + * Partially derived from the SoftFloat 2c package (see copyright + * notice above) + */ +static void normalize_round_pack_xfp(t_bool sign, int32 exp, + t_uint64 frac_0, t_uint64 frac_1, + RM rounding_mode, XFP *result) +{ + int8 shift_count; + + if (frac_0 == 0) { + frac_0 = frac_1; + frac_1 = 0; + exp -= 64; + } + + shift_count = leading_zeros_64(frac_0); + short_shift_left_128(frac_0, frac_1, shift_count, &frac_0, &frac_1); + exp -= shift_count; + + round_pack_xfp(sign, exp, frac_0, frac_1, rounding_mode, result); +} + + +/* + * Normalize the subnormal 80-bit floating point value represented by + * the denormalized input fractional comonent. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static void normalize_sfp_subnormal(uint32 in_frac, int16 *out_exp, uint32 *out_frac) +{ + int8 shift_count; + + shift_count = leading_zeros(in_frac) - 8; + + if (shift_count < 0) { + /* There was invalid input, there's nothing we can do. */ + *out_frac = in_frac; + *out_exp = 0; + return; + } + + *out_frac = in_frac << shift_count; + *out_exp = (uint16)(1 - shift_count); +} + +/* + * Normalize the subnormal 64-bit floating point value represented by + * the denormalized input fractional comonent. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static void normalize_dfp_subnormal(t_uint64 in_frac, int16 *out_exp, t_uint64 *out_frac) +{ + int8 shift_count; + + shift_count = leading_zeros_64(in_frac) - 11; + + if (shift_count < 0) { + /* There was invalid input, there's nothing we can do. */ + *out_frac = in_frac; + *out_exp = 0; + return; + } + + *out_frac = in_frac << shift_count; + *out_exp = 1 - shift_count; +} + +/* + * Normalize the subnormal 32-bit floating point value represented by + * the denormalized input fractional comonent. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static void normalize_xfp_subnormal(t_uint64 in_frac, int32 *out_exp, t_uint64 *out_frac) +{ + int8 shift_count; + + shift_count = leading_zeros_64(in_frac); + if (shift_count < 64) { + *out_frac = in_frac << shift_count; + } else { + *out_frac = 0; + } + *out_exp = 1 - shift_count; +} + +/* + * Returns the result of converting the 32-bit floating point NaN + * value to the canonincal NaN format. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static T_NAN sfp_to_common_nan(SFP val) +{ + T_NAN nan = {0}; + + if (SFP_IS_TRAPPING_NAN(val)) { + mau_state.trapping_nan = TRUE; + } + + nan.sign = val >> 31; + nan.low = 0; + nan.high = ((t_uint64) val) << 41; + + return nan; +} + +/* + * Returns the result of converting the 64-bit floating point NaN + * value to the canonincal NaN format. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static T_NAN dfp_to_common_nan(DFP val) +{ + T_NAN nan = {0}; + + if (DFP_IS_TRAPPING_NAN(val)) { + mau_state.trapping_nan = TRUE; + } + + nan.sign = (val >> 63) & 1; + nan.low = 0; + nan.high = (t_uint64)(val << 12); + + return nan; +} + +/* + * Returns the result of converting the 80-bit floating point NaN + * value to the canonincal NaN format. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static T_NAN xfp_to_common_nan(XFP *val) +{ + T_NAN nan = {0}; + + if (XFP_IS_TRAPPING_NAN(val)) { + mau_state.trapping_nan = TRUE; + } + + nan.sign = val->sign_exp >> 15; + nan.low = 0; + nan.high = val->frac << 1; + + return nan; +} + +/* + * Returns the result of converting a canonical NAN format value to a + * 32-bit floating point format. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static SFP common_nan_to_sfp(T_NAN nan) +{ + return ((((uint32)nan.sign) << 31) + | 0x7fc00000 + | (nan.high >> 41)); +} + +/* + * Returns the result of converting a canonical NAN format value to a + * 64-bit floating point format. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static DFP common_nan_to_dfp(T_NAN nan) +{ + return ((((t_uint64)nan.sign) << 63) + | 0x7ff8000000000000ull + | (nan.high >> 12)); +} + +/* + * Returns the result of converting a canonical NAN format value to an + * 80-bit floating point format. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static void common_nan_to_xfp(T_NAN nan, XFP *result) +{ + result->frac = 0xc000000000000000ull | (nan.high >> 1); + result->sign_exp = (((uint16)nan.sign) << 15) | 0x7fff; +} + +/* + * Convert a 32-bit floating point value to an 80-bit floating point + * value. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static void sfp_to_xfp(SFP val, XFP *result) +{ + t_bool sign; + int16 exp; + uint32 frac; + + sign = SFP_SIGN(val); + exp = SFP_EXP(val); + frac = SFP_FRAC(val); + + if (exp == 0xff) { + if (frac) { + common_nan_to_xfp(sfp_to_common_nan(val), result); + return; + } + } + + if (exp == 0) { + if (frac == 0) { + PACK_XFP(sign, 0, 0, result); + return; + } + normalize_sfp_subnormal(frac, &exp, &frac); + } + + frac |= 0x800000; + + PACK_XFP(sign, exp + 0x3f80, ((t_uint64) frac) << 40, result); +} + +/* + * Convert a 64-bit floating point value to an 80-bit floating point value. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +void dfp_to_xfp(DFP val, XFP *result) +{ + t_bool sign; + int16 exp; + t_uint64 frac; + + sign = DFP_SIGN(val); + exp = DFP_EXP(val); + frac = DFP_FRAC(val); + + if (exp == 0x7ff) { + if (sign) { + common_nan_to_xfp(dfp_to_common_nan(val), result); + } + + PACK_XFP(sign, 0xff, 0, result); + return; + } + if (exp == 0) { + if (frac == 0) { + PACK_XFP(sign, 0, 0, result); + return; + } + normalize_dfp_subnormal(frac, &exp, &frac); + } + + PACK_XFP(sign, + exp + 0x3c00, + 0x8000000000000000ull | (frac << 11), + result); +} + +/* + * Convert an 80-bit floating point value to a 32-bit floating point + * value. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static SFP xfp_to_sfp(XFP *val, RM rounding_mode) +{ + t_bool sign; + int32 exp; + t_uint64 frac; + uint32 dst_frac; + + sign = XFP_SIGN(val); + exp = XFP_EXP(val); + frac = XFP_FRAC(val); + + if (exp == 0x7fff) { + if ((t_uint64)(frac << 1)) { + return common_nan_to_sfp(xfp_to_common_nan(val)); + } + return PACK_SFP(sign, 0xff, 0); + } + + shift_right_64_jamming(frac, 33, &frac); + + dst_frac = (uint32)frac; + + if (exp || frac) { + exp -= 0x3f81; + } + + return round_pack_sfp(sign, exp, dst_frac, rounding_mode); +} + +/* + * Convert an 80-bit floating point value to a 64-bit floating point + * value. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static DFP xfp_to_dfp(XFP *val, RM rounding_mode) +{ + t_bool sign; + int32 exp; + t_uint64 frac; + + sign = XFP_SIGN(val); + exp = XFP_EXP(val); + frac = XFP_FRAC(val); + + sim_debug(TRACE_DBG, &mau_dev, + "[xfp_to_dfp] input=%04x%016llx input_exp=%04x packed_exp=%04x\n", + val->sign_exp, val->frac, (uint16)exp, (uint16)(exp - 0x3c01)); + + if (exp == 0x7fff) { + if ((t_uint64)(frac << 1)) { + return common_nan_to_dfp(xfp_to_common_nan(val)); + } + return PACK_DFP(sign, 0x7ff, 0); + } + + if (exp || frac) { + exp -= 0x3c01; + } + + return round_pack_dfp(sign, exp, frac, val->s, rounding_mode); +} + +/***************************************************************************** + * Comparison Functions + ****************************************************************************/ + +/* + * Returns true if the two 80-bit floating point values are equal. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static uint32 xfp_eq(XFP *a, XFP *b) +{ + if (((XFP_EXP(a) == 0x7fff) && (t_uint64)(XFP_FRAC(a) << 1)) || + ((XFP_EXP(b) == 0x7fff) && (t_uint64)(XFP_FRAC(b) << 1))) { + + /* Check for NAN and raise invalid exception */ + if (XFP_IS_TRAPPING_NAN(a) || XFP_IS_TRAPPING_NAN(b)) { + mau_exc(MAU_ASR_IS, MAU_ASR_IM); + } + + return 0; + } + + return ((a->frac == b->frac) && + ((a->sign_exp == b->sign_exp) || + ((a->frac == 0) && ((uint16)((a->sign_exp|b->sign_exp) << 1) == 0)))); +} + +/* + * Returns true if the 80-bit floating point value 'a' is less than + * the 80-bit floating point value 'b'. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static uint32 xfp_lt(XFP *a, XFP *b) +{ + uint32 a_sign, b_sign; + + if (((XFP_EXP(a) == 0x7fff) && (t_uint64)(XFP_FRAC(a) << 1)) || + ((XFP_EXP(b) == 0x7fff) && (t_uint64)(XFP_FRAC(b) << 1))) { + return 0; + } + + a_sign = XFP_SIGN(a); + b_sign = XFP_SIGN(b); + + if (a_sign != b_sign) { + return(a_sign && + ((((uint16)((a->sign_exp|b->sign_exp) << 1)) | a->frac | b->frac) != 0)); + } + + if (a_sign) { + return (b->sign_exp < a->sign_exp) || ((b->sign_exp == a->sign_exp) && (b->frac < a->frac)); + } else { + return (a->sign_exp < b->sign_exp) || ((a->sign_exp == b->sign_exp) && (a->frac < b->frac)); + } +} + +/***************************************************************************** + * Conversion Functions + ****************************************************************************/ + +/* + * Convert a 32-bit signed integer value to an IEEE-754 extended + * precion (80-bit) floating point value. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +void mau_int_to_xfp(int32 val, XFP *result) +{ + int32 shift_width; + t_bool sign; + uint32 abs_val; + uint16 sign_exp = 0; + t_uint64 frac = 0; + + if (val) { + sign = (val < 0); + abs_val = (uint32)(sign ? -val : val); + shift_width = leading_zeros(abs_val); + sign_exp = (sign << 15) | (0x401e - shift_width); + frac = (t_uint64) (abs_val << shift_width) << 32; + } + + result->sign_exp = sign_exp; + result->frac = frac; + result->s = 0; + + if (sign_exp & 0x8000) { + mau_state.asr |= MAU_ASR_N; + } + + if ((sign_exp & 0x7fff) == 0 && frac == 0) { + mau_state.asr |= MAU_ASR_Z; + } +} + +/* + * Convert a floating point value to a 64-bit integer. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +t_int64 xfp_to_int64(XFP *val, RM rounding_mode) +{ + t_bool sign; + int32 exp, shift_count; + t_uint64 frac, frac_extra; + + sign = XFP_SIGN(val); + exp = XFP_EXP(val); + frac = XFP_FRAC(val); + shift_count = 0x403e - exp; + if (shift_count <= 0) { + if (shift_count) { + mau_exc(MAU_ASR_IS, MAU_ASR_IM); + if (!sign || ((exp == 0x7fff) && (frac != 0x8000000000000000ull))) { + return 0x7fffffffffffffffull; + } + return 0x8000000000000000ull; + } + frac_extra = 0; + } else { + shift_right_extra_64_jamming(frac, 0, shift_count, &frac, &frac_extra); + } + + return round_pack_int64(sign, frac, frac_extra, rounding_mode); +} + +void mau_int64_to_xfp(t_uint64 val, XFP *result) +{ + t_bool sign; + t_uint64 abs; + int8 shift_count; + + if (val == 0) { + PACK_XFP(0, 0, 0, result); + return; + } + + sign = (val & 0x8000000000000000ull) != 0ull; + abs = val & 0x7fffffffffffffffull; + shift_count = leading_zeros_64(abs); + if (shift_count < 64) { + abs = abs << shift_count; + } else { + abs = 0; + } + PACK_XFP(sign, 0x403e - shift_count, abs, result); +} + +/* + * Convert a float value to a decimal value. + */ +void xfp_to_decimal(XFP *a, DEC *d, RM rounding_mode) +{ + t_int64 tmp; + int i; + t_bool sign; + uint16 digits[19] = {0}; + + tmp = xfp_to_int64(a, rounding_mode); + + if (tmp < 0) { + sign = 0xb; + } else { + sign = 0xa; + } + + for (i = 0; i < 19; i++) { + digits[i] = tmp % 10; + tmp /= 10; + } + + d->l = sign; + d->l |= (t_uint64)digits[0] << 4; + d->l |= (t_uint64)digits[1] << 8; + d->l |= (t_uint64)digits[2] << 12; + d->l |= (t_uint64)digits[3] << 16; + d->l |= (t_uint64)digits[4] << 20; + d->l |= (t_uint64)digits[5] << 24; + d->l |= (t_uint64)digits[6] << 28; + d->l |= (t_uint64)digits[7] << 32; + d->l |= (t_uint64)digits[8] << 36; + d->l |= (t_uint64)digits[9] << 40; + d->l |= (t_uint64)digits[10] << 44; + d->l |= (t_uint64)digits[11] << 48; + d->l |= (t_uint64)digits[12] << 52; + d->l |= (t_uint64)digits[13] << 56; + d->l |= (t_uint64)digits[14] << 60; + d->h = (uint32)digits[15]; + d->h |= (uint32)digits[15] << 4; + d->h |= (uint32)digits[15] << 8; + + sim_debug(TRACE_DBG, &mau_dev, + "[xfp_to_decimal] " + "Digits: %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d 0x%x\n", + digits[17], digits[16], digits[15], digits[14], digits[13], digits[12], + digits[11], digits[10], digits[9], digits[8], digits[7], digits[6], + digits[5], digits[4], digits[3], digits[2], digits[1], digits[0], + sign); +} + +/* + * Convert a decimal value to a float value. + */ +void mau_decimal_to_xfp(DEC *d, XFP *a) +{ + int i; + t_bool sign; + uint16 digits[18] = {0}; + t_uint64 multiplier = 1; + t_uint64 tmp; + t_int64 signed_tmp; + + sim_debug(TRACE_DBG, &mau_dev, + "[mau_decimal_to_xfp] DEC input: %08x %08x %08x\n", + d->h, (uint32)(d->l >> 32), (uint32)(d->l)); + + sign = (d->l) & 15; + digits[0] = (d->l >> 4) & 15; + digits[1] = (d->l >> 8) & 15; + digits[2] = (d->l >> 12) & 15; + digits[3] = (d->l >> 16) & 15; + digits[4] = (d->l >> 20) & 15; + digits[5] = (d->l >> 24) & 15; + digits[6] = (d->l >> 28) & 15; + digits[7] = (d->l >> 32) & 15; + digits[8] = (d->l >> 36) & 15; + digits[9] = (d->l >> 40) & 15; + digits[10] = (d->l >> 44) & 15; + digits[11] = (d->l >> 48) & 15; + digits[12] = (d->l >> 52) & 15; + digits[13] = (d->l >> 56) & 15; + digits[14] = (d->l >> 60) & 15; + digits[15] = (d->h) & 15; + digits[16] = (d->h >> 4) & 15; + digits[17] = (d->h >> 8) & 15; + + sim_debug(TRACE_DBG, &mau_dev, + "[mau_decimal_to_xfp] " + "Digits: %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d 0x%x\n", + digits[17], digits[16], digits[15], digits[14], digits[13], digits[12], + digits[11], digits[10], digits[9], digits[8], digits[7], digits[6], + digits[5], digits[4], digits[3], digits[2], digits[1], digits[0], + sign); + + tmp = 0; + + for (i = 0; i < 18; i++) { + tmp += digits[i] * multiplier; + multiplier *= 10; + } + + switch (sign) { + case 0xd: + case 0xb: + /* Negative number */ + signed_tmp = -((t_int64) tmp); + break; + /* TODO: HANDLE NAN AND INFINITY */ + default: + signed_tmp = (t_int64) tmp; + } + + sim_debug(TRACE_DBG, &mau_dev, + "[mau_decimal_to_xfp] tmp val = %lld\n", + signed_tmp); + + mau_int64_to_xfp((t_uint64) signed_tmp, a); + + sim_debug(TRACE_DBG, &mau_dev, + "[mau_decimal_to_xfp] XFP = %04x%016llx\n", + a->sign_exp, a->frac); + +} + +/* + * Convert a floating point value to a 32-bit integer. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +uint32 xfp_to_int(XFP *val, RM rounding_mode) +{ + t_bool sign; + int32 exp, shift_count; + t_uint64 frac; + + sign = XFP_SIGN(val); + exp = XFP_EXP(val); + frac = XFP_FRAC(val); + + if ((exp == 0x7fff) && (t_uint64)(frac << 1)) { + sign = 0; + } + + shift_count = 0x4037 - exp; + + if (shift_count <= 0) { + shift_count = 1; + } + + shift_right_64_jamming(frac, shift_count, &frac); + + return round_pack_int(sign, frac, rounding_mode); +} + +/* + * Round an 80-bit extended precission floating-point value + * to an integer. + * + * Derived from the SoftFloat 2c library (see copyright notice above) + */ +void mau_round_xfp_to_int(XFP *val, XFP *result, RM rounding_mode) +{ + t_bool sign; + int32 exp; + t_uint64 last_bit_mask, round_bits_mask; + + exp = XFP_EXP(val); + + if (0x403e <= exp) { + if ((exp == 0x7fff) && (t_uint64)(XFP_FRAC(val) << 1)) { + propagate_xfp_nan(val, val, result); + return; + } + result->sign_exp = val->sign_exp; + result->frac = val->frac; + return; + } + if (exp < 0x3ff) { + if ((exp == 0) && ((t_uint64)(XFP_FRAC(val) << 1) == 0)) { + result->sign_exp = val->sign_exp; + result->frac = val->frac; + return; + } + mau_exc(MAU_ASR_PS, MAU_ASR_PM); + sign = XFP_SIGN(val); + switch (rounding_mode) { + case ROUND_NEAREST: + if (exp == 0x3ffe && (t_uint64)(XFP_FRAC(val) << 1)) { + PACK_XFP(sign, 0x3fff, 0x8000000000000000ull, result); + return; + } + break; + case ROUND_MINUS_INF: + if (sign) { + PACK_XFP(1, 0x3fff, 0x8000000000000000ull, result); + } else { + PACK_XFP(0, 0, 0, result); + } + return; + case ROUND_PLUS_INF: + if (sign) { + PACK_XFP(1, 0, 0, result); + } else { + PACK_XFP(0, 0x3fff, 0x8000000000000000ull, result); + } + return; + default: + /* Do nothing */ + break; + } + PACK_XFP(sign, 0, 0, result); + return; + } + + last_bit_mask = 1; + last_bit_mask <<= 0x403e - exp; + round_bits_mask = last_bit_mask - 1; + + result->sign_exp = val->sign_exp; + result->frac = val->frac; + + if (rounding_mode == ROUND_NEAREST) { + result->frac += last_bit_mask >> 1; + if ((result->frac & round_bits_mask) == 0) { + result->frac &= ~last_bit_mask; + } + } else if (rounding_mode != ROUND_ZERO) { + if (XFP_SIGN(result) ^ (rounding_mode == ROUND_PLUS_INF)) { + result->frac += round_bits_mask; + } + } + + result->frac &= ~round_bits_mask; + if (result->frac == 0) { + ++result->sign_exp; + result->frac = 0x8000000000000000ull; + } + + if (result->frac != val->frac) { + mau_exc(MAU_ASR_PS, MAU_ASR_PM); + } +} + +/***************************************************************************** + * Math Functions + ****************************************************************************/ + +/* + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static void xfp_add_fracs(XFP *a, XFP *b, t_bool sign, XFP *result, RM rounding_mode) +{ + int32 a_exp, b_exp, r_exp; + t_uint64 a_frac, b_frac, r_frac_0, r_frac_1; + int32 exp_diff; + + sim_debug(TRACE_DBG, &mau_dev, + "[ADD_FRACS] a=%04x%016llx b=%04x%016llx\n", + a->sign_exp, a->frac, + b->sign_exp, b->frac); + + a_exp = XFP_EXP(a); + a_frac = XFP_FRAC(a); + b_exp = XFP_EXP(b); + b_frac = XFP_FRAC(b); + + exp_diff = a_exp - b_exp; + if (0 < exp_diff) { + if (a_exp == 0x7fff) { + if ((t_uint64) (a_frac << 1)) { + propagate_xfp_nan(a, b, result); + return; + } + result->sign_exp = a->sign_exp; + result->frac = a->frac; + return; + } + if (b_exp == 0) { + --exp_diff; + } + shift_right_extra_64_jamming(b_frac, 0, exp_diff, &b_frac, &r_frac_1); + r_exp = a_exp; + } else if (exp_diff < 0) { + if (b_exp == 0x7fff) { + if ((t_uint64) (b_frac << 1)) { + propagate_xfp_nan(a, b, result); + return; + } + PACK_XFP(sign, 0x7fff, 0x8000000000000000ull, result); + return; + } + if (a_exp == 0) { + ++exp_diff; + } + + shift_right_extra_64_jamming(a_frac, 0, -exp_diff, &a_frac, &r_frac_1); + r_exp = b_exp; + } else { + if (a_exp == 0x7fff) { + if ((t_uint64)((a_frac | b_frac) << 1)) { + propagate_xfp_nan(a, b, result); + return; + } + result->sign_exp = a->sign_exp; + result->frac = a->frac; + return; + } + r_frac_1 = 0; + r_frac_0 = a_frac + b_frac; + if (a_exp == 0) { + normalize_xfp_subnormal(r_frac_0, &r_exp, &r_frac_0); + + round_pack_xfp(sign, r_exp, r_frac_0, r_frac_1, rounding_mode, result); + return; + } + r_exp = a_exp; + shift_right_extra_64_jamming(r_frac_0, r_frac_1, 1, &r_frac_0, &r_frac_1); + r_frac_0 |= 0x8000000000000000ull; + ++r_exp; + round_pack_xfp(sign, r_exp, r_frac_0, r_frac_1, rounding_mode, result); + return; + } + r_frac_0 = a_frac + b_frac; + if (((t_int64) r_frac_0) < 0) { + round_pack_xfp(sign, r_exp, r_frac_0, r_frac_1, rounding_mode, result); + return; + } + shift_right_extra_64_jamming(r_frac_0, r_frac_1, 1, &r_frac_0, &r_frac_1); + r_frac_0 |= 0x8000000000000000ull; + ++r_exp; + round_pack_xfp(sign, r_exp, r_frac_0, r_frac_1, rounding_mode, result); + return; +} + +/* + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static void xfp_sub_fracs(XFP *a, XFP *b, t_bool sign, XFP *result, RM rounding_mode) +{ + int32 a_exp, b_exp, r_exp; + t_uint64 a_frac, b_frac, r_frac_0, r_frac_1; + int32 exp_diff; + + a_exp = XFP_EXP(a); + a_frac = XFP_FRAC(a); + b_exp = XFP_EXP(b); + b_frac = XFP_FRAC(b); + exp_diff = a_exp - b_exp; + + if (0 < exp_diff) { + /* aExpBigger */ + if (a_exp == 0x7fff) { + if ((t_uint64)(a_frac << 1)) { + propagate_xfp_nan(a, b, result); + return; + } + result->sign_exp = a->sign_exp; + result->frac = a->frac; + return; + } + if (b_exp == 0) { + --exp_diff; + } + shift_right_128_jamming(b_frac, 0, exp_diff, &b_frac, &r_frac_1); + /* aBigger */ + sub_128(a_frac, 0, b_frac, r_frac_1, &r_frac_0, &r_frac_1); + r_exp = a_exp; + /* normalizeRoundAndPack */ + normalize_round_pack_xfp(sign, r_exp, r_frac_0, r_frac_1, rounding_mode, result); + return; + } + if (exp_diff < 0) { + /* bExpBigger */ + if (b_exp == 0x7fff) { + if ((t_uint64)(b_frac << 1)) { + propagate_xfp_nan(a, b, result); + return; + } + PACK_XFP(sign ? 0 : 1, 0x7fff, 0x8000000000000000ull, result); + return; + } + if (a_exp == 0) { + ++exp_diff; + } + shift_right_128_jamming(a_frac, 0, -exp_diff, &a_frac, &r_frac_1); + /* bBigger */ + sub_128(b_frac, 0, a_frac, r_frac_1, &r_frac_0, &r_frac_1); + r_exp = b_exp; + sign = sign ? 0 : 1; + /* normalizeRoundAndPack */ + normalize_round_pack_xfp(sign, r_exp, + r_frac_0, r_frac_1, + rounding_mode, result); + return; + } + if (a_exp == 0x7fff) { + if ((t_uint64)((a_frac | b_frac) << 1)) { + propagate_xfp_nan(a, b, result); + return; + } + mau_exc(MAU_ASR_IS, MAU_ASR_IM); /* Invalid */ + result->sign_exp = DEFAULT_XFP_NAN_SIGN_EXP; + result->frac = DEFAULT_XFP_NAN_FRAC; + return; + } + if (a_exp == 0) { + a_exp = 1; + b_exp = 1; + } + r_frac_1 = 0; + if (b_frac < a_frac) { + /* aBigger */ + sub_128(a_frac, 0, b_frac, r_frac_1, &r_frac_0, &r_frac_1); + r_exp = a_exp; + /* normalizeRoundAndPack */ + normalize_round_pack_xfp(sign, r_exp, + r_frac_0, r_frac_1, + rounding_mode, result); + return; + } + if (a_frac < b_frac) { + /* bBigger */ + sub_128(b_frac, 0, a_frac, r_frac_1, &r_frac_0, &r_frac_1); + r_exp = b_exp; + sign ^= 1; + + /* normalizeRoundAndPack */ + normalize_round_pack_xfp(sign, r_exp, + r_frac_0, r_frac_1, + rounding_mode, result); + return; + } + + PACK_XFP(rounding_mode == ROUND_MINUS_INF, 0, 0, result); +} + +/************************************************************************* + * + * MAU-specific functions + * + *************************************************************************/ + +/* + * Set condition flags based on comparison of the two values A and B. + * + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static void xfp_cmp(XFP *a, XFP *b) +{ + mau_state.asr &= ~(MAU_ASR_N|MAU_ASR_Z|MAU_ASR_UO); + + /* Page 5-9: + * + * "An invalid operation exception condition exists if either or + * both source operands are trapping NaNs. If the exception is + * masked then the UO flag would be set. However, if this + * exception is enabled, and, if Op1 is a trapping NaN, it is + * converted to double-extended precision and stored in DR. Else, + * Op2 (converted to double-extended precision, if necessary) is + * stored in DR." + */ + + if (XFP_IS_NAN(a) || XFP_IS_NAN(b)) { + if ((mau_state.asr & MAU_ASR_IM) == 0) { + mau_state.asr |= MAU_ASR_UO; + } else if (XFP_IS_NAN(a)) { + mau_state.dr.sign_exp = a->sign_exp; + mau_state.dr.frac = a->frac; + } else { + mau_state.dr.sign_exp = b->sign_exp; + mau_state.dr.frac = b->frac; + } + return; + } + + if (xfp_lt(a, b)) { + mau_state.asr |= MAU_ASR_N; + } + + if (xfp_eq(a, b)) { + mau_state.asr |= MAU_ASR_Z; + } +} + +static void xfp_cmpe(XFP *a, XFP *b) +{ + mau_state.asr &= ~(MAU_ASR_N|MAU_ASR_Z|MAU_ASR_UO); + + /* Page 5-10: + * + * "When two unordered values are compared, then, in additon to + * the response specified below, the invalid operation exception + * sticky flag (ASR = 1) is set and the trap invoked if the + * invalid operation exceptionis enabled."" + */ + + if ((XFP_IS_NAN(a) || XFP_IS_NAN(b)) && (mau_state.asr & MAU_ASR_IM)) { + mau_state.asr |= MAU_ASR_UO; + return; + } + + if (xfp_lt(a, b)) { + mau_state.asr |= MAU_ASR_N; + } + + if (xfp_eq(a, b)) { + mau_state.asr |= MAU_ASR_Z; + } +} + +static void xfp_cmps(XFP *a, XFP *b) +{ + mau_state.asr &= ~(MAU_ASR_N|MAU_ASR_Z|MAU_ASR_UO); + + if (XFP_IS_NAN(a) || XFP_IS_NAN(b)) { + if ((mau_state.asr & MAU_ASR_IM) == 0) { + mau_state.asr |= MAU_ASR_UO; + } else if (XFP_IS_NAN(a)) { + mau_state.dr.sign_exp = a->sign_exp; + mau_state.dr.frac = a->frac; + } else { + mau_state.dr.sign_exp = b->sign_exp; + mau_state.dr.frac = b->frac; + } + return; + } + + if (xfp_lt(a, b)) { + mau_state.asr |= MAU_ASR_Z; + } else if (xfp_eq(a, b)) { + mau_state.asr |= MAU_ASR_N; + } +} + +static void xfp_cmpes(XFP *a, XFP *b) +{ + mau_state.asr &= ~(MAU_ASR_N|MAU_ASR_Z|MAU_ASR_UO); + + if ((XFP_IS_NAN(a) || XFP_IS_NAN(b)) && (mau_state.asr & MAU_ASR_IM)) { + mau_state.asr |= MAU_ASR_UO; + return; + } + + if (xfp_lt(a, b)) { + mau_state.asr |= MAU_ASR_Z; + } + + if (xfp_eq(a, b)) { + mau_state.asr |= MAU_ASR_N; + } +} + +static void xfp_add(XFP *a, XFP *b, XFP *result, RM rounding_mode) +{ + uint32 a_sign, b_sign; + + a_sign = XFP_SIGN(a); + b_sign = XFP_SIGN(b); + + if (a_sign == b_sign) { + xfp_add_fracs(a, b, a_sign, result, rounding_mode); + } else { + xfp_sub_fracs(a, b, a_sign, result, rounding_mode); + } +} + +static void xfp_sub(XFP *a, XFP *b, XFP *result, RM rounding_mode) +{ + uint32 a_sign, b_sign; + + a_sign = XFP_SIGN(a); + b_sign = XFP_SIGN(b); + + if (a_sign == b_sign) { + xfp_sub_fracs(a, b, a_sign, result, rounding_mode); + } else { + xfp_add_fracs(a, b, a_sign, result, rounding_mode); + } +} + +/* + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static void xfp_mul(XFP *a, XFP *b, XFP *result, RM rounding_mode) +{ + uint32 a_sign, b_sign, r_sign; + int32 a_exp, b_exp, r_exp; + t_uint64 a_frac, b_frac, r_frac_0, r_frac_1; + + sim_debug(TRACE_DBG, &mau_dev, + "[MUL] op1=%04x%016llx op2=%04x%016llx\n", + a->sign_exp, a->frac, + b->sign_exp, b->frac); + + a_sign = XFP_SIGN(a); + a_exp = XFP_EXP(a); + a_frac = XFP_FRAC(a); + b_sign = XFP_SIGN(b); + b_exp = XFP_EXP(b); + b_frac = XFP_FRAC(b); + + r_sign = a_sign ^ b_sign; + + if (a_exp == 0x7fff) { + if ((t_uint64)(a_frac << 1) || ((b_exp == 0x7fff) && (t_uint64)(b_frac << 1))) { + propagate_xfp_nan(a, b, result); + return; + } + if ((b_exp | b_frac) == 0) { + /* invalid */ + mau_exc(MAU_ASR_IS, MAU_ASR_IM); + result->sign_exp = DEFAULT_XFP_NAN_SIGN_EXP; + result->frac = DEFAULT_XFP_NAN_FRAC; + return; + } + PACK_XFP(r_sign, 0x7fff, 0x8000000000000000ull, result); + return; + } + + if (b_exp == 0x7fff) { + if ((t_uint64)(b_frac << 1)) { + propagate_xfp_nan(a, b, result); + return; + } + if ((a_exp | a_frac) == 0) { + /* invalid */ + mau_exc(MAU_ASR_IS, MAU_ASR_IM); + result->sign_exp = DEFAULT_XFP_NAN_SIGN_EXP; + result->frac = DEFAULT_XFP_NAN_FRAC; + return; + } + PACK_XFP(r_sign, 0x7fff, 0x8000000000000000ull, result); + return; + } + + if (a_exp == 0) { + if (a_frac == 0) { + PACK_XFP(r_sign, 0, 0, result); + return; + } + normalize_xfp_subnormal(a_frac, &a_exp, &a_frac); + } + + if (b_exp == 0) { + if (b_frac == 0) { + PACK_XFP(r_sign, 0, 0, result); + return; + } + normalize_xfp_subnormal(b_frac, &b_exp, &b_frac); + } + + r_exp = a_exp + b_exp - 0x3ffe; + mul_64_to_128(a_frac, b_frac, &r_frac_0, &r_frac_1); + if (0 < (t_int64)r_frac_0) { + short_shift_left_128(r_frac_0, r_frac_1, 1, + &r_frac_0, &r_frac_1); + --r_exp; + } + + round_pack_xfp(r_sign, r_exp, r_frac_0, + r_frac_1, rounding_mode, result); +} + +/* + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static void xfp_div(XFP *a, XFP *b, XFP *result, RM rounding_mode) +{ + t_bool a_sign, b_sign, r_sign; + int32 a_exp, b_exp, r_exp; + t_uint64 a_frac, b_frac, r_frac0, r_frac1; + t_uint64 rem0, rem1, rem2, term0, term1, term2; + + sim_debug(TRACE_DBG, &mau_dev, + "[DIV] op1=%04x%016llx op2=%04x%016llx\n", + b->sign_exp, b->frac, a->sign_exp, a->frac); + + a_sign = XFP_SIGN(a); + a_exp = XFP_EXP(a); + a_frac = XFP_FRAC(a); + + b_sign = XFP_SIGN(b); + b_exp = XFP_EXP(b); + b_frac = XFP_FRAC(b); + + r_sign = a_sign ^ b_sign; + + if (a_exp == 0x7fff) { + if ((t_uint64)(a_frac << 1)) { + propagate_xfp_nan(a, b, result); + return; + } + + if (b_exp == 0x7fff) { + if ((t_uint64)(b_frac << 1)) { + propagate_xfp_nan(a, b, result); + return; + } + /* Invalid */ + mau_exc(MAU_ASR_IS, MAU_ASR_IM); + result->sign_exp = DEFAULT_XFP_NAN_SIGN_EXP; + result->frac = DEFAULT_XFP_NAN_FRAC; + return; + } + + PACK_XFP(r_sign, 0x7fff, 0x8000000000000000ull, result); + return; + } + + if (b_exp == 0x7fff) { + if ((t_uint64) (b_frac << 1)) { + propagate_xfp_nan(a, b, result); + return; + } + + PACK_XFP(r_sign, 0, 0, result); + return; + } + + if (b_exp == 0) { + if (b_frac == 0) { + if ((a_exp | b_frac) == 0) { + /* Invalid */ + mau_exc(MAU_ASR_IS, MAU_ASR_IM); + result->sign_exp = DEFAULT_XFP_NAN_SIGN_EXP; + result->frac = DEFAULT_XFP_NAN_FRAC; + return; + } + /* Divide by zero - SPECIAL CASE 4 */ + sim_debug(TRACE_DBG, &mau_dev, + "[DIV] Divide by zero detected.\n"); + mau_case_div_zero(a, b, result); + return; + } + normalize_xfp_subnormal(b_frac, &b_exp, &b_frac); + } + + if (a_exp == 0) { + if (a_frac == 0) { + PACK_XFP(r_sign, 0, 0, result); + return; + } + normalize_xfp_subnormal(a_frac, &a_exp, &a_frac); + } + + r_exp = a_exp - b_exp + 0x3ffe; + rem1 = 0; + if (b_frac <= a_frac) { + shift_right_128(a_frac, 0, 1, &a_frac, &rem1); + ++r_exp; + } + + r_frac0 = estimate_div_128_to_64(a_frac, rem1, b_frac); + mul_64_to_128(b_frac, r_frac0, &term0, &term1); + sub_128(a_frac, rem1, term0, term1, &rem0, &rem1); + + while ((t_int64) rem0 < 0) { + --r_frac0; + add_128(rem0, rem1, 0, b_frac, &rem0, &rem1); + } + + r_frac1 = estimate_div_128_to_64(rem1, 0, b_frac); + if ((t_uint64)(r_frac1 << 1) <= 8) { + mul_64_to_128(b_frac, r_frac1, &term1, &term2); + sub_128(rem1, 0, term1, term2, &rem1, &rem2); + while ((t_int64) rem1 < 0) { + --r_frac1; + add_128(rem1, rem2, 0, b_frac, &rem1, &rem2); + } + r_frac1 |= ((rem1 | rem2) != 0); + } + + round_pack_xfp(r_sign, r_exp, r_frac0, r_frac1, rounding_mode, result); +} + +/* + * Derived from the SoftFloat 2c package (see copyright notice above) + */ +static void xfp_sqrt(XFP *a, XFP *result, RM rounding_mode) +{ + XFP zero = {0, 0, 0}; + t_bool a_sign; + int32 a_exp, norm_exp, r_exp; + uint32 a_frac_32, sqrt_recip_32, r_frac_32; + t_uint64 a_frac, norm_frac, q, x64, z_frac, z_frac_extra; + t_mau_128 nan_128, rem, y, term; + + sim_debug(TRACE_DBG, &mau_dev, + "[SQRT] op1=%04x%016llx\n", + a->sign_exp, a->frac); + + a_sign = XFP_SIGN(a); + a_exp = XFP_EXP(a); + a_frac = XFP_FRAC(a); + + if (a_exp == 0x7fff) { + if ( a_frac & 0x7fffffffffffffffull ) { + propagate_xfp_nan_128(a, &zero, &nan_128); + result->sign_exp = (uint32) nan_128.high; + result->frac = nan_128.low; + return; + } + if ( ! a_sign ) { + result->sign_exp = a->sign_exp; + result->frac = a->frac; + } + /* Invalid */ + mau_exc(MAU_ASR_IS, MAU_ASR_IM); + result->sign_exp = DEFAULT_XFP_NAN_SIGN_EXP; + result->frac = DEFAULT_XFP_NAN_FRAC; + return; + } + + if (a_sign) { + if (!a_frac) { + PACK_XFP(a_sign, 0, 0, result); + return; + } + mau_exc(MAU_ASR_IS, MAU_ASR_IM); + result->sign_exp = DEFAULT_XFP_NAN_SIGN_EXP; + result->frac = DEFAULT_XFP_NAN_FRAC; + return; + } + + if (!a_exp) { + a_exp = 1; + } + + if (!(a_frac & 0x8000000000000000ull)) { + if (!a_frac) { + PACK_XFP(a_sign, 0, 0, result); + return; + } + normalize_xfp_subnormal(a->frac, &norm_exp, &norm_frac); + a_exp += norm_exp; + a_frac = norm_frac; + } + + /* + * r_frac_32 is guaranteed to be a lower bound on the square root of + * a_frac_32, which makes r_frac_32 also a lower bound on the square + * root of `a_frac'. + */ + r_exp = ((a_exp - 0x3FFF) >> 1) + 0x3FFF; + a_exp &= 1; + a_frac_32 = a_frac >> 32; + sqrt_recip_32 = approx_recip_sqrt_32(a_exp, a_frac_32); + r_frac_32 = ((t_uint64) a_frac_32 * sqrt_recip_32) >> 32; + + if (a_exp) { + r_frac_32 >>= 1; + short_shift_left_128(0, a_frac, 61, &rem.high, &rem.low); + } else { + short_shift_left_128(0, a_frac, 62, &rem.high, &rem.low); + } + + rem.high -= (t_uint64) r_frac_32 * r_frac_32; + + q = ((uint32) (rem.high >> 2) * (t_uint64) sqrt_recip_32) >> 32; + x64 = (t_uint64) r_frac_32 << 32; + z_frac = x64 + (q<<3); + short_shift_left_128(rem.high, rem.low, 29, &y.high, &y.low); + + /* Repeating this loop is a rare occurrence. */ + while(1) { + mul_64_by_shifted_32_to_128(x64 + z_frac, (uint32) q, &term); + sub_128(y.high, y.low, term.high, term.low, &rem.high, &rem.low); + if (!(rem.high & 0x8000000000000000ull)) { + break; + } + --q; + z_frac -= 1<<3; + } + + q = (((rem.high>>2) * sqrt_recip_32)>>32) + 2; + x64 = z_frac; + z_frac = (z_frac<<1) + (q>>25); + z_frac_extra = (t_uint64) (q<<39); + + if ( (q & 0xffffff) <= 2 ) { + q &= ~(t_uint64) 0xffff; + z_frac_extra = (t_uint64) (q<<39); + mul_64_by_shifted_32_to_128(x64 + (q >> 27), (uint32) q, &term); + x64 = (uint32) (q<<5) * (t_uint64) (uint32) q; + add_128(term.high, term.low, 0, x64, &term.high, &term.low); + short_shift_left_128(rem.high, rem.low, 28, &rem.high, &rem.low); + sub_128(rem.high, rem.low, term.high, term.low, &rem.high, &rem.low); + if (rem.high & 0x8000000000000000ull) { + if (!z_frac_extra ) { + --z_frac; + } + --z_frac_extra; + } else { + if (rem.high | rem.low) { + z_frac_extra |= 1; + } + } + } + + round_pack_xfp(0, r_exp, z_frac, z_frac_extra, rounding_mode,result); + return; +} + +static void xfp_remainder(XFP *a, XFP *b, XFP *result, RM rounding_mode) +{ + uint32 a_sign, r_sign; + int32 a_exp, b_exp, exp_diff; + t_uint64 a_frac_0, a_frac_1, b_frac; + t_uint64 q, term_0, term_1, alt_a_frac_0, alt_a_frac_1; + + a_sign = XFP_SIGN(a); + a_exp = XFP_EXP(a); + a_frac_0 = XFP_FRAC(a); + b_exp = XFP_EXP(b); + b_frac = XFP_FRAC(b); + + if (a_exp == 0x7fff) { + if ((t_uint64)(a_frac_0 << 1) || + ((b_exp == 0x7fff) && (t_uint64)(b_frac << 1))) { + propagate_xfp_nan(a, b, result); + return; + } + /* invalid */ + mau_exc(MAU_ASR_IS, MAU_ASR_IM); + result->sign_exp = DEFAULT_XFP_NAN_SIGN_EXP; + result->frac = DEFAULT_XFP_NAN_FRAC; + return; + } + + if (b_exp == 0x7fff) { + if ((t_uint64)(b_frac << 1)) { + propagate_xfp_nan(a, b, result); + } + result->sign_exp = a->sign_exp; + result->frac = a->frac; + return; + } + + if (b_exp == 0) { + if (b_frac == 0) { + /* invalid */ + mau_exc(MAU_ASR_IS, MAU_ASR_IM); + result->sign_exp = DEFAULT_XFP_NAN_SIGN_EXP; + result->frac = DEFAULT_XFP_NAN_FRAC; + return; + } + normalize_xfp_subnormal(b_frac, &b_exp, &b_frac); + } + + if (a_exp == 0) { + if ((t_uint64)(a_frac_0 << 1) == 0) { + result->sign_exp = a->sign_exp; + result->frac = a->frac; + return; + } + normalize_xfp_subnormal(a_frac_0, &a_exp, &a_frac_0); + } + + b_frac |= 0x8000000000000000ull; + r_sign = a_sign; + exp_diff = a_exp - b_exp; + a_frac_1 = 0; + if (exp_diff < 0) { + if (exp_diff < -1) { + result->sign_exp = a->sign_exp; + result->frac = a->frac; + return; + } + shift_right_128(a_frac_0, 0, 1, &a_frac_0, &a_frac_1); + exp_diff = 0; + } + + q = (b_frac <= a_frac_0); + + if (q) { + a_frac_0 -= b_frac; + } + + exp_diff -= 64; + + while (0 < exp_diff) { + q = estimate_div_128_to_64(a_frac_0, a_frac_1, b_frac); + q = (2 < q) ? q - 2 : 0; + mul_64_to_128(b_frac, q, &term_0, &term_1); + sub_128(a_frac_0, a_frac_1, term_0, term_1, &a_frac_0, &a_frac_1); + short_shift_left_128(a_frac_0, a_frac_1, 62, &a_frac_0, &a_frac_1); + exp_diff -= 62; + } + + exp_diff += 64; + + if (0 < exp_diff) { + q = estimate_div_128_to_64(a_frac_0, a_frac_1, b_frac); + q = (2 < q) ? q - 2 : 0; + q >>= 64 - exp_diff; + mul_64_to_128(b_frac, q << (64 - exp_diff), &term_0, &term_1); + sub_128(a_frac_0, a_frac_1, term_0, term_1, &a_frac_0, &a_frac_1); + short_shift_left_128(0, b_frac, 64 - exp_diff, &term_0, &term_1); + while (le_128(term_0, term_1, a_frac_0, a_frac_1)) { + ++q; + sub_128(a_frac_0, a_frac_1, term_0, term_1, &a_frac_0, &a_frac_1); + } + } else { + term_0 = b_frac; + term_1 = 0; + } + + sub_128(term_0, term_1, a_frac_0, a_frac_1, &alt_a_frac_0, &alt_a_frac_1); + + if (lt_128(alt_a_frac_0, alt_a_frac_1, a_frac_0, a_frac_1) || + (eq_128(alt_a_frac_0, alt_a_frac_1, a_frac_0, a_frac_1) && + (q & 1))) { + a_frac_0 = alt_a_frac_0; + a_frac_1 = alt_a_frac_1; + r_sign = r_sign ? 0 : 1; + } + + normalize_round_pack_xfp(r_sign, b_exp + exp_diff, + a_frac_0, a_frac_1, + rounding_mode, result); +} + +/* + * Load an extended precision 80-bit IEE-754 floating point value from + * memory or register, based on the operand's specification. + */ +static void load_src_op(uint8 op, XFP *xfp) +{ + DFP dfp; + SFP sfp; + + switch (op) { + case M_OP_F0: + xfp->sign_exp = mau_state.f0.sign_exp; + xfp->frac = mau_state.f0.frac; + break; + case M_OP_F1: + xfp->sign_exp = mau_state.f1.sign_exp; + xfp->frac = mau_state.f1.frac; + break; + case M_OP_F2: + xfp->sign_exp = mau_state.f2.sign_exp; + xfp->frac = mau_state.f2.frac; + break; + case M_OP_F3: + xfp->sign_exp = mau_state.f3.sign_exp; + xfp->frac = mau_state.f3.frac; + break; + case M_OP_MEM_SINGLE: + sfp = read_w(mau_state.src, ACC_AF, BUS_PER); + sfp_to_xfp(sfp, xfp); + break; + case M_OP_MEM_DOUBLE: + dfp = (t_uint64) read_w(mau_state.src + 4, ACC_AF, BUS_PER); + dfp |= ((t_uint64) read_w(mau_state.src, ACC_AF, BUS_PER)) << 32; + sim_debug(TRACE_DBG, &mau_dev, + "[load_src_op][DOUBLE] Loaded %016llx\n", + dfp); + dfp_to_xfp(dfp, xfp); + sim_debug(TRACE_DBG, &mau_dev, + "[load_src_op][DOUBLE] Expanded To %04x%016llx\n", + xfp->sign_exp, xfp->frac); + break; + case M_OP_MEM_TRIPLE: + xfp->frac = (t_uint64) read_w(mau_state.src + 8, ACC_AF, BUS_PER); + xfp->frac |= ((t_uint64) read_w(mau_state.src + 4, ACC_AF, BUS_PER)) << 32; + xfp->sign_exp = (uint32) read_w(mau_state.src, ACC_AF, BUS_PER); + break; + default: + break; + } +} + +/* + * Load OP1 as a DEC value. + */ +static void load_op1_decimal(DEC *d) +{ + uint32 low, mid, high; + + switch (mau_state.op1) { + case M_OP_MEM_TRIPLE: + low = read_w(mau_state.src + 8, ACC_AF, BUS_PER); + mid = read_w(mau_state.src + 4, ACC_AF, BUS_PER); + high = read_w(mau_state.src, ACC_AF, BUS_PER); + d->l = low; + d->l |= ((t_uint64) mid << 32); + d->h = high; + break; + default: + /* Invalid */ + mau_exc(MAU_ASR_IS, MAU_ASR_IM); + break; + } +} + +static void store_op3_int(uint32 val) +{ + switch(mau_state.op3) { + case M_OP3_F0_SINGLE: + mau_state.f0.sign_exp = 0; + mau_state.f0.frac = (t_uint64)val; + break; + case M_OP3_F1_SINGLE: + mau_state.f1.sign_exp = 0; + mau_state.f1.frac = (t_uint64)val; + break; + case M_OP3_F2_SINGLE: + mau_state.f2.sign_exp = 0; + mau_state.f2.frac = (t_uint64)val; + break; + case M_OP3_F3_SINGLE: + mau_state.f3.sign_exp = 0; + mau_state.f3.frac = (t_uint64)val; + break; + case M_OP3_MEM_SINGLE: + write_w(mau_state.dst, val, BUS_PER); + break; + default: + /* Indeterminate output, unsupported */ + break; + } + + mau_state.dr.sign_exp = 0; + mau_state.dr.frac = (t_uint64)val; +} + +static void store_op3_decimal(DEC *d) +{ + + switch(mau_state.op3) { + case M_OP3_MEM_TRIPLE: + write_w(mau_state.dst, d->h, BUS_PER); + write_w(mau_state.dst + 4, (uint32)((t_uint64)d->l >> 32), BUS_PER); + write_w(mau_state.dst + 8, (uint32)d->l, BUS_PER); + break; + default: + /* Unsupported */ + return; + } + + mau_state.dr.sign_exp = d->h; + mau_state.dr.frac = ((t_uint64)d->l >> 32) | (t_uint64)d->l; +} + +static void store_op3_reg(XFP *xfp, XFP *reg) +{ + DFP dfp; + SFP sfp; + XFP xfp_r; + + if (mau_state.ntnan) { + reg->sign_exp = GEN_NONTRAPPING_NAN.sign_exp; + reg->frac = GEN_NONTRAPPING_NAN.frac; + } else { + switch(mau_state.op3) { + case M_OP3_F0_SINGLE: + case M_OP3_F1_SINGLE: + case M_OP3_F2_SINGLE: + case M_OP3_F3_SINGLE: + sfp = xfp_to_sfp(xfp, MAU_RM); + sfp_to_xfp(sfp, &xfp_r); + reg->sign_exp = xfp_r.sign_exp; + reg->frac = xfp_r.frac; + reg->s = xfp_r.s; + break; + case M_OP3_F0_DOUBLE: + case M_OP3_F1_DOUBLE: + case M_OP3_F2_DOUBLE: + case M_OP3_F3_DOUBLE: + dfp = xfp_to_dfp(xfp, MAU_RM); + dfp_to_xfp(dfp, &xfp_r); + reg->sign_exp = xfp_r.sign_exp; + reg->frac = xfp_r.frac; + reg->s = xfp_r.s; + break; + case M_OP3_F0_TRIPLE: + case M_OP3_F1_TRIPLE: + case M_OP3_F2_TRIPLE: + case M_OP3_F3_TRIPLE: + reg->sign_exp = xfp->sign_exp; + reg->frac = xfp->frac; + reg->s = xfp->s; + break; + } + } + if (set_nz()) { + if (XFP_SIGN(xfp)) { + mau_state.asr |= MAU_ASR_N; + } + if (XFP_EXP(xfp) == 0 && XFP_FRAC(xfp) == 0) { + mau_state.asr |= MAU_ASR_Z; + } + } +} + +static void store_op3(XFP *xfp) +{ + DFP dfp; + SFP sfp; + t_bool store_dr = FALSE; + + sim_debug(TRACE_DBG, &mau_dev, + "[store_op3] op3=%04x%016llx\n", + xfp->sign_exp, + xfp->frac); + + switch (mau_state.opcode) { + case M_ADD: + case M_SUB: + case M_MUL: + case M_DIV: + store_dr = TRUE; + break; + default: + break; + } + + switch (mau_state.op3) { + case M_OP3_F0_SINGLE: + case M_OP3_F0_DOUBLE: + case M_OP3_F0_TRIPLE: + store_op3_reg(xfp, &mau_state.f0); + break; + case M_OP3_F1_SINGLE: + case M_OP3_F1_DOUBLE: + case M_OP3_F1_TRIPLE: + store_op3_reg(xfp, &mau_state.f1); + break; + case M_OP3_F2_SINGLE: + case M_OP3_F2_DOUBLE: + case M_OP3_F2_TRIPLE: + store_op3_reg(xfp, &mau_state.f2); + break; + case M_OP3_F3_SINGLE: + case M_OP3_F3_DOUBLE: + case M_OP3_F3_TRIPLE: + store_op3_reg(xfp, &mau_state.f3); + break; + case M_OP3_MEM_SINGLE: + if (mau_state.ntnan) { + sfp = xfp_to_sfp(&GEN_NONTRAPPING_NAN, MAU_RM); + } else { + sfp = xfp_to_sfp(xfp, MAU_RM); + } + if (set_nz()) { + if (SFP_SIGN(sfp)) { + mau_state.asr |= MAU_ASR_N; + } + if (SFP_EXP(sfp) == 0 && SFP_FRAC(sfp) == 0) { + mau_state.asr |= MAU_ASR_Z; + } + } + write_w(mau_state.dst, (uint32)sfp, BUS_PER); + break; + case M_OP3_MEM_DOUBLE: + if (mau_state.ntnan) { + dfp = xfp_to_dfp(&GEN_NONTRAPPING_NAN, MAU_RM); + } else { + dfp = xfp_to_dfp(xfp, MAU_RM); + } + if (store_dr) { + mau_state.dr.sign_exp = ((uint16)(DFP_SIGN(dfp)) << 15) | (uint16)(DFP_EXP(dfp)); + mau_state.dr.frac = (t_uint64)(DFP_FRAC(dfp)); + if (DFP_EXP(dfp)) { + /* If the number is normalized, add the implicit + normalized bit 52 */ + mau_state.dr.frac |= ((t_uint64)1 << 52); + } + } + if (set_nz()) { + if (DFP_SIGN(dfp)) { + mau_state.asr |= MAU_ASR_N; + } + if (DFP_EXP(dfp) == 0 && DFP_FRAC(dfp) == 0) { + mau_state.asr |= MAU_ASR_Z; + } + } + write_w(mau_state.dst, (uint32)(dfp >> 32), BUS_PER); + write_w(mau_state.dst + 4, (uint32)(dfp), BUS_PER); + break; + case M_OP3_MEM_TRIPLE: + if (mau_state.ntnan) { + write_w(mau_state.dst, (uint32)(GEN_NONTRAPPING_NAN.sign_exp), BUS_PER); + write_w(mau_state.dst + 4, (uint32)(GEN_NONTRAPPING_NAN.frac >> 32), BUS_PER); + write_w(mau_state.dst + 8, (uint32)(GEN_NONTRAPPING_NAN.frac), BUS_PER); + } else { + write_w(mau_state.dst, (uint32)(xfp->sign_exp), BUS_PER); + write_w(mau_state.dst + 4, (uint32)(xfp->frac >> 32), BUS_PER); + write_w(mau_state.dst + 8, (uint32)(xfp->frac), BUS_PER); + } + if (set_nz()) { + if (XFP_SIGN(xfp)) { + mau_state.asr |= MAU_ASR_N; + } + if (XFP_EXP(xfp) == 0 && XFP_FRAC(xfp) == 0) { + mau_state.asr |= MAU_ASR_Z; + } + } + break; + default: + sim_debug(TRACE_DBG, &mau_dev, + "[store_op3] WARNING: Unhandled destination: %02x\n", mau_state.op3); + break; + } +} + +/************************************************************************* + * + * MAU instruction impelementations + * + *************************************************************************/ + +static void mau_rdasr() +{ + switch (mau_state.op3) { + /* Handled */ + case M_OP3_MEM_SINGLE: + write_w(mau_state.dst, mau_state.asr, BUS_PER); + break; + case M_OP3_MEM_DOUBLE: + write_w(mau_state.dst, mau_state.asr, BUS_PER); + write_w(mau_state.dst + 4, mau_state.asr, BUS_PER); + break; + case M_OP3_MEM_TRIPLE: + write_w(mau_state.dst, mau_state.asr, BUS_PER); + write_w(mau_state.dst + 4, mau_state.asr, BUS_PER); + write_w(mau_state.dst + 8, mau_state.asr, BUS_PER); + break; + /* Unhandled */ + default: + sim_debug(TRACE_DBG, &mau_dev, + "[mau_rdasr] WARNING: Unhandled source: %02x\n", + mau_state.op3); + break; + } +} + +static void mau_wrasr() +{ + switch (mau_state.op1) { + /* Handled */ + case M_OP_MEM_SINGLE: + mau_state.asr = read_w(mau_state.src, ACC_AF, BUS_PER); + sim_debug(TRACE_DBG, &mau_dev, + "[WRASR] Writing ASR with: %08x\n", + mau_state.asr); + break; + default: + sim_debug(TRACE_DBG, &mau_dev, + "[mau_wrasr] WARNING: Unhandled source: %02x\n", + mau_state.op3); + break; + } +} + +/* + * OP3 = OP1 + */ +static void mau_move() +{ + XFP xfp = {0}; + + load_src_op(mau_state.op1, &xfp); + store_op3(&xfp); +} + +static void mau_cmp() +{ + XFP a, b; + + load_src_op(mau_state.op1, &a); + load_src_op(mau_state.op2, &b); + xfp_cmp(&a, &b); +} + +static void mau_cmps() +{ + XFP a, b; + + load_src_op(mau_state.op1, &a); + load_src_op(mau_state.op2, &b); + xfp_cmps(&a, &b); +} + +static void mau_cmpe() +{ + XFP a, b; + + load_src_op(mau_state.op1, &a); + load_src_op(mau_state.op2, &b); + xfp_cmpe(&a, &b); +} + +static void mau_cmpes() +{ + XFP a, b; + + load_src_op(mau_state.op1, &a); + load_src_op(mau_state.op2, &b); + xfp_cmpes(&a, &b); +} + +static void mau_ldr() +{ + XFP xfp; + + load_src_op(mau_state.op1, &xfp); + sim_debug(TRACE_DBG, &mau_dev, + "[LDR] Loading DR with %04x%016llx\n", + xfp.sign_exp, xfp.frac); + mau_state.dr.sign_exp = xfp.sign_exp; + mau_state.dr.frac = xfp.frac; +} + +static void mau_erof() +{ + DFP dfp; + SFP sfp; + + switch (mau_state.op3) { + case M_OP3_F0_SINGLE: + case M_OP3_F0_DOUBLE: + case M_OP3_F0_TRIPLE: + mau_state.f0.sign_exp = mau_state.dr.sign_exp; + mau_state.f0.frac = mau_state.dr.frac; + return; + case M_OP3_F1_SINGLE: + case M_OP3_F1_DOUBLE: + case M_OP3_F1_TRIPLE: + mau_state.f1.sign_exp = mau_state.dr.sign_exp; + mau_state.f1.frac = mau_state.dr.frac; + return; + case M_OP3_F2_SINGLE: + case M_OP3_F2_DOUBLE: + case M_OP3_F2_TRIPLE: + mau_state.f2.sign_exp = mau_state.dr.sign_exp; + mau_state.f2.frac = mau_state.dr.frac; + return; + case M_OP3_F3_SINGLE: + case M_OP3_F3_DOUBLE: + case M_OP3_F3_TRIPLE: + mau_state.f3.sign_exp = mau_state.dr.sign_exp; + mau_state.f3.frac = mau_state.dr.frac; + return; + case M_OP3_MEM_SINGLE: + sfp = xfp_to_sfp(&(mau_state.dr), MAU_RM); + write_w(mau_state.dst, (uint32)sfp, BUS_PER); + return; + case M_OP3_MEM_DOUBLE: + dfp = xfp_to_dfp(&(mau_state.dr), MAU_RM); + write_w(mau_state.dst + 4, (uint32)(dfp >> 32), BUS_PER); + write_w(mau_state.dst, (uint32)(dfp), BUS_PER); + return; + case M_OP3_MEM_TRIPLE: + write_w(mau_state.dst, (uint32)(mau_state.dr.sign_exp), BUS_PER); + write_w(mau_state.dst + 4, (uint32)(mau_state.dr.frac >> 32), BUS_PER); + write_w(mau_state.dst + 8, (uint32)(mau_state.dr.frac), BUS_PER); + return; + default: + sim_debug(TRACE_DBG, &mau_dev, + "[mau_erof] WARNING: Unhandled destination: %02x\n", mau_state.op3); + return; + } +} + + +static void mau_rtoi() +{ + XFP a, result; + + load_src_op(mau_state.op1, &a); + mau_round_xfp_to_int(&a, &result, MAU_RM); + store_op3(&result); +} + +static void mau_ftoi() +{ + XFP a; + uint32 result; + + load_src_op(mau_state.op1, &a); + result = xfp_to_int(&a, MAU_RM); + store_op3_int(result); +} + +static void mau_dtof() +{ + DEC d; + XFP result; + + load_op1_decimal(&d); + mau_decimal_to_xfp(&d, &result); + store_op3(&result); +} + +static void mau_ftod() +{ + XFP a; + DEC d; + + load_src_op(mau_state.op1, &a); + xfp_to_decimal(&a, &d, MAU_RM); + store_op3_decimal(&d); +} + +static void mau_add() +{ + XFP a, b, result; + + load_src_op(mau_state.op1, &a); + load_src_op(mau_state.op2, &b); + xfp_add(&a, &b, &result, MAU_RM); + store_op3(&result); +} + +/* + * OP3 = OP2 - OP1 + */ +static void mau_sub() +{ + XFP a, b, result; + + load_src_op(mau_state.op1, &a); + load_src_op(mau_state.op2, &b); + xfp_sub(&b, &a, &result, MAU_RM); + store_op3(&result); +} + +/* + * OP3 = OP1 * OP2 + */ +static void mau_mul() +{ + XFP a, b, result; + + load_src_op(mau_state.op1, &a); + load_src_op(mau_state.op2, &b); + xfp_mul(&b, &a, &result, MAU_RM); + store_op3(&result); +} + +/* + * OP3 = OP1 / OP2 + */ +static void mau_div() +{ + XFP a, b, result; + + load_src_op(mau_state.op1, &a); + load_src_op(mau_state.op2, &b); + sim_debug(TRACE_DBG, &mau_dev, + "[DIV OP2/OP1] OP2=0x%04x%016llx OP1=0x%04x%016llx\n", + b.sign_exp, b.frac, + a.sign_exp, a.frac); + xfp_div(&b, &a, &result, MAU_RM); + store_op3(&result); +} + +static void mau_neg() +{ + XFP a, result; + + load_src_op(mau_state.op1, &a); + result.sign_exp = a.sign_exp; + result.frac = a.frac; + result.sign_exp ^= 0x8000; + result.s = a.s; + store_op3(&result); +} + +static void mau_abs() +{ + XFP a, result; + + load_src_op(mau_state.op1, &a); + result.sign_exp = a.sign_exp; + result.frac = a.frac; + result.sign_exp &= 0x7fff; + result.s = a.s; + store_op3(&result); +} + +/* + * OP3 = sqrt(OP1) + */ +static void mau_sqrt() +{ + XFP a, result; + + load_src_op(mau_state.op1, &a); + xfp_sqrt(&a, &result, MAU_RM); + store_op3(&result); +} + +/* + * OP3 = float(OP1) + * + * If the source operand is more than one word wide, only the last + * word is converted. + */ +static void mau_itof() +{ + XFP xfp; + int32 val = 0; + + mau_state.asr &= ~(MAU_ASR_N|MAU_ASR_Z); + + switch(mau_state.op1) { + case M_OP_F0: + case M_OP_F1: + case M_OP_F2: + case M_OP_F3: + mau_exc(MAU_ASR_IS, MAU_ASR_IM); + return; + case M_OP_MEM_SINGLE: + val = read_w(mau_state.src, ACC_AF, BUS_PER); + break; + case M_OP_MEM_DOUBLE: + val = read_w(mau_state.src + 4, ACC_AF, BUS_PER); + break; + case M_OP_MEM_TRIPLE: + val = read_w(mau_state.src + 8, ACC_AF, BUS_PER); + break; + default: + break; + } + /* Convert */ + mau_int_to_xfp(val, &xfp); + + store_op3(&xfp); +} + +/* + * OP3 = REMAINDER(b/a) + */ +static void mau_remainder() +{ + XFP a, b, result; + + load_src_op(mau_state.op1, &a); + load_src_op(mau_state.op2, &b); + xfp_remainder(&b, &a, &result, MAU_RM); + store_op3(&result); +} + +/* + * Decode the command word into its corresponding parts. Both src and + * dst are optional depending on the WE32100 operand, and may be set + * to any value if not used. + */ +static SIM_INLINE void mau_decode(uint32 cmd, uint32 src, uint32 dst) +{ + mau_state.cmd = cmd; + mau_state.src = src; + mau_state.dst = dst; + mau_state.opcode = (uint8) ((cmd & 0x7c00) >> 10); + mau_state.op1 = (uint8) ((cmd & 0x0380) >> 7); + mau_state.op2 = (uint8) ((cmd & 0x0070) >> 4); + mau_state.op3 = (uint8) (cmd & 0x000f); + sim_debug(DECODE_DBG, &mau_dev, + "opcode=%s (%02x) op1=%s op2=%s op3=%s\n", + mau_op_names[mau_state.opcode], + mau_state.opcode, + src_op_names[mau_state.op1 & 0x7], + src_op_names[mau_state.op2 & 0x7], + dst_op_names[mau_state.op3 & 0xf]); +} + +/* + * Handle a command. + */ +static void mau_execute() +{ + clear_asr(); + + switch(mau_state.opcode) { + case M_NOP: + /* Do nothing */ + break; + case M_ADD: + mau_add(); + break; + case M_SUB: + mau_sub(); + break; + case M_MUL: + mau_mul(); + break; + case M_DIV: + mau_div(); + break; + case M_RDASR: + mau_rdasr(); + break; + case M_WRASR: + mau_wrasr(); + break; + case M_MOVE: + mau_move(); + break; + case M_LDR: + mau_ldr(); + break; + case M_ITOF: + mau_itof(); + break; + case M_EROF: + mau_erof(); + break; + case M_RTOI: + mau_rtoi(); + break; + case M_FTOI: + mau_ftoi(); + break; + case M_CMP: + mau_cmp(); + break; + case M_CMPS: + mau_cmps(); + break; + case M_CMPE: + mau_cmpe(); + break; + case M_CMPES: + mau_cmpes(); + break; + case M_REM: + mau_remainder(); + break; + case M_NEG: + mau_neg(); + break; + case M_ABS: + mau_abs(); + break; + case M_SQRT: + mau_sqrt(); + break; + case M_FTOD: + mau_ftod(); + break; + case M_DTOF: + mau_dtof(); + break; + default: + sim_debug(TRACE_DBG, &mau_dev, + "[execute] unhandled opcode %s [0x%02x]\n", + mau_op_names[mau_state.opcode], + mau_state.opcode); + break; + } + + /* If an error has occured, abort */ + abort_on_fault(); + + /* Copy the N, Z, V and C (from PS) flags over to the CPU's PSW */ + R[NUM_PSW] &= ~(MAU_ASR_N|MAU_ASR_Z|MAU_ASR_IO|MAU_ASR_PS); + R[NUM_PSW] |= (mau_state.asr & (MAU_ASR_N|MAU_ASR_Z|MAU_ASR_IO|MAU_ASR_PS)); + + /* Set the RA and CSC flags in the ASR */ + mau_state.asr |= MAU_ASR_RA; + if (mau_state.opcode != M_RDASR && mau_state.opcode != M_LDR) { + mau_state.asr |= MAU_ASR_CSC; + } +} + +/* + * Receive a broadcast from the CPU, and potentially handle it. + */ +t_stat mau_broadcast(uint32 cmd, uint32 src, uint32 dst) +{ + uint8 id = (uint8) ((cmd & 0xff000000) >> 24); + + /* If the MAU isn't attached, or if this message isn't for us, + * return SCPE_NXM. Otherwise, decode and act on the command. */ + if (id != MAU_ID) { + sim_debug(DECODE_DBG, &mau_dev, + "[broadcast] Message for coprocessor id %d is not for MAU (%d)\n", + id, MAU_ID); + return SCPE_NXM; + } else if (mau_dev.flags & DEV_DIS) { + sim_debug(DECODE_DBG, &mau_dev, + "[broadcast] Message for MAU, but MAU is not attached.\n"); + return SCPE_NOATT; + } else { + mau_decode(cmd, src, dst); + mau_execute(); + return SCPE_OK; + } +} + +CONST char *mau_description(DEVICE *dptr) +{ +#if defined(REV3) + return "WE 32106 MAU"; +#else + return "WE 32206 MAU"; +#endif +} diff --git a/3B2/3b2_mau.h b/3B2/3b2_mau.h index 6320df42..811c1748 100644 --- a/3B2/3b2_mau.h +++ b/3B2/3b2_mau.h @@ -1,389 +1,389 @@ -/* 3b2_mau.h: WE32106 and WE32206 Math Accelerator Unit - - Copyright (c) 2021-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. - - --------------------------------------------------------------------- - - This file is part of a simulation of the WE 32106 and WE 32206 Math - Acceleration Units. The WE 32106 and WE 32206 are IEEE-754 - compabitle floating point hardware math accelerators that were - available as an optional component on the AT&T 3B2/310 and 3B2/400, - and as a standard component on the 3B2/500, 3B2/600, 3B2/700, and - 3B2/1000. - - Portions of this code are derived from the SoftFloat 2c library by - John R. Hauser. Functions derived from SoftFloat 2c are clearly - marked in the comments. - - Legal Notice - ============ - - SoftFloat was written by John R. Hauser. Release 2c of SoftFloat - was made possible in part by the International Computer Science - Institute, located at Suite 600, 1947 Center Street, Berkeley, - California 94704. Funding was partially provided by the National - Science Foundation under grant MIP-9311980. The original version - of this code was written as part of a project to build a - fixed-point vector processor in collaboration with the University - of California at Berkeley, overseen by Profs. Nelson Morgan and - John Wawrzynek. - - THIS SOFTWARE IS DISTRIBUTED AS IS, FOR FREE. Although reasonable - effort has been made to avoid it, THIS SOFTWARE MAY CONTAIN FAULTS - THAT WILL AT TIMES RESULT IN INCORRECT BEHAVIOR. USE OF THIS - SOFTWARE IS RESTRICTED TO PERSONS AND ORGANIZATIONS WHO CAN AND - WILL TOLERATE ALL LOSSES, COSTS, OR OTHER PROBLEMS THEY INCUR DUE - TO THE SOFTWARE WITHOUT RECOMPENSE FROM JOHN HAUSER OR THE - INTERNATIONAL COMPUTER SCIENCE INSTITUTE, AND WHO FURTHERMORE - EFFECTIVELY INDEMNIFY JOHN HAUSER AND THE INTERNATIONAL COMPUTER - SCIENCE INSTITUTE (possibly via similar legal notice) AGAINST ALL - LOSSES, COSTS, OR OTHER PROBLEMS INCURRED BY THEIR CUSTOMERS AND - CLIENTS DUE TO THE SOFTWARE, OR INCURRED BY ANYONE DUE TO A - DERIVATIVE WORK THEY CREATE USING ANY PART OF THE SOFTWARE. - - The following are expressly permitted, even for commercial - purposes: - - (1) distribution of SoftFloat in whole or in part, as long as this - and other legal notices remain and are prominent, and provided also - that, for a partial distribution, prominent notice is given that it - is a subset of the original; and - - (2) inclusion or use of SoftFloat in whole or in part in a - derivative work, provided that the use restrictions above are met - and the minimal documentation requirements stated in the source - code are satisfied. - --------------------------------------------------------------------- - - Data Types - ========== - - The WE32106 MAU stores values using IEEE-754 1985 types, plus a - non-standard Decimal type. - - - Decimal Type - 18 BCD digits long. Each digit is 4 bits wide. - Sign is encoded in byte 0. - - 3322 2222 2222 1111 1111 1100 0000 0000 - 1098 7654 3210 9876 5432 1098 7654 3210 - +-------------------+----+----+----+----+ - | unused | D18| D17| D16| D15| High Word - +----+----+----+----+----+----+----+----+ - | D14| D13| D12| D11| D10| D09| D08| D07| Middle Word - +----+----+----+----+----+----+----+----+ - | D06| D05| D04| D03| D02| D01| D00|sign| Low Word - +----+----+----+----+----+----+----+----+ - - Sign: 0: Positive Infinity 10: Positive Number - 1: Negative Infinity 11: Negative Number - 2: Positive NaN 12: Positive Number - 3: Negative NaN 13: Negative Number - 4-9: Trapping NaN 14-15: Positive Number - - - Extended Precision (80-bit) - exponent biased by 16383 - - 3 322222222221111 1 111110000000000 - 1 098765432109876 5 432109876543210 - +-----------------+-+---------------+ - | unused |S| Exponent | High Word - +-+---------------+-+---------------+ - |J| Fraction (high word) | Middle Word - +-+---------------------------------+ - | Fraction (low word) | Low Word - +-----------------------------------+ - - - - Double Precision (64-bit) - exponent biased by 1023 - - 3 3222222222 211111111110000000000 - 1 0987654321 098765432109876543210 - +-+----------+---------------------+ - |S| Exponent | Fraction (high) | High Word - +-+----------+---------------------+ - | Fraction (low) | Low Word - +----------------------------------+ - - - - Single Precision (32-bit) - exponent biased by 127 - - 3 32222222 22211111111110000000000 - 1 09876543 21098765432109876543210 - +-+--------+-----------------------+ - |S| Exp | Fraction | - +-+--------+-----------------------+ - -*/ - -#ifndef _3B2_REV2_MAU_H_ -#define _3B2_REV2_MAU_H_ - -#include "sim_defs.h" - -#define SRC_LEN_INVALID 0 -#define SRC_LEN_SINGLE 1 -#define SRC_LEN_DOUBLE 2 -#define SRC_LEN_TRIPLE 3 - -#define MAU_ASR_RC_SHIFT 22 - -#define MAU_ASR_PR 0x20u /* Partial Remainder */ -#define MAU_ASR_QS 0x40u /* Divide By Zero Sticky */ -#define MAU_ASR_US 0x80u /* Underflow Sticky */ -#define MAU_ASR_OS 0x100u /* Overflow Sticky */ -#define MAU_ASR_IS 0x200u /* Invalid Operation Sticky */ -#define MAU_ASR_PM 0x400u /* Inexact Mask */ -#define MAU_ASR_QM 0x800u /* Divide by Zero Mask */ -#define MAU_ASR_UM 0x1000u /* Underflow Mask */ -#define MAU_ASR_OM 0x2000u /* Overflow Mask */ -#define MAU_ASR_IM 0x4000u /* Invalid Operation Mask */ - -#define MAU_ASR_UO 0x10000u /* Unordered */ -#define MAU_ASR_CSC 0x20000u /* Context Switch Control */ -#define MAU_ASR_PS 0x40000u /* Inexact Sticky */ -#define MAU_ASR_IO 0x80000u /* Integer Overflow */ -#define MAU_ASR_Z 0x100000u /* Zero Flag */ -#define MAU_ASR_N 0x200000u /* Negative Flag */ -#define MAU_ASR_RC 0x400000u /* Round Control */ - -#define MAU_ASR_NTNC 0x1000000u /* Nontrapping NaN Control */ -#define MAU_ASR_ECP 0x2000000u /* Exception Condition */ - -#define MAU_ASR_RA 0x80000000u /* Result Available */ - -#if defined(REV3) -#define MAU_ASR_FE 0x1u /* Feature Enable */ -#define MAU_ASR_VER 0x2u /* Version */ -#define MAU_ASR_UW 0x10u /* Unaligned Word */ -#define MAU_ASR_WF 0x8000u /* Write Fault Indicator */ -#define MAU_RC_RN 0 /* Round toward Nearest */ -#define MAU_RC_RP 1 /* Round toward Plus Infin. */ -#define MAU_RC_RM 2 /* Round toward Neg. Infin. */ -#define MAU_RC_RZ 3 /* Round toward Zero */ -#endif - -#define SFP_SIGN(V) (((V) >> 31) & 1) -#define SFP_EXP(V) (((V) >> 23) & 0xff) -#define SFP_FRAC(V) ((V) & 0x7fffff) - -#define DFP_SIGN(V) (((V) >> 63) & 1) -#define DFP_EXP(V) (((V) >> 52) & 0x7ff) -#define DFP_FRAC(V) ((V) & 0xfffffffffffffull) - -#define XFP_SIGN(V) (((V)->sign_exp >> 15) & 1) -#define XFP_EXP(V) ((V)->sign_exp & 0x7fff) -#define XFP_FRAC(V) ((V)->frac) - -#define XFP_IS_NORMAL(V) ((V)->frac & 0x8000000000000000ull) - -#define DEFAULT_XFP_NAN_SIGN_EXP 0xffff -#define DEFAULT_XFP_NAN_FRAC 0xc000000000000000ull - -#define SFP_IS_TRAPPING_NAN(V) (((((V) >> 22) & 0x1ff) == 0x1fe) && \ - ((V) & 0x3fffff)) -#define DFP_IS_TRAPPING_NAN(V) (((((V) >> 51) & 0xfff) == 0xffe) && \ - ((V) & 0x7ffffffffffffull)) -#define XFP_IS_NAN(V) ((((V)->sign_exp & 0x7fff) == 0x7fff) && \ - (t_uint64)((V)->frac << 1)) -#define XFP_IS_TRAPPING_NAN(V) ((((V)->sign_exp) & 0x7fff) && \ - ((((V)->frac) & ~(0x4000000000000000ull)) << 1) && \ - (((V)->frac) == ((V)->frac & ~(0x4000000000000000ull)))) -#define PACK_DFP(SIGN,EXP,FRAC) ((((t_uint64)(SIGN))<<63) + \ - (((t_uint64)(EXP))<<52) + \ - ((t_uint64)(FRAC))) -#define PACK_SFP(SIGN,EXP,FRAC) (((uint32)(SIGN)<<31) + \ - ((uint32)(EXP)<<23) + \ - ((uint32)(FRAC))) -#define PACK_XFP(SIGN,EXP,FRAC,V) do { \ - (V)->frac = (FRAC); \ - (V)->sign_exp = ((uint16)(SIGN) << 15) + (EXP); \ - (V)->s = FALSE; \ - } while (0) - -#define PACK_XFP_S(SIGN,EXP,FRAC,S,V) do { \ - (V)->frac = (FRAC); \ - (V)->sign_exp = ((uint16)(SIGN) << 15) + (EXP); \ - (V)->s = (S) != 0; \ - } while (0) - -#define MAU_RM ((RM)((mau_state.asr >> 22) & 3)) - -typedef enum { - M_ADD = 0x02, - M_SUB = 0x03, - M_DIV = 0x04, - M_REM = 0x05, - M_MUL = 0x06, - M_MOVE = 0x07, - M_RDASR = 0x08, - M_WRASR = 0x09, - M_CMP = 0x0a, - M_CMPE = 0x0b, - M_ABS = 0x0c, - M_SQRT = 0x0d, - M_RTOI = 0x0e, - M_FTOI = 0x0f, - M_ITOF = 0x10, - M_DTOF = 0x11, - M_FTOD = 0x12, - M_NOP = 0x13, - M_EROF = 0x14, - M_NEG = 0x17, - M_LDR = 0x18, - M_CMPS = 0x1a, - M_CMPES = 0x1b -} mau_opcodes; - -typedef enum { - M_OP3_F0_SINGLE, - M_OP3_F1_SINGLE, - M_OP3_F2_SINGLE, - M_OP3_F3_SINGLE, - M_OP3_F0_DOUBLE, - M_OP3_F1_DOUBLE, - M_OP3_F2_DOUBLE, - M_OP3_F3_DOUBLE, - M_OP3_F0_TRIPLE, - M_OP3_F1_TRIPLE, - M_OP3_F2_TRIPLE, - M_OP3_F3_TRIPLE, - M_OP3_MEM_SINGLE, - M_OP3_MEM_DOUBLE, - M_OP3_MEM_TRIPLE, - M_OP3_NONE -} op3_spec; - -/* Specifier bytes for Operands 1 and 2 */ -typedef enum { - M_OP_F0, - M_OP_F1, - M_OP_F2, - M_OP_F3, - M_OP_MEM_SINGLE, - M_OP_MEM_DOUBLE, - M_OP_MEM_TRIPLE, - M_OP_NONE -} op_spec; - -/* - * 128-bit value - */ -typedef struct { - t_uint64 low; - t_uint64 high; -} t_mau_128; - -/* - * Not-a-Number Type - */ -typedef struct { - t_bool sign; - t_uint64 high; - t_uint64 low; -} T_NAN; - -/* - * Extended Precision (80 bits). - * - * Note that an undocumented feature of the WE32106 requires the use - * of uint32 rather than uint16 for the sign and exponent components - * of the struct. Although bits 80-95 are "unused", several - * diagnostics actually expect these bits to be moved and preserved on - * word transfers. They are ignored and discarded by math routines, - * however. - * - * The 's' field holds the Sticky bit used by rounding. - */ -typedef struct { - uint32 sign_exp; /* Sign and Exponent */ - t_uint64 frac; /* Fraction/Significand/Mantissa */ - t_bool s; /* Sticky bit */ -} XFP; - -typedef struct { - uint32 h; - t_uint64 l; -} DEC; - -/* - * Supported rounding modes. - */ -typedef enum { - ROUND_NEAREST, - ROUND_PLUS_INF, - ROUND_MINUS_INF, - ROUND_ZERO -} RM; - -/* - * Double Precision (64 bits) - */ -typedef t_uint64 DFP; - -/* - * Single Precision (32 bits) - */ -typedef uint32 SFP; - -/* - * MAU state - */ - -typedef struct { - uint32 cmd; - /* Exception */ - uint32 exception; - /* Status register */ - uint32 asr; - t_bool trapping_nan; - /* Generate a Non-Trapping NaN */ - t_bool ntnan; - /* Source (from broadcast) */ - uint32 src; - /* Destination (from broadcast) */ - uint32 dst; - uint8 opcode; - uint8 op1; - uint8 op2; - uint8 op3; - /* Data Register */ - XFP dr; - /* Operand Registers */ - XFP f0; - XFP f1; - XFP f2; - XFP f3; -} MAU_STATE; - -t_stat mau_reset(DEVICE *dptr); -t_stat mau_attach(UNIT *uptr, CONST char *cptr); -t_stat mau_detach(UNIT *uptr); -t_stat mau_broadcast(uint32 cmd, uint32 src, uint32 dst); -CONST char *mau_description(DEVICE *dptr); -t_stat mau_broadcast(uint32 cmd, uint32 src, uint32 dst); - -#endif /* _3B2_REV2_MAU_H_ */ +/* 3b2_mau.h: WE32106 and WE32206 Math Accelerator Unit + + Copyright (c) 2021-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. + + --------------------------------------------------------------------- + + This file is part of a simulation of the WE 32106 and WE 32206 Math + Acceleration Units. The WE 32106 and WE 32206 are IEEE-754 + compabitle floating point hardware math accelerators that were + available as an optional component on the AT&T 3B2/310 and 3B2/400, + and as a standard component on the 3B2/500, 3B2/600, 3B2/700, and + 3B2/1000. + + Portions of this code are derived from the SoftFloat 2c library by + John R. Hauser. Functions derived from SoftFloat 2c are clearly + marked in the comments. + + Legal Notice + ============ + + SoftFloat was written by John R. Hauser. Release 2c of SoftFloat + was made possible in part by the International Computer Science + Institute, located at Suite 600, 1947 Center Street, Berkeley, + California 94704. Funding was partially provided by the National + Science Foundation under grant MIP-9311980. The original version + of this code was written as part of a project to build a + fixed-point vector processor in collaboration with the University + of California at Berkeley, overseen by Profs. Nelson Morgan and + John Wawrzynek. + + THIS SOFTWARE IS DISTRIBUTED AS IS, FOR FREE. Although reasonable + effort has been made to avoid it, THIS SOFTWARE MAY CONTAIN FAULTS + THAT WILL AT TIMES RESULT IN INCORRECT BEHAVIOR. USE OF THIS + SOFTWARE IS RESTRICTED TO PERSONS AND ORGANIZATIONS WHO CAN AND + WILL TOLERATE ALL LOSSES, COSTS, OR OTHER PROBLEMS THEY INCUR DUE + TO THE SOFTWARE WITHOUT RECOMPENSE FROM JOHN HAUSER OR THE + INTERNATIONAL COMPUTER SCIENCE INSTITUTE, AND WHO FURTHERMORE + EFFECTIVELY INDEMNIFY JOHN HAUSER AND THE INTERNATIONAL COMPUTER + SCIENCE INSTITUTE (possibly via similar legal notice) AGAINST ALL + LOSSES, COSTS, OR OTHER PROBLEMS INCURRED BY THEIR CUSTOMERS AND + CLIENTS DUE TO THE SOFTWARE, OR INCURRED BY ANYONE DUE TO A + DERIVATIVE WORK THEY CREATE USING ANY PART OF THE SOFTWARE. + + The following are expressly permitted, even for commercial + purposes: + + (1) distribution of SoftFloat in whole or in part, as long as this + and other legal notices remain and are prominent, and provided also + that, for a partial distribution, prominent notice is given that it + is a subset of the original; and + + (2) inclusion or use of SoftFloat in whole or in part in a + derivative work, provided that the use restrictions above are met + and the minimal documentation requirements stated in the source + code are satisfied. + --------------------------------------------------------------------- + + Data Types + ========== + + The WE32106 MAU stores values using IEEE-754 1985 types, plus a + non-standard Decimal type. + + - Decimal Type - 18 BCD digits long. Each digit is 4 bits wide. + Sign is encoded in byte 0. + + 3322 2222 2222 1111 1111 1100 0000 0000 + 1098 7654 3210 9876 5432 1098 7654 3210 + +-------------------+----+----+----+----+ + | unused | D18| D17| D16| D15| High Word + +----+----+----+----+----+----+----+----+ + | D14| D13| D12| D11| D10| D09| D08| D07| Middle Word + +----+----+----+----+----+----+----+----+ + | D06| D05| D04| D03| D02| D01| D00|sign| Low Word + +----+----+----+----+----+----+----+----+ + + Sign: 0: Positive Infinity 10: Positive Number + 1: Negative Infinity 11: Negative Number + 2: Positive NaN 12: Positive Number + 3: Negative NaN 13: Negative Number + 4-9: Trapping NaN 14-15: Positive Number + + - Extended Precision (80-bit) - exponent biased by 16383 + + 3 322222222221111 1 111110000000000 + 1 098765432109876 5 432109876543210 + +-----------------+-+---------------+ + | unused |S| Exponent | High Word + +-+---------------+-+---------------+ + |J| Fraction (high word) | Middle Word + +-+---------------------------------+ + | Fraction (low word) | Low Word + +-----------------------------------+ + + + - Double Precision (64-bit) - exponent biased by 1023 + + 3 3222222222 211111111110000000000 + 1 0987654321 098765432109876543210 + +-+----------+---------------------+ + |S| Exponent | Fraction (high) | High Word + +-+----------+---------------------+ + | Fraction (low) | Low Word + +----------------------------------+ + + + - Single Precision (32-bit) - exponent biased by 127 + + 3 32222222 22211111111110000000000 + 1 09876543 21098765432109876543210 + +-+--------+-----------------------+ + |S| Exp | Fraction | + +-+--------+-----------------------+ + +*/ + +#ifndef _3B2_REV2_MAU_H_ +#define _3B2_REV2_MAU_H_ + +#include "sim_defs.h" + +#define SRC_LEN_INVALID 0 +#define SRC_LEN_SINGLE 1 +#define SRC_LEN_DOUBLE 2 +#define SRC_LEN_TRIPLE 3 + +#define MAU_ASR_RC_SHIFT 22 + +#define MAU_ASR_PR 0x20u /* Partial Remainder */ +#define MAU_ASR_QS 0x40u /* Divide By Zero Sticky */ +#define MAU_ASR_US 0x80u /* Underflow Sticky */ +#define MAU_ASR_OS 0x100u /* Overflow Sticky */ +#define MAU_ASR_IS 0x200u /* Invalid Operation Sticky */ +#define MAU_ASR_PM 0x400u /* Inexact Mask */ +#define MAU_ASR_QM 0x800u /* Divide by Zero Mask */ +#define MAU_ASR_UM 0x1000u /* Underflow Mask */ +#define MAU_ASR_OM 0x2000u /* Overflow Mask */ +#define MAU_ASR_IM 0x4000u /* Invalid Operation Mask */ + +#define MAU_ASR_UO 0x10000u /* Unordered */ +#define MAU_ASR_CSC 0x20000u /* Context Switch Control */ +#define MAU_ASR_PS 0x40000u /* Inexact Sticky */ +#define MAU_ASR_IO 0x80000u /* Integer Overflow */ +#define MAU_ASR_Z 0x100000u /* Zero Flag */ +#define MAU_ASR_N 0x200000u /* Negative Flag */ +#define MAU_ASR_RC 0x400000u /* Round Control */ + +#define MAU_ASR_NTNC 0x1000000u /* Nontrapping NaN Control */ +#define MAU_ASR_ECP 0x2000000u /* Exception Condition */ + +#define MAU_ASR_RA 0x80000000u /* Result Available */ + +#if defined(REV3) +#define MAU_ASR_FE 0x1u /* Feature Enable */ +#define MAU_ASR_VER 0x2u /* Version */ +#define MAU_ASR_UW 0x10u /* Unaligned Word */ +#define MAU_ASR_WF 0x8000u /* Write Fault Indicator */ +#define MAU_RC_RN 0 /* Round toward Nearest */ +#define MAU_RC_RP 1 /* Round toward Plus Infin. */ +#define MAU_RC_RM 2 /* Round toward Neg. Infin. */ +#define MAU_RC_RZ 3 /* Round toward Zero */ +#endif + +#define SFP_SIGN(V) (((V) >> 31) & 1) +#define SFP_EXP(V) (((V) >> 23) & 0xff) +#define SFP_FRAC(V) ((V) & 0x7fffff) + +#define DFP_SIGN(V) (((V) >> 63) & 1) +#define DFP_EXP(V) (((V) >> 52) & 0x7ff) +#define DFP_FRAC(V) ((V) & 0xfffffffffffffull) + +#define XFP_SIGN(V) (((V)->sign_exp >> 15) & 1) +#define XFP_EXP(V) ((V)->sign_exp & 0x7fff) +#define XFP_FRAC(V) ((V)->frac) + +#define XFP_IS_NORMAL(V) ((V)->frac & 0x8000000000000000ull) + +#define DEFAULT_XFP_NAN_SIGN_EXP 0xffff +#define DEFAULT_XFP_NAN_FRAC 0xc000000000000000ull + +#define SFP_IS_TRAPPING_NAN(V) (((((V) >> 22) & 0x1ff) == 0x1fe) && \ + ((V) & 0x3fffff)) +#define DFP_IS_TRAPPING_NAN(V) (((((V) >> 51) & 0xfff) == 0xffe) && \ + ((V) & 0x7ffffffffffffull)) +#define XFP_IS_NAN(V) ((((V)->sign_exp & 0x7fff) == 0x7fff) && \ + (t_uint64)((V)->frac << 1)) +#define XFP_IS_TRAPPING_NAN(V) ((((V)->sign_exp) & 0x7fff) && \ + ((((V)->frac) & ~(0x4000000000000000ull)) << 1) && \ + (((V)->frac) == ((V)->frac & ~(0x4000000000000000ull)))) +#define PACK_DFP(SIGN,EXP,FRAC) ((((t_uint64)(SIGN))<<63) + \ + (((t_uint64)(EXP))<<52) + \ + ((t_uint64)(FRAC))) +#define PACK_SFP(SIGN,EXP,FRAC) (((uint32)(SIGN)<<31) + \ + ((uint32)(EXP)<<23) + \ + ((uint32)(FRAC))) +#define PACK_XFP(SIGN,EXP,FRAC,V) do { \ + (V)->frac = (FRAC); \ + (V)->sign_exp = ((uint16)(SIGN) << 15) + (EXP); \ + (V)->s = FALSE; \ + } while (0) + +#define PACK_XFP_S(SIGN,EXP,FRAC,S,V) do { \ + (V)->frac = (FRAC); \ + (V)->sign_exp = ((uint16)(SIGN) << 15) + (EXP); \ + (V)->s = (S) != 0; \ + } while (0) + +#define MAU_RM ((RM)((mau_state.asr >> 22) & 3)) + +typedef enum { + M_ADD = 0x02, + M_SUB = 0x03, + M_DIV = 0x04, + M_REM = 0x05, + M_MUL = 0x06, + M_MOVE = 0x07, + M_RDASR = 0x08, + M_WRASR = 0x09, + M_CMP = 0x0a, + M_CMPE = 0x0b, + M_ABS = 0x0c, + M_SQRT = 0x0d, + M_RTOI = 0x0e, + M_FTOI = 0x0f, + M_ITOF = 0x10, + M_DTOF = 0x11, + M_FTOD = 0x12, + M_NOP = 0x13, + M_EROF = 0x14, + M_NEG = 0x17, + M_LDR = 0x18, + M_CMPS = 0x1a, + M_CMPES = 0x1b +} mau_opcodes; + +typedef enum { + M_OP3_F0_SINGLE, + M_OP3_F1_SINGLE, + M_OP3_F2_SINGLE, + M_OP3_F3_SINGLE, + M_OP3_F0_DOUBLE, + M_OP3_F1_DOUBLE, + M_OP3_F2_DOUBLE, + M_OP3_F3_DOUBLE, + M_OP3_F0_TRIPLE, + M_OP3_F1_TRIPLE, + M_OP3_F2_TRIPLE, + M_OP3_F3_TRIPLE, + M_OP3_MEM_SINGLE, + M_OP3_MEM_DOUBLE, + M_OP3_MEM_TRIPLE, + M_OP3_NONE +} op3_spec; + +/* Specifier bytes for Operands 1 and 2 */ +typedef enum { + M_OP_F0, + M_OP_F1, + M_OP_F2, + M_OP_F3, + M_OP_MEM_SINGLE, + M_OP_MEM_DOUBLE, + M_OP_MEM_TRIPLE, + M_OP_NONE +} op_spec; + +/* + * 128-bit value + */ +typedef struct { + t_uint64 low; + t_uint64 high; +} t_mau_128; + +/* + * Not-a-Number Type + */ +typedef struct { + t_bool sign; + t_uint64 high; + t_uint64 low; +} T_NAN; + +/* + * Extended Precision (80 bits). + * + * Note that an undocumented feature of the WE32106 requires the use + * of uint32 rather than uint16 for the sign and exponent components + * of the struct. Although bits 80-95 are "unused", several + * diagnostics actually expect these bits to be moved and preserved on + * word transfers. They are ignored and discarded by math routines, + * however. + * + * The 's' field holds the Sticky bit used by rounding. + */ +typedef struct { + uint32 sign_exp; /* Sign and Exponent */ + t_uint64 frac; /* Fraction/Significand/Mantissa */ + t_bool s; /* Sticky bit */ +} XFP; + +typedef struct { + uint32 h; + t_uint64 l; +} DEC; + +/* + * Supported rounding modes. + */ +typedef enum { + ROUND_NEAREST, + ROUND_PLUS_INF, + ROUND_MINUS_INF, + ROUND_ZERO +} RM; + +/* + * Double Precision (64 bits) + */ +typedef t_uint64 DFP; + +/* + * Single Precision (32 bits) + */ +typedef uint32 SFP; + +/* + * MAU state + */ + +typedef struct { + uint32 cmd; + /* Exception */ + uint32 exception; + /* Status register */ + uint32 asr; + t_bool trapping_nan; + /* Generate a Non-Trapping NaN */ + t_bool ntnan; + /* Source (from broadcast) */ + uint32 src; + /* Destination (from broadcast) */ + uint32 dst; + uint8 opcode; + uint8 op1; + uint8 op2; + uint8 op3; + /* Data Register */ + XFP dr; + /* Operand Registers */ + XFP f0; + XFP f1; + XFP f2; + XFP f3; +} MAU_STATE; + +t_stat mau_reset(DEVICE *dptr); +t_stat mau_attach(UNIT *uptr, CONST char *cptr); +t_stat mau_detach(UNIT *uptr); +t_stat mau_broadcast(uint32 cmd, uint32 src, uint32 dst); +CONST char *mau_description(DEVICE *dptr); +t_stat mau_broadcast(uint32 cmd, uint32 src, uint32 dst); + +#endif /* _3B2_REV2_MAU_H_ */ diff --git a/3B2/3b2_mem.c b/3B2/3b2_mem.c index 7603fe1c..1a776d45 100644 --- a/3B2/3b2_mem.c +++ b/3B2/3b2_mem.c @@ -1,346 +1,346 @@ -/* 3b2_mem.c: Memory Map Access Routines - - Copyright (c) 2021-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#include "3b2_mem.h" - -#include "3b2_cpu.h" -#include "3b2_csr.h" -#include "3b2_io.h" -#include "3b2_mmu.h" -#include "3b2_stddev.h" -#include "3b2_dmac.h" - -#if defined(REV3) -static uint32 ecc_addr; /* ECC address */ -static t_bool ecc_err; /* ECC multi-bit error */ -#endif - -/* - * ECC is simulated just enough to pass diagnostics, and no more. - * - * Checking and setting of ECC syndrome bits is a no-op for Rev 2. - */ -static SIM_INLINE void check_ecc(uint32 pa, t_bool write, uint8 src) -{ -#if defined(REV3) - /* Force ECC Syndrome mode enables a diagnostic mode on the AM2960 - data correction ICs */ - if (write && !CSR(CSRFECC)) { - sim_debug(EXECUTE_MSG, &mmu_dev, - "ECC Error on Write. pa=%08x\n", - pa); - ecc_addr = pa; - ecc_err = TRUE; - } else if (ecc_err && !write && pa == ecc_addr) { - sim_debug(EXECUTE_MSG, &mmu_dev, - "ECC Error detected on Read. pa=%08x psw=%08x cur_ipl=%d csr=%08x\n", - pa, R[NUM_PSW], PSW_CUR_IPL, csr_data); - flt[0] = ecc_addr & 0x3ffff; - flt[1] = MA_CPU_IO|MA_CPU_BU; - ecc_err = FALSE; - CSRBIT(CSRFRF, TRUE); /* Fault registers frozen */ - CSRBIT(CSRMBERR, TRUE); /* Multi-bit error */ - CPU_SET_INT(INT_MBERR); - /* Only abort if CPU is doing the read */ - if (src == BUS_CPU) { - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); - } - } -#endif -} - -/* Read Word (Physical Address) */ -uint32 pread_w(uint32 pa, uint8 src) -{ - uint8 *m; - uint32 index = 0; - -#if defined(REV3) - if ((pa & 3) && (R[NUM_PSW] & PSW_EA_MASK) == 0) { -#else - if (pa & 3) { -#endif - sim_debug(READ_MSG, &mmu_dev, - "Cannot read physical address. ALIGNMENT ISSUE: %08x\n", - pa); - CSRBIT(CSRALGN, TRUE); - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); - } - - if (IS_IO(pa)) { - return io_read(pa, 32); - } - - if (IS_ROM(pa)) { - m = ROM; - index = pa; - } else if (IS_RAM(pa)) { - check_ecc(pa, FALSE, src); - m = RAM; - index = (pa - PHYS_MEM_BASE); - } else { - return 0; - } - - return ATOW(m, index); -} - -/* - * Write Word (Physical Address) - */ -void pwrite_w(uint32 pa, uint32 val, uint8 src) -{ - uint32 index; - - if (pa & 3) { - sim_debug(WRITE_MSG, &mmu_dev, - "Cannot write physical address. ALIGNMENT ISSUE: %08x\n", - pa); - CSRBIT(CSRALGN, TRUE); - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); - } - - if (IS_IO(pa)) { - io_write(pa, val, 32); - return; - } - - if (IS_RAM(pa)) { - check_ecc(pa, TRUE, src); - index = pa - PHYS_MEM_BASE; - RAM[index] = (val >> 24) & 0xff; - RAM[index + 1] = (val >> 16) & 0xff; - RAM[index + 2] = (val >> 8) & 0xff; - RAM[index + 3] = val & 0xff; - return; - } -} - -/* - * Read Halfword (Physical Address) - */ -uint16 pread_h(uint32 pa, uint8 src) -{ - uint8 *m; - uint32 index; - - if (pa & 1) { - sim_debug(READ_MSG, &mmu_dev, - "Cannot read physical address. ALIGNMENT ISSUE %08x\n", - pa); - CSRBIT(CSRALGN, TRUE); - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); - } - - if (IS_IO(pa)) { - return (uint16) io_read(pa, 16); - } - - if (IS_ROM(pa)) { - m = ROM; - index = pa; - } else if (IS_RAM(pa)) { - check_ecc(pa, FALSE, src); - m = RAM; - index = pa - PHYS_MEM_BASE; - } else { - return 0; - } - - return ATOH(m, index); -} - -/* - * Write Halfword (Physical Address) - */ -void pwrite_h(uint32 pa, uint16 val, uint8 src) -{ - uint32 index; - -#if defined(REV3) - if ((pa & 1) && (R[NUM_PSW] & PSW_EA_MASK) == 0) { -#else - if (pa & 1) { -#endif - sim_debug(WRITE_MSG, &mmu_dev, - "Cannot write physical address %08x, ALIGNMENT ISSUE\n", - pa); - CSRBIT(CSRALGN, TRUE); - } - - if (IS_IO(pa)) { - io_write(pa, val, 16); - return; - } - - if (IS_RAM(pa)) { - check_ecc(pa, TRUE, src); - index = pa - PHYS_MEM_BASE; - RAM[index] = (val >> 8) & 0xff; - RAM[index + 1] = val & 0xff; - return; - } -} - -/* - * Read Byte (Physical Address) - */ -uint8 pread_b(uint32 pa, uint8 src) -{ - if (IS_IO(pa)) { - return (uint8)(io_read(pa, 8)); - } - - if (IS_ROM(pa)) { - return ROM[pa]; - } else if (IS_RAM(pa)) { - check_ecc(pa, FALSE, src); - return RAM[pa - PHYS_MEM_BASE]; - } else { - return 0; - } -} - -/* Write Byte (Physical Address) */ -void pwrite_b(uint32 pa, uint8 val, uint8 src) -{ - uint32 index; - - if (IS_IO(pa)) { - io_write(pa, val, 8); - return; - } - - if (IS_RAM(pa)) { - check_ecc(pa, TRUE, src); - index = pa - PHYS_MEM_BASE; - RAM[index] = val; - return; - } -} - -/* Write to ROM (used by ROM load) */ -void pwrite_b_rom(uint32 pa, uint8 val) { - if (IS_ROM(pa)) { - ROM[pa] = val; - } - } - -/* Read Byte (Virtual Address) */ -uint8 read_b(uint32 va, uint8 r_acc, uint8 src) -{ - return pread_b(mmu_xlate_addr(va, r_acc), src); -} - -/* Write Byte (Virtual Address) */ -void write_b(uint32 va, uint8 val, uint8 src) -{ - pwrite_b(mmu_xlate_addr(va, ACC_W), val, src); -} - -/* Read Halfword (Virtual Address) */ -uint16 read_h(uint32 va, uint8 r_acc, uint8 src) -{ - return pread_h(mmu_xlate_addr(va, r_acc), src); -} - -/* Write Halfword (Virtual Address) */ -void write_h(uint32 va, uint16 val, uint8 src) -{ - pwrite_h(mmu_xlate_addr(va, ACC_W), val, src); -} - -/* Read Word (Virtual Address) */ -uint32 read_w(uint32 va, uint8 r_acc, uint8 src) -{ - return pread_w(mmu_xlate_addr(va, r_acc), src); -} - -/* Write Word (Virtual Address) */ -void write_w(uint32 va, uint32 val, uint8 src) -{ - pwrite_w(mmu_xlate_addr(va, ACC_W), val, src); -} - -t_stat read_operand(uint32 va, uint8 *val) -{ - uint32 pa; - t_stat succ; - - succ = mmu_decode_va(va, ACC_IF, TRUE, &pa); - - if (succ == SCPE_OK) { - *val = pread_b(pa, BUS_CPU); - } else { - *val = 0; - } - - return succ; -} - -t_stat examine(uint32 va, uint8 *val) -{ - uint32 pa; - t_stat succ; - - succ = mmu_decode_va(va, 0, FALSE, &pa); - - if (succ == SCPE_OK) { - if (IS_ROM(pa) || IS_RAM(pa)) { - *val = pread_b(pa, BUS_CPU); - return SCPE_OK; - } else { - *val = 0; - return SCPE_NXM; - } - } else { - *val = 0; - return succ; - } -} - -t_stat deposit(uint32 va, uint8 val) -{ - uint32 pa; - t_stat succ; - - succ = mmu_decode_va(va, 0, FALSE, &pa); - - if (succ == SCPE_OK) { - if (IS_RAM(pa)) { - pwrite_b(pa, val, BUS_CPU); - return SCPE_OK; - } else { - return SCPE_NXM; - } - } else { - return succ; - } -} +/* 3b2_mem.c: Memory Map Access Routines + + Copyright (c) 2021-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#include "3b2_mem.h" + +#include "3b2_cpu.h" +#include "3b2_csr.h" +#include "3b2_io.h" +#include "3b2_mmu.h" +#include "3b2_stddev.h" +#include "3b2_dmac.h" + +#if defined(REV3) +static uint32 ecc_addr; /* ECC address */ +static t_bool ecc_err; /* ECC multi-bit error */ +#endif + +/* + * ECC is simulated just enough to pass diagnostics, and no more. + * + * Checking and setting of ECC syndrome bits is a no-op for Rev 2. + */ +static SIM_INLINE void check_ecc(uint32 pa, t_bool write, uint8 src) +{ +#if defined(REV3) + /* Force ECC Syndrome mode enables a diagnostic mode on the AM2960 + data correction ICs */ + if (write && !CSR(CSRFECC)) { + sim_debug(EXECUTE_MSG, &mmu_dev, + "ECC Error on Write. pa=%08x\n", + pa); + ecc_addr = pa; + ecc_err = TRUE; + } else if (ecc_err && !write && pa == ecc_addr) { + sim_debug(EXECUTE_MSG, &mmu_dev, + "ECC Error detected on Read. pa=%08x psw=%08x cur_ipl=%d csr=%08x\n", + pa, R[NUM_PSW], PSW_CUR_IPL, csr_data); + flt[0] = ecc_addr & 0x3ffff; + flt[1] = MA_CPU_IO|MA_CPU_BU; + ecc_err = FALSE; + CSRBIT(CSRFRF, TRUE); /* Fault registers frozen */ + CSRBIT(CSRMBERR, TRUE); /* Multi-bit error */ + CPU_SET_INT(INT_MBERR); + /* Only abort if CPU is doing the read */ + if (src == BUS_CPU) { + cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); + } + } +#endif +} + +/* Read Word (Physical Address) */ +uint32 pread_w(uint32 pa, uint8 src) +{ + uint8 *m; + uint32 index = 0; + +#if defined(REV3) + if ((pa & 3) && (R[NUM_PSW] & PSW_EA_MASK) == 0) { +#else + if (pa & 3) { +#endif + sim_debug(READ_MSG, &mmu_dev, + "Cannot read physical address. ALIGNMENT ISSUE: %08x\n", + pa); + CSRBIT(CSRALGN, TRUE); + cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); + } + + if (IS_IO(pa)) { + return io_read(pa, 32); + } + + if (IS_ROM(pa)) { + m = ROM; + index = pa; + } else if (IS_RAM(pa)) { + check_ecc(pa, FALSE, src); + m = RAM; + index = (pa - PHYS_MEM_BASE); + } else { + return 0; + } + + return ATOW(m, index); +} + +/* + * Write Word (Physical Address) + */ +void pwrite_w(uint32 pa, uint32 val, uint8 src) +{ + uint32 index; + + if (pa & 3) { + sim_debug(WRITE_MSG, &mmu_dev, + "Cannot write physical address. ALIGNMENT ISSUE: %08x\n", + pa); + CSRBIT(CSRALGN, TRUE); + cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); + } + + if (IS_IO(pa)) { + io_write(pa, val, 32); + return; + } + + if (IS_RAM(pa)) { + check_ecc(pa, TRUE, src); + index = pa - PHYS_MEM_BASE; + RAM[index] = (val >> 24) & 0xff; + RAM[index + 1] = (val >> 16) & 0xff; + RAM[index + 2] = (val >> 8) & 0xff; + RAM[index + 3] = val & 0xff; + return; + } +} + +/* + * Read Halfword (Physical Address) + */ +uint16 pread_h(uint32 pa, uint8 src) +{ + uint8 *m; + uint32 index; + + if (pa & 1) { + sim_debug(READ_MSG, &mmu_dev, + "Cannot read physical address. ALIGNMENT ISSUE %08x\n", + pa); + CSRBIT(CSRALGN, TRUE); + cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); + } + + if (IS_IO(pa)) { + return (uint16) io_read(pa, 16); + } + + if (IS_ROM(pa)) { + m = ROM; + index = pa; + } else if (IS_RAM(pa)) { + check_ecc(pa, FALSE, src); + m = RAM; + index = pa - PHYS_MEM_BASE; + } else { + return 0; + } + + return ATOH(m, index); +} + +/* + * Write Halfword (Physical Address) + */ +void pwrite_h(uint32 pa, uint16 val, uint8 src) +{ + uint32 index; + +#if defined(REV3) + if ((pa & 1) && (R[NUM_PSW] & PSW_EA_MASK) == 0) { +#else + if (pa & 1) { +#endif + sim_debug(WRITE_MSG, &mmu_dev, + "Cannot write physical address %08x, ALIGNMENT ISSUE\n", + pa); + CSRBIT(CSRALGN, TRUE); + } + + if (IS_IO(pa)) { + io_write(pa, val, 16); + return; + } + + if (IS_RAM(pa)) { + check_ecc(pa, TRUE, src); + index = pa - PHYS_MEM_BASE; + RAM[index] = (val >> 8) & 0xff; + RAM[index + 1] = val & 0xff; + return; + } +} + +/* + * Read Byte (Physical Address) + */ +uint8 pread_b(uint32 pa, uint8 src) +{ + if (IS_IO(pa)) { + return (uint8)(io_read(pa, 8)); + } + + if (IS_ROM(pa)) { + return ROM[pa]; + } else if (IS_RAM(pa)) { + check_ecc(pa, FALSE, src); + return RAM[pa - PHYS_MEM_BASE]; + } else { + return 0; + } +} + +/* Write Byte (Physical Address) */ +void pwrite_b(uint32 pa, uint8 val, uint8 src) +{ + uint32 index; + + if (IS_IO(pa)) { + io_write(pa, val, 8); + return; + } + + if (IS_RAM(pa)) { + check_ecc(pa, TRUE, src); + index = pa - PHYS_MEM_BASE; + RAM[index] = val; + return; + } +} + +/* Write to ROM (used by ROM load) */ +void pwrite_b_rom(uint32 pa, uint8 val) { + if (IS_ROM(pa)) { + ROM[pa] = val; + } + } + +/* Read Byte (Virtual Address) */ +uint8 read_b(uint32 va, uint8 r_acc, uint8 src) +{ + return pread_b(mmu_xlate_addr(va, r_acc), src); +} + +/* Write Byte (Virtual Address) */ +void write_b(uint32 va, uint8 val, uint8 src) +{ + pwrite_b(mmu_xlate_addr(va, ACC_W), val, src); +} + +/* Read Halfword (Virtual Address) */ +uint16 read_h(uint32 va, uint8 r_acc, uint8 src) +{ + return pread_h(mmu_xlate_addr(va, r_acc), src); +} + +/* Write Halfword (Virtual Address) */ +void write_h(uint32 va, uint16 val, uint8 src) +{ + pwrite_h(mmu_xlate_addr(va, ACC_W), val, src); +} + +/* Read Word (Virtual Address) */ +uint32 read_w(uint32 va, uint8 r_acc, uint8 src) +{ + return pread_w(mmu_xlate_addr(va, r_acc), src); +} + +/* Write Word (Virtual Address) */ +void write_w(uint32 va, uint32 val, uint8 src) +{ + pwrite_w(mmu_xlate_addr(va, ACC_W), val, src); +} + +t_stat read_operand(uint32 va, uint8 *val) +{ + uint32 pa; + t_stat succ; + + succ = mmu_decode_va(va, ACC_IF, TRUE, &pa); + + if (succ == SCPE_OK) { + *val = pread_b(pa, BUS_CPU); + } else { + *val = 0; + } + + return succ; +} + +t_stat examine(uint32 va, uint8 *val) +{ + uint32 pa; + t_stat succ; + + succ = mmu_decode_va(va, 0, FALSE, &pa); + + if (succ == SCPE_OK) { + if (IS_ROM(pa) || IS_RAM(pa)) { + *val = pread_b(pa, BUS_CPU); + return SCPE_OK; + } else { + *val = 0; + return SCPE_NXM; + } + } else { + *val = 0; + return succ; + } +} + +t_stat deposit(uint32 va, uint8 val) +{ + uint32 pa; + t_stat succ; + + succ = mmu_decode_va(va, 0, FALSE, &pa); + + if (succ == SCPE_OK) { + if (IS_RAM(pa)) { + pwrite_b(pa, val, BUS_CPU); + return SCPE_OK; + } else { + return SCPE_NXM; + } + } else { + return succ; + } +} diff --git a/3B2/3b2_mem.h b/3B2/3b2_mem.h index 79575c9e..0f0fab3c 100644 --- a/3B2/3b2_mem.h +++ b/3B2/3b2_mem.h @@ -1,79 +1,79 @@ -/* 3b2_mem.h: Memory Map Access Routines - - Copyright (c) 2021-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#ifndef _3B2_MEM_H_ -#define _3B2_MEM_H_ - -#include "3b2_defs.h" - -#define IS_ROM(PA) ((PA) < ROM_SIZE) -#define IS_RAM(PA) (((PA) >= PHYS_MEM_BASE) && ((PA) < (PHYS_MEM_BASE + MEM_SIZE))) -#if defined(REV3) -#define IS_IO(PA) (((PA >= IO_BOTTOM) && (PA < IO_TOP)) || \ - ((PA >= CIO_BOTTOM) && (PA < CIO_TOP)) || \ - ((PA >= VCACHE_BOTTOM) && (PA < VCACHE_TOP)) || \ - ((PA >= BUB_BOTTOM) && (PA < BUB_TOP))) -#else -#define IS_IO(PA) (((PA >= IO_BOTTOM) && (PA < IO_TOP)) || \ - ((PA >= CIO_BOTTOM) && (PA < CIO_TOP))) -#endif - -#define MA_BUB3 0x100 /* BUBUS slot 3 master on fault */ -#define MA_BUB2 0x200 /* BUBUS slot 2 master on fault */ -#define MA_BUB1 0x400 /* BUBUS slot 1 master on fault */ -#define MA_CPU_BU 0x2000 /* CPU access BUBUS peripheral */ -#define MA_BUB0 0x4000 /* BUBUS slot 0 master on fault */ -#define MA_CPU_IO 0x8000 /* CPU accessing I/O peripheral */ -#define MA_IO_NLY 0x10000 /* IO Bus Master on fault */ -#define MA_IO_BM 0x80000 /* IO Bus Master or BUBUS was master on fault */ - -#define BUS_PER 0 /* Read or Write is from peripheral */ -#define BUS_CPU 1 /* Read or Write is from CPU */ - -uint32 pread_w(uint32 pa, uint8 src); -void pwrite_w(uint32 pa, uint32 val, uint8 src); -uint8 pread_b(uint32 pa, uint8 src); -void pwrite_b(uint32 pa, uint8 val, uint8 src); -void pwrite_b_rom(uint32 pa, uint8 val); -uint16 pread_h(uint32 pa, uint8 src); -void pwrite_h(uint32 pa, uint16 val, uint8 src); - -uint8 read_b(uint32 va, uint8 r_acc, uint8 src); -uint16 read_h(uint32 va, uint8 r_acc, uint8 src); -uint32 read_w(uint32 va, uint8 r_acc, uint8 src); -void write_b(uint32 va, uint8 val, uint8 src); -void write_h(uint32 va, uint16 val, uint8 src); -void write_w(uint32 va, uint32 val, uint8 src); - -t_stat read_operand(uint32 va, uint8 *val); -t_stat examine(uint32 va, uint8 *val); -t_stat deposit(uint32 va, uint8 val); - -#endif /* _3B2_MEM_H_ */ +/* 3b2_mem.h: Memory Map Access Routines + + Copyright (c) 2021-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#ifndef _3B2_MEM_H_ +#define _3B2_MEM_H_ + +#include "3b2_defs.h" + +#define IS_ROM(PA) ((PA) < ROM_SIZE) +#define IS_RAM(PA) (((PA) >= PHYS_MEM_BASE) && ((PA) < (PHYS_MEM_BASE + MEM_SIZE))) +#if defined(REV3) +#define IS_IO(PA) (((PA >= IO_BOTTOM) && (PA < IO_TOP)) || \ + ((PA >= CIO_BOTTOM) && (PA < CIO_TOP)) || \ + ((PA >= VCACHE_BOTTOM) && (PA < VCACHE_TOP)) || \ + ((PA >= BUB_BOTTOM) && (PA < BUB_TOP))) +#else +#define IS_IO(PA) (((PA >= IO_BOTTOM) && (PA < IO_TOP)) || \ + ((PA >= CIO_BOTTOM) && (PA < CIO_TOP))) +#endif + +#define MA_BUB3 0x100 /* BUBUS slot 3 master on fault */ +#define MA_BUB2 0x200 /* BUBUS slot 2 master on fault */ +#define MA_BUB1 0x400 /* BUBUS slot 1 master on fault */ +#define MA_CPU_BU 0x2000 /* CPU access BUBUS peripheral */ +#define MA_BUB0 0x4000 /* BUBUS slot 0 master on fault */ +#define MA_CPU_IO 0x8000 /* CPU accessing I/O peripheral */ +#define MA_IO_NLY 0x10000 /* IO Bus Master on fault */ +#define MA_IO_BM 0x80000 /* IO Bus Master or BUBUS was master on fault */ + +#define BUS_PER 0 /* Read or Write is from peripheral */ +#define BUS_CPU 1 /* Read or Write is from CPU */ + +uint32 pread_w(uint32 pa, uint8 src); +void pwrite_w(uint32 pa, uint32 val, uint8 src); +uint8 pread_b(uint32 pa, uint8 src); +void pwrite_b(uint32 pa, uint8 val, uint8 src); +void pwrite_b_rom(uint32 pa, uint8 val); +uint16 pread_h(uint32 pa, uint8 src); +void pwrite_h(uint32 pa, uint16 val, uint8 src); + +uint8 read_b(uint32 va, uint8 r_acc, uint8 src); +uint16 read_h(uint32 va, uint8 r_acc, uint8 src); +uint32 read_w(uint32 va, uint8 r_acc, uint8 src); +void write_b(uint32 va, uint8 val, uint8 src); +void write_h(uint32 va, uint16 val, uint8 src); +void write_w(uint32 va, uint32 val, uint8 src); + +t_stat read_operand(uint32 va, uint8 *val); +t_stat examine(uint32 va, uint8 *val); +t_stat deposit(uint32 va, uint8 val); + +#endif /* _3B2_MEM_H_ */ diff --git a/3B2/3b2_mmu.h b/3B2/3b2_mmu.h index f16a153a..4283b230 100644 --- a/3B2/3b2_mmu.h +++ b/3B2/3b2_mmu.h @@ -1,40 +1,40 @@ -/* 3b2_mmu.h: Common MMU header - - Copyright (c) 2021-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#ifndef _3B2_MMU_H_ -#define _3B2_MMU_H_ - -#if defined(REV3) -#include "3b2_rev3_mmu.h" -#else -#include "3b2_rev2_mmu.h" -#endif - -#endif /* _3B2_MMU_H_ */ +/* 3b2_mmu.h: Common MMU header + + Copyright (c) 2021-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#ifndef _3B2_MMU_H_ +#define _3B2_MMU_H_ + +#if defined(REV3) +#include "3b2_rev3_mmu.h" +#else +#include "3b2_rev2_mmu.h" +#endif + +#endif /* _3B2_MMU_H_ */ diff --git a/3B2/3b2_ni.c b/3B2/3b2_ni.c index d3e5f4b5..36a74cfa 100644 --- a/3B2/3b2_ni.c +++ b/3B2/3b2_ni.c @@ -1,1121 +1,1121 @@ -/* 3b2_ni.c: CM195A Network Interface CIO Card - - Copyright (c) 2018-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -/* - * NI is an intelligent feature card for the 3B2 that provides a - * 10BASE5 Ethernet interface. - - * Overview - * -------- - * - * The NI board is based on the Common I/O (CIO) platform. Like other - * CIO boards, it uses an 80186 embedded processor. The board and the - * 3B2 host communicate by reading and writing to the 3B2's main - * memory at locations established by the host via a series of job - * request and job completion queues. Only three interrupts are used: - * Two interrupts (80186 interrupts INT0 and INT1) are triggered by - * the 3B2 and tell the card when work is available in the request - * queue. One WE32100 interrupt (at a negotiated vector and predefined - * IPL) is used by the CIO board to tell the 3B2 that a new entry is - * available in the completion queue. - * - * The on-board ROM does not contain the full firmware required to - * perform all application-specific work. Rather, it is used only to - * bootstrap the 80186 and provide essential communication between the - * 3B2 host and the board's internal RAM. During initialization, the - * host must upload application-specific code to the board's RAM and - * cause the board to start running it. This is known as - * "pumping". The 80186 binary code for the NI board under System V - * Release 3 is stored in the file `/lib/pump/ni` - * - * Implementation Details - * ---------------------- - * - * The 10BASE5 interface on the NI board is driven by an Intel 82586 - * IEEE 802.3 LAN Coprocessor, controlled by the board's 80186 - * CPU. The 82586 is completely opaque to the host due to the nature - * of the CIO protocol. Nevertheless, an attempt is made to simulate - * the behavior of the 82586 where appropriate and possible. - * - * The NI board uses a sanity timer to occasionally write a watchdog - * or heartbeat entry into the completion queue, indicating that the - * Ethernet interface is still alive and that all is well. If the UNIX - * driver has not seen this heartbeat after approximately 10 seconds, - * it will consider the board to be in an "DOWN" state and send it a - * TERM ioctl. - * - * The NI board does behave differently from the other CIO boards in - * one respect: Unlike other CIO boards, the NI board takes jobs from - * its two Packet Receive CIO request queues by polling them, and then - * stores the taken jobs in a small 4-entry internal cache. It polls - * these queues quite rapidly in the real NI so it always has a full - * cache available for future incoming packets. To prevent performance - * issues, this simulation polls rapidly ("fast polling mode") only - * when absolutely necessary. Typically, that means only after the - * card has been reset, but before the request queues have finished - * being built by the 3B2 host. The UNIX NI driver expects and - * requires this behavior! - * - * Open Issues - * ----------- - * - * 1. The simulated card does not yet support setting or removing - * multicast Ethernet addresses. ioctl operations that attempt to - * set or remove multicast Ethernet addresses should silently - * fail. This will be supported in a future release. - * - */ - -#include "3b2_ni.h" - -#include "3b2_io.h" -#include "3b2_mem.h" -#include "3b2_stddev.h" - -/* State container for the card */ -NI_STATE ni; - -t_bool ni_conf = FALSE; - -/* Static Function Declarations */ -static void dump_packet(const char *direction, ETH_PACK *pkt); -static void ni_enable(); -static void ni_disable(); -static void ni_cmd(uint8 slot, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp); - -/* - * A list of pumped code CRCs that will cause Force Function Call to - * respond with "Test Passed". Must be null-terminated. - */ -static const uint32 NI_DIAG_CRCS[] = { - 0x795268a4, - 0xfab1057c, - 0x10ca00cd, - 0x9b3ddeda, - 0x267b19a0, - 0x123f36c0, - 0xc04ca0ab, - 0x96d0506e, /* Rev 3 NI, SVR 3.2.3 */ - 0x9d3fafde, /* Rev 3 NI, SVR 3.2.3 */ - 0, -}; - -/* - * A list of pumped code CRCs that will cause the sysgen routine to - * respond with a full completion request instead of an express - * completion request. Must be null-terminated. - */ -static const uint32 NI_PUMP_CRCS[] = { - 0xfab1057c, /* Rev 2 NI, SVR 3.x */ - 0xf6744bed, /* Rev 2 NI, SVR 3.x */ - 0x96d0506e, /* Rev 3 NI, SVR 3.2.3 */ - 0x9d3fafde, /* Rev 3 NI, SVR 3.2.3 */ - 0x3553230a, /* Rev 3 NI, SVR 3.2.3 */ - 0, -}; - -/* - * Unit 0: Packet reception. - * Unit 1: Sanity timer. - * Unit 2: Request Queue poller. - * Unit 3: CIO requests. - */ -UNIT ni_unit[] = { - { UDATA(&ni_rcv_svc, UNIT_IDLE|UNIT_ATTABLE, 0) }, - { UDATA(&ni_sanity_svc, UNIT_IDLE|UNIT_DIS, 0) }, - { UDATA(&ni_rq_svc, UNIT_IDLE|UNIT_DIS, 0) }, - { UDATA(&ni_cio_svc, UNIT_DIS, 0) }, - { 0 } -}; - -static UNIT *rcv_unit = &ni_unit[0]; -static UNIT *sanity_unit = &ni_unit[1]; -static UNIT *rq_unit = &ni_unit[2]; -static UNIT *cio_unit = &ni_unit[3]; - -MTAB ni_mod[] = { - { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "STATS", "STATS", - &ni_set_stats, &ni_show_stats, NULL, "Display or reset statistics" }, - { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "POLL", NULL, - NULL, &ni_show_poll, NULL, "Display the current polling mode" }, - { MTAB_XTD|MTAB_VDV|MTAB_VALR|MTAB_NC, 0, "MAC", "MAC=xx:xx:xx:xx:xx:xx", - &ni_setmac, &ni_showmac, NULL, "MAC address" }, - { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "FILTERS", NULL, - NULL, &ni_show_filters, NULL, "Display address filters" }, - { 0 } -}; - -static DEBTAB ni_debug[] = { - { "TRACE", DBG_TRACE, "trace routine calls" }, - { "IO", DBG_IO, "debug i/o" }, - { "CACHE", DBG_CACHE, "debug job cache" }, - { "PACKET", DBG_DAT, "display packet data" }, - { "ERR", DBG_ERR, "display errors" }, - { "ETH", DBG_ETH, "debug ethernet device" }, - { 0 } -}; - -DEVICE ni_dev = { - "NI", /* name */ - ni_unit, /* units */ - NULL, /* registers */ - ni_mod, /* modifiers */ - 4, /* #units */ - 16, /* address radix */ - 32, /* address width */ - 1, /* address incr. */ - 16, /* data radix */ - 8, /* data width */ - NULL, /* examine routine */ - NULL, /* deposit routine */ - &ni_reset, /* reset routine */ - NULL, /* boot routine */ - &ni_attach, /* attach routine */ - &ni_detach, /* detach routine */ - NULL, /* context */ - DEV_DISABLE|DEV_DIS| - DEV_DEBUG|DEV_ETHER, /* flags */ - 0, /* debug control flags */ - ni_debug, /* debug flag names */ - NULL, /* memory size change */ - NULL, /* logical name */ - &ni_help, /* help routine */ - NULL, /* attach help routine */ - NULL, /* help context */ - &ni_description, /* device description */ - NULL, -}; - -static void dump_packet(const char *direction, ETH_PACK *pkt) -{ - char dumpline[82]; - char *p; - uint32 char_offset, i; - - if (!direction) { - return; - } - - snprintf(dumpline, 10, "%08x ", 0); - char_offset = 9; - - for (i = 0; i < pkt->len; i++) { - snprintf(dumpline + char_offset, 4, "%02x ", pkt->msg[i]); - snprintf(dumpline + 61 + (i % 16), 2, "%c", CHAR(pkt->msg[i])); - char_offset += 3; - - if ((i + 1) % 16 == 0) { - - snprintf(dumpline + 56, 5, " |"); - snprintf(dumpline + 78, 2, "|"); - - for (p = dumpline; p < (dumpline + 80); p++) { - if (*p == '\0') { - *p = ' '; - } - } - *p = '\0'; - sim_debug(DBG_DAT, &ni_dev, - "[%s packet]: %s\n", direction, dumpline); - memset(dumpline, 0, 80); - snprintf(dumpline, 10, "%08x ", i + 1); - char_offset = 9; - } - } - - /* Finish any leftover bits */ - if ((i + 1) % 16 != 0) { - snprintf(dumpline + 56, 5, " |"); - snprintf(dumpline + 78, 2, "|"); - - for (p = dumpline; p < (dumpline + 80); p++) { - if (*p == '\0') { - *p = ' '; - } - } - *p = '\0'; - - sim_debug(DBG_DAT, &ni_dev, - "[%s packet]: %s\n", direction, dumpline); - } -} - -static void ni_enable() -{ - sim_debug(DBG_TRACE, &ni_dev, - "[ni_enable] Enabling the interface.\n"); - - /* Reset Statistics */ - memset(&ni.stats, 0, sizeof(ni_stat_info)); - - /* Clear out job cache */ - memset(&ni.job_cache, 0, sizeof(ni_job_cache) * 2); - - /* Enter fast polling mode */ - ni.poll_rate = NI_QPOLL_FAST; - - /* Start the queue poller in fast poll mode */ - sim_activate_abs(rq_unit, NI_QPOLL_FAST); - - /* Start the sanity timer */ - sim_activate_after(sanity_unit, NI_SANITY_INTERVAL_US); - - /* Enable the interface */ - ni.enabled = TRUE; -} - -static void ni_disable() -{ - sim_debug(DBG_TRACE, &ni_dev, - "[ni_disable] Disabling the interface.\n"); - ni.enabled = FALSE; - sim_cancel(ni_unit); - sim_cancel(rcv_unit); - sim_cancel(rq_unit); - sim_cancel(cio_unit); - sim_cancel(sanity_unit); - CIO_CLR_INT(ni.slot); -} - -static void ni_cmd(uint8 slot, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp) -{ - int i, j; - int32 delay; - uint16 hdrsize; - t_stat status; - int prot_info_offset; - cio_entry centry = {0}; - uint8 app_data[4] = {rapp_data[0], rapp_data[1], rapp_data[2], rapp_data[3]}; - - /* Assume some default values, but let the handlers below - * override these where appropriate */ - centry.opcode = CIO_SUCCESS; - centry.subdevice = rentry->subdevice; - centry.address = rentry->address; - - cio[slot].op = rentry->opcode; - - delay = NI_INT_DELAY; - - switch(rentry->opcode) { - case CIO_DLM: - for (i = 0; i < rentry->byte_count; i++) { - ni.crc = cio_crc32_shift(ni.crc, pread_b(rentry->address + i, BUS_PER)); - } - - centry.address = rentry->address + rentry->byte_count; - sim_debug(DBG_TRACE, &ni_dev, - "[ni_cmd] CIO Download Memory: bytecnt=%04x " - "addr=%08x return_addr=%08x subdev=%02x (CRC=%08x)\n", - rentry->byte_count, rentry->address, - centry.address, centry.subdevice, ni.crc); - - if (is_exp) { - cio_cexpress(slot, NIQESIZE, ¢ry, app_data); - } else { - cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); - } - - break; - case CIO_FCF: - sim_debug(DBG_TRACE, &ni_dev, - "[ni_cmd] CIO Force Function Call (CRC=%08x)\n", - ni.crc); - - /* If the currently running program is a diagnostics program, - * we are expected to write results into memory at address - * 0x200f000 */ - for (i = 0; NI_DIAG_CRCS[i] != 0; i++) { - if (ni.crc == NI_DIAG_CRCS[i]) { - pwrite_h(0x200f000, 0x1, BUS_PER); /* Test success */ - pwrite_h(0x200f002, 0x0, BUS_PER); /* Test Number */ - pwrite_h(0x200f004, 0x0, BUS_PER); /* Actual */ - pwrite_h(0x200f006, 0x0, BUS_PER); /* Expected */ - pwrite_b(0x200f008, 0x1, BUS_PER); /* Success flag again */ - break; - } - } - - /* Store the sequence byte we were sent for later reply. */ - ni.fcf_seq = rapp_data[3]; - - /* "Force Function Call" causes the CIO card to start running - * pumped code as a new process, taking over from its firmware - * ROM. As a result, a new sysgen is necessary to get the card - * in the right state. */ - - ni_disable(); - cio[slot].sysgen_s = 0; - - if (cio[slot].ivec == 0 || cio[slot].ivec == 3) { - cio_cexpress(slot, NIQESIZE, ¢ry, app_data); - } else { - cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); - } - - break; - case CIO_DSD: - /* Determine Sub-Devices. We have none. */ - sim_debug(DBG_TRACE, &ni_dev, - "[ni_cmd] Determine Sub-Devices.\n"); - - /* The system wants us to write sub-device structures at the - * supplied address */ - pwrite_h(rentry->address, 0x0, BUS_PER); - - if (is_exp) { - cio_cexpress(slot, NIQESIZE, ¢ry, app_data); - } else { - cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); - } - - break; - case NI_SETID: - sim_debug(DBG_TRACE, &ni_dev, - "[ni_cmd] NI SETID Operation\n"); - - /* Try to read the mac from memory */ - for (i = 0; i < MAC_SIZE_BYTES; i++) { - ni.mac_bytes[i] = pread_b(rentry->address + i, BUS_PER); - } - - snprintf(ni.mac_str, MAC_SIZE_CHARS, "%02x:%02x:%02x:%02x:%02x:%02x", - ni.mac_bytes[0], ni.mac_bytes[1], ni.mac_bytes[2], - ni.mac_bytes[3], ni.mac_bytes[4], ni.mac_bytes[5]); - - sim_debug(DBG_TRACE, &ni_dev, - "[ni_cmd] NI SETID: New MAC: %s\n", - ni.mac_str); - - status = ni_setmac(ni_dev.units, 0, ni.mac_str, NULL); - - cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); - - break; - case NI_TURNOFF: - sim_debug(DBG_TRACE, &ni_dev, - "[ni_cmd] NI TURNOFF Operation\n"); - - ni_disable(); - - cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); - - break; - case NI_TURNON: - sim_debug(DBG_TRACE, &ni_dev, - "[ni_cmd] NI TURNON Operation\n"); - - ni_enable(); - - cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); - - break; - case NI_STATS: - sim_debug(DBG_TRACE, &ni_dev, - "[ni_cmd] NI STATS Operation\n"); - - cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); - - break; - case NI_SEND: - case NI_SEND_A: - sim_debug(DBG_TRACE, &ni_dev, - "[ni_cmd] NI SEND Operation (opcode=%d)\n", - rentry->opcode); - - /* TODO: Why is this always 4 for a send? */ - centry.subdevice = 4; - - /* TODO: On the real 3B2, this appears to be some sort of - * checksum. Perhaps the packet checksum? I'm not sure. I need - * to run Wireshark on the real 3B2 and investigate. - * - * However, we're in luck: The driver code doesn't seem to - * validate it or check against it in any way, so we can - * put anything in there. - */ - centry.address = rentry->address; - centry.byte_count = rentry->byte_count; - - - /* If the interface is not attached, we can't actually send - * any packets. */ - if (!(rcv_unit->flags & UNIT_ATT)) { - ni.stats.tx_fail++; - centry.opcode = CIO_FAILURE; - sim_debug(DBG_TRACE, &ni_dev, - "[ni_cmd] NI SEND failure. Not attached. tx_fail=%d\n", - ni.stats.tx_fail); - break; - } - - /* Reset the write packet */ - ni.wr_buf.len = 0; - ni.wr_buf.oversize = NULL; - - /* Read the size of the header */ - hdrsize = pread_h(rentry->address + EIG_TABLE_SIZE, BUS_PER); - - /* Read out the packet frame */ - for (i = 0; i < rentry->byte_count; i++) { - ni.wr_buf.msg[i] = pread_b(rentry->address + PKT_START_OFFSET + i, BUS_PER); - } - - /* Get a pointer to the buffer containing the protocol data */ - prot_info_offset = 0; - i = 0; - do { - ni.prot.addr = pread_w(rentry->address + prot_info_offset, BUS_PER); - ni.prot.size = pread_h(rentry->address + prot_info_offset + 4, BUS_PER); - ni.prot.last = pread_h(rentry->address + prot_info_offset + 6, BUS_PER); - prot_info_offset += 8; - - /* Fill in the frame from this buffer */ - for (j=0; j < ni.prot.size; i++, j++) { - ni.wr_buf.msg[hdrsize + i] = pread_b(ni.prot.addr + j, BUS_PER); - } - } while (!ni.prot.last); - - /* Fill in packet details */ - ni.wr_buf.len = rentry->byte_count; - - sim_debug(DBG_IO, &ni_dev, - "[XMT] Transmitting a packet of size %d (0x%x)\n", - ni.wr_buf.len, ni.wr_buf.len); - - /* Send it */ - status = eth_write(ni.eth, &ni.wr_buf, NULL); - - if (status == SCPE_OK) { - if (ni_dev.dctrl & DBG_DAT) { - dump_packet("XMT", &ni.wr_buf); - } - ni.stats.tx_bytes += ni.wr_buf.len; - ni.stats.tx_pkt++; - } else { - ni.stats.tx_fail++; - centry.opcode = CIO_FAILURE; - } - - /* Weird behavior seen on the real 3B2's completion queue: If - * the byte count value is < 0xff, shift it! I really wish I - * understood this card... */ - if (centry.byte_count < 0xff) { - centry.byte_count <<= 8; - } - - cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); - - delay = 0; - - break; - default: - sim_debug(DBG_TRACE, &ni_dev, - "[ni_cmd] Opcode %d Not Handled Yet\n", - rentry->opcode); - - cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); - - break; - } - - sim_activate_abs(cio_unit, delay); -} - -t_stat ni_setmac(UNIT *uptr, int32 val, CONST char* cptr, void* desc) -{ - t_stat status; - - UNUSED(val); - UNUSED(desc); - - status = SCPE_OK; - status = eth_mac_scan_ex(&ni.macs[NI_NIC_MAC], cptr, uptr); - - if (status == SCPE_OK) { - eth_filter(ni.eth, ni.filter_count, ni.macs, 0, 0); - } else { - sim_debug(DBG_ERR, &ni_dev, - "[ni_setmac] Error in eth_mac_scan_ex. status=%d\n", status); - } - - return status; -} - -t_stat ni_showmac(FILE* st, UNIT* uptr, int32 val, CONST void* desc) -{ - char buffer[20]; - - UNUSED(uptr); - UNUSED(val); - UNUSED(desc); - - eth_mac_fmt(&ni.macs[NI_NIC_MAC], buffer); - fprintf(st, "MAC=%s", buffer); - return SCPE_OK; -} - -t_stat ni_show_filters(FILE* st, UNIT* uptr, int32 val, CONST void* desc) -{ - char buffer[20]; - int i; - - UNUSED(uptr); - UNUSED(val); - UNUSED(desc); - - eth_mac_fmt(&ni.macs[NI_NIC_MAC], buffer); - fprintf(st, "Physical Address=%s\n", buffer); - if (ni.filter_count > 0) { - fprintf(st, "Filters:\n"); - for (i=0; i < ni.filter_count; i++) { - eth_mac_fmt((ETH_MAC *) ni.macs[i], buffer); - fprintf(st, "[%2d]: %s\n", i, buffer); - } - fprintf(st, "\n"); - } - - return SCPE_OK; -} - -void ni_sysgen(uint8 slot) -{ - int i; - t_bool pumped = FALSE; - cio_entry cqe = {0}; - uint8 app_data[4] = {0}; - - ni_disable(); - - app_data[3] = 0x64; - cqe.opcode = CIO_SYSGEN_OK; - - sim_debug(DBG_TRACE, &ni_dev, - "[ni_sysgen] CIO SYSGEN. rqp=%08x, cqp=%08x, nrq=%d, rqs=%d cqs=%d\n", - cio[slot].rqp, cio[slot].cqp, cio[slot].no_rque, cio[slot].rqs, cio[slot].cqs); - - /* If the card has been successfully pumped, then we respond with - * a full completion queue entry. Otherwise, an express entry is - * used. */ - for (i = 0; NI_PUMP_CRCS[i] != 0; i++) { - if (NI_PUMP_CRCS[i] == ni.crc) { - cio_cqueue(slot, CIO_STAT, NIQESIZE, &cqe, app_data); - pumped = TRUE; - break; - } - } - - if (!pumped) { - cio_cexpress(slot, NIQESIZE, &cqe, app_data); - } - - /* Now clear out the old CRC value, in case the card needs to be - * sysgen'ed again later. */ - ni.crc = 0; - - sim_activate_abs(cio_unit, NI_INT_DELAY); -} - -/* - * Handler for CIO INT0 (express job) requests. - */ -void ni_express(uint8 slot) -{ - cio_entry rqe = {0}; - uint8 app_data[4] = {0}; - - sim_debug(DBG_TRACE, &ni_dev, - "[ni_express] Handling express CIO request.\n"); - - cio_rexpress(slot, NIQESIZE, &rqe, app_data); - ni_cmd(slot, &rqe, app_data, TRUE); -} - -/* - * Handler for CIO INT1 (full job) requests. - */ -void ni_full(uint8 slot) -{ - cio_entry rqe = {0}; - uint8 app_data[4] = {0}; - - sim_debug(DBG_TRACE, &ni_dev, - "[ni_full] INT1 received. Handling full CIO request.\n"); - - while (cio_cqueue_avail(slot, NIQESIZE) && - cio_rqueue(slot, GE_QUEUE, NIQESIZE, &rqe, app_data) == SCPE_OK) { - ni_cmd(slot, &rqe, app_data, FALSE); - } -} - -/* - * Handler for CIO RESET requests. - */ -void ni_cio_reset(uint8 slot) -{ - UNUSED(slot); - - ni_disable(); -} - -t_stat ni_reset(DEVICE *dptr) -{ - t_stat r; - uint8 slot; - char uname[16]; - - if (dptr->flags & DEV_DIS) { - cio_remove_all(NI_ID); - ni_conf = FALSE; - return SCPE_OK; - } - - if (!ni_conf) { - r = cio_install(NI_ID, "NI", NI_IPL, - &ni_express, &ni_full, &ni_sysgen, &ni_cio_reset, - &slot); - if (r != SCPE_OK) { - return r; - } - /* Remember the card slot */ - ni.slot = slot; - ni_conf = TRUE; - } - - /* Set an initial MAC address in the AT&T NI range */ - ni_setmac(ni_dev.units, 0, "80:00:10:03:00:00/32", NULL); - - /* Set up unit names */ - snprintf(uname, 16, "%s-RCV", dptr->name); - sim_set_uname(rcv_unit, uname); - snprintf(uname, 16, "%s-SANITY", dptr->name); - sim_set_uname(sanity_unit, uname); - snprintf(uname, 16, "%s-RQ", dptr->name); - sim_set_uname(rq_unit, uname); - snprintf(uname, 16, "%s-CIO", dptr->name); - sim_set_uname(cio_unit, uname); - - /* Ensure that the broadcast address is configured, and that we - * have a minmimum of two filters set. */ - memset(&ni.macs[NI_BCST_MAC], 0xff, sizeof(ETH_MAC)); - ni.filter_count = NI_FILTER_MIN; - - ni.poll_rate = NI_QPOLL_FAST; - - /* Make sure the transceiver is disabled and all - * polling activity and interrupts are disabled. */ - ni_disable(); - - return SCPE_OK; -} - -t_stat ni_rcv_svc(UNIT *uptr) -{ - t_stat read_succ; - - UNUSED(uptr); - - /* Since we cannot know which queue (large packet or small packet - * queue) will have room for the next packet that we read, for - * safety reasons we will not call eth_read() until we're certain - * there's room available in BOTH queues. */ - while (ni.enabled && NI_BUFFERS_AVAIL) { - read_succ = eth_read(ni.eth, &ni.rd_buf, NULL); - if (!read_succ) { - break; - } - /* Attempt to process the packet that was received. */ - ni_process_packet(); - } - - return SCPE_OK; -} - -/* - * Service used by the card to poll for available request queue - * entries. - */ -t_stat ni_rq_svc(UNIT *uptr) -{ - t_bool rq_taken; - int i, wp, no_rque; - cio_entry rqe = {0}; - uint8 slot[4] = {0}; - - UNUSED(uptr); - - rq_taken = FALSE; - no_rque = cio[ni.slot].no_rque - 1; - - for (i = 0; i < no_rque; i++) { - while (NI_CACHE_HAS_SPACE(i) && cio_rqueue(ni.slot, i+1, NIQESIZE, &rqe, slot) == SCPE_OK) { - sim_debug(DBG_CACHE, &ni_dev, - "[cache - FILL] %s packet entry. lp=%02x ulp=%02x " - "slot=%d addr=0x%08x\n", - i == 0 ? "Small" : "Large", - cio_r_lp(ni.slot, i+1, NIQESIZE), - cio_r_ulp(ni.slot, i+1, NIQESIZE), - slot[3], rqe.address); - wp = ni.job_cache[i].wp; - ni.job_cache[i].req[wp].addr = rqe.address; - ni.job_cache[i].req[wp].slot = slot[3]; - ni.job_cache[i].wp = (wp + 1) % NI_CACHE_LEN; - ni.stats.rq_taken++; - rq_taken = TRUE; - } - } - - /* Somewhat of a kludge, unfortunately. */ - if (ni.poll_rate == NI_QPOLL_FAST && ni.stats.rq_taken >= 6) { - sim_debug(DBG_TRACE, &ni_dev, - "[ni_rq_svc] Switching to slow poll mode.\n"); - ni.poll_rate = NI_QPOLL_SLOW; - } - - /* If any receive jobs were found, schedule a packet read right - away */ - if (rq_taken) { - sim_activate_abs(rcv_unit, 0); - } - - /* Reactivate the poller. */ - if (ni.poll_rate == NI_QPOLL_FAST) { - sim_activate_abs(rq_unit, NI_QPOLL_FAST); - } else { - sim_clock_coschedule(rq_unit, 1000); - } - - return SCPE_OK; -} - -/* - * The NI card uses a sanity timer to poke the host every few seconds - * and let it know that it is still alive. This service handling - * routine is responsible for scheduling these notifications. - * - * The NI driver expects these notifications to happen no more than 15 - * seconds apart. Unfortunately, I do not yet know the exact frequency - * with which the real hardware sends these updates, but it appears - * not to happen very frequently, so we'll have to settle for an - * educated guess of 10 seconds. - */ -t_stat ni_sanity_svc(UNIT *uptr) -{ - cio_entry cqe = {0}; - uint8 app_data[4] = {0}; - - UNUSED(uptr); - - sim_debug(DBG_TRACE, &ni_dev, - "[ni_sanity_svc] Firing sanity timer.\n"); - - cqe.opcode = NI_SANITY; - cio_cqueue(ni.slot, CIO_STAT, NIQESIZE, &cqe, app_data); - - CIO_SET_INT(ni.slot); - - sim_activate_after(sanity_unit, NI_SANITY_INTERVAL_US); - - return SCPE_OK; -} - -t_stat ni_cio_svc(UNIT *uptr) -{ - UNUSED(uptr); - - sim_debug(DBG_TRACE, &ni_dev, - "[ni_cio_svc] Handling a CIO service (Setting Interrupt) for board %d\n", ni.slot); - CIO_SET_INT(ni.slot); - - return SCPE_OK; -} - -/* - * Do the work of trying to process the most recently received packet - */ -void ni_process_packet() -{ - int i, rp; - uint32 addr; - uint8 slot; - cio_entry centry = {0}; - uint8 capp_data[4] = {0}; - int len = 0; - int que_num = 0; - uint8 *rbuf; - - len = ni.rd_buf.len; - rbuf = ni.rd_buf.msg; - que_num = len > SM_PKT_MAX ? LG_QUEUE : SM_QUEUE; - - sim_debug(DBG_IO, &ni_dev, - "[ni_process_packet] Receiving a packet of size %d (0x%x)\n", - len, len); - - /* Availability of a job in the job cache was checked before - * calling ni_process_packet(), so there is no need to check it - * again. */ - rp = ni.job_cache[que_num].rp; - addr = ni.job_cache[que_num].req[rp].addr; - slot = ni.job_cache[que_num].req[rp].slot; - ni.job_cache[que_num].rp = (rp + 1) % NI_CACHE_LEN; - sim_debug(DBG_CACHE, &ni_dev, - "[cache - DRAIN] %s packet entry. lp=%02x ulp=%02x " - "slot=%d addr=0x%08x\n", - que_num == 0 ? "Small" : "Large", - cio_r_lp(ni.slot, que_num+1, NIQESIZE), - cio_r_ulp(ni.slot, que_num+1, NIQESIZE), - slot, addr); - - /* Store the packet into main memory */ - for (i = 0; i < len; i++) { - pwrite_b(addr + i, rbuf[i], BUS_PER); - } - - if (ni_dev.dctrl & DBG_DAT) { - dump_packet("RCV", &ni.rd_buf); - } - - ni.stats.rx_pkt++; - ni.stats.rx_bytes += len; - - /* Build a reply CIO message */ - centry.subdevice = 4; /* TODO: Why is it always 4? */ - centry.opcode = 0; - centry.address = addr + len; - centry.byte_count = len; - capp_data[3] = slot; - - /* TODO: We should probably also check status here. */ - cio_cqueue(ni.slot, CIO_STAT, NIQESIZE, ¢ry, capp_data); - - /* Trigger an interrupt */ - CIO_SET_INT(ni.slot); -} - -t_stat ni_attach(UNIT *uptr, CONST char *cptr) -{ - t_stat status; - char *tptr; - - sim_debug(DBG_TRACE, &ni_dev, "ni_attach()\n"); - - tptr = (char *) malloc(strlen(cptr) + 1); - if (tptr == NULL) { - return SCPE_MEM; - } - strcpy(tptr, cptr); - - ni.eth = (ETH_DEV *) malloc(sizeof(ETH_DEV)); - if (!ni.eth) { - free(tptr); - return SCPE_MEM; - } - - status = eth_open(ni.eth, cptr, &ni_dev, DBG_ETH); - if (status != SCPE_OK) { - sim_debug(DBG_ERR, &ni_dev, "ni_attach failure: open\n"); - free(tptr); - free(ni.eth); - ni.eth = NULL; - return status; - } - - status = eth_check_address_conflict(ni.eth, &ni.macs[NI_NIC_MAC]); - if (status != SCPE_OK) { - sim_debug(DBG_ERR, &ni_dev, "ni_attach failure: mac check\n"); - eth_close(ni.eth); - free(tptr); - free(ni.eth); - ni.eth = NULL; - return status; - } - - /* Ensure the ethernet device is in async mode */ - /* TODO: Determine best latency */ - status = eth_set_async(ni.eth, 1000); - if (status != SCPE_OK) { - sim_debug(DBG_ERR, &ni_dev, "ni_attach failure: eth_set_async\n"); - eth_close(ni.eth); - free(tptr); - free(ni.eth); - ni.eth = NULL; - return status; - } - - uptr->filename = tptr; - uptr->flags |= UNIT_ATT; - - eth_filter(ni.eth, ni.filter_count, ni.macs, 0, 0); - - return SCPE_OK; -} - -t_stat ni_detach(UNIT *uptr) -{ - sim_debug(DBG_TRACE, &ni_dev, "ni_detach()\n"); - - if (uptr->flags & UNIT_ATT) { - /* TODO: Do we really want to disable here? Or is that ONLY FCF's job? */ - /* ni_disable(); */ - eth_close(ni.eth); - free(ni.eth); - ni.eth = NULL; - free(uptr->filename); - uptr->filename = NULL; - uptr->flags &= ~UNIT_ATT; - } - - return SCPE_OK; -} - -t_stat ni_set_stats(UNIT* uptr, int32 val, CONST char* cptr, void* desc) -{ - int init, elements, i; - uint32 *stats_array; - - UNUSED(uptr); - UNUSED(val); - UNUSED(cptr); - UNUSED(desc); - - if (cptr) { - init = atoi(cptr); - stats_array = (uint32 *)&ni.stats; - elements = sizeof(ni_stat_info) / sizeof(uint32); - - for (i = 0 ; i < elements; i++) { - stats_array[i] = init; - } - } else { - memset(&ni.stats, 0, sizeof(ni_stat_info)); - } - - - return SCPE_OK; -} - -t_stat ni_show_stats(FILE* st, UNIT* uptr, int32 val, CONST void* desc) -{ - const char *fmt = " %-15s%d\n"; - - UNUSED(uptr); - UNUSED(val); - UNUSED(desc); - - fprintf(st, "NI Ethernet statistics:\n"); - fprintf(st, fmt, "Recv:", ni.stats.rx_pkt); - fprintf(st, fmt, "Recv Bytes:", ni.stats.rx_bytes); - fprintf(st, fmt, "Xmit:", ni.stats.tx_pkt); - fprintf(st, fmt, "Xmit Bytes:", ni.stats.tx_bytes); - fprintf(st, fmt, "Xmit Fail:", ni.stats.tx_fail); - - eth_show_dev(st, ni.eth); - - return SCPE_OK; -} - -t_stat ni_show_poll(FILE *st, UNIT *uptr, int32 val, CONST void *desc) -{ - UNUSED(uptr); - UNUSED(val); - UNUSED(desc); - - if (ni.poll_rate == NI_QPOLL_FAST) { - fprintf(st, "polling=fast"); - } else { - fprintf(st, "polling=slow"); - } - - return SCPE_OK; -} - - -t_stat ni_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) -{ - const char help_string[] = - /****************************************************************************/ - " The Network Interface (NI) 10BASE5 controller is a Common I/O card for\n" - " the AT&T 3B2 that allows the 3B2 to connect to an Ethernet Local Area\n" - " Network (LAN).\n" - "1 Enabling\n" - " The simulator allows a single NI card to be configured in the first\n" - " available slot. The NI card is disabled at startup. To use the card\n" - " you must first enable it with the command:\n" - "\n" - "+sim> SET %D ENABLE\n" - "1 Configuration\n" - " By default, the card uses a self-assigned MAC address in the AT&T address\n" - " range (beginning with 80:00:10:03), however, another MAC may be set by\n" - " using the SET %D MAC command, e.g.:\n" - "\n" - "+sim> SET %D MAC=\n" - "\n" - " Please note, however, that the %D driver for AT&T System V R3 UNIX\n" - " always sets a MAC in the AT&T range through a software command.\n" - "1 Attaching\n" - " The %D card must be attached to a LAN device to communicate with systems\n" - " on the LAN.\n" - "\n" - " To get a list of available devices on your host platform, use the command:\n" - "\n" - "+sim> SHOW ETH\n" - "\n" - " After enabling the card, it can be attached to one of the host's\n" - " Ethernet devices with the ATTACH command. For example, depending on your\n" - " platform:\n" - "\n" - "+sim> ATTACH %D eth0\n" - "+sim> ATTACH %D en0\n" - "1 Dependencies\n" -#if defined(_WIN32) - " The WinPcap package must be installed in order to enable\n" - " communication with other computers on the local LAN.\n" - "\n" - " The WinPcap package is available from http://www.winpcap.org/\n" -#else - " To build simulators with the ability to communicate to other computers\n" - " on the local LAN, the libpcap development package must be installed on\n" - " the system which builds the simulator.\n" -#endif - "1 Performance\n" - " The simulated NI device is capable of much faster transfer speeds than\n" - " the real NI card in a 3B2, which was limited to a 10 Mbit pipe shared\n" - " between all hosts on the LAN.\n"; - - return scp_help(st, dptr, uptr, flag, help_string, cptr); -} - -const char *ni_description(DEVICE *dptr) -{ - UNUSED(dptr); - - return "NI 10BASE5 Ethernet controller"; -} +/* 3b2_ni.c: CM195A Network Interface CIO Card + + Copyright (c) 2018-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +/* + * NI is an intelligent feature card for the 3B2 that provides a + * 10BASE5 Ethernet interface. + + * Overview + * -------- + * + * The NI board is based on the Common I/O (CIO) platform. Like other + * CIO boards, it uses an 80186 embedded processor. The board and the + * 3B2 host communicate by reading and writing to the 3B2's main + * memory at locations established by the host via a series of job + * request and job completion queues. Only three interrupts are used: + * Two interrupts (80186 interrupts INT0 and INT1) are triggered by + * the 3B2 and tell the card when work is available in the request + * queue. One WE32100 interrupt (at a negotiated vector and predefined + * IPL) is used by the CIO board to tell the 3B2 that a new entry is + * available in the completion queue. + * + * The on-board ROM does not contain the full firmware required to + * perform all application-specific work. Rather, it is used only to + * bootstrap the 80186 and provide essential communication between the + * 3B2 host and the board's internal RAM. During initialization, the + * host must upload application-specific code to the board's RAM and + * cause the board to start running it. This is known as + * "pumping". The 80186 binary code for the NI board under System V + * Release 3 is stored in the file `/lib/pump/ni` + * + * Implementation Details + * ---------------------- + * + * The 10BASE5 interface on the NI board is driven by an Intel 82586 + * IEEE 802.3 LAN Coprocessor, controlled by the board's 80186 + * CPU. The 82586 is completely opaque to the host due to the nature + * of the CIO protocol. Nevertheless, an attempt is made to simulate + * the behavior of the 82586 where appropriate and possible. + * + * The NI board uses a sanity timer to occasionally write a watchdog + * or heartbeat entry into the completion queue, indicating that the + * Ethernet interface is still alive and that all is well. If the UNIX + * driver has not seen this heartbeat after approximately 10 seconds, + * it will consider the board to be in an "DOWN" state and send it a + * TERM ioctl. + * + * The NI board does behave differently from the other CIO boards in + * one respect: Unlike other CIO boards, the NI board takes jobs from + * its two Packet Receive CIO request queues by polling them, and then + * stores the taken jobs in a small 4-entry internal cache. It polls + * these queues quite rapidly in the real NI so it always has a full + * cache available for future incoming packets. To prevent performance + * issues, this simulation polls rapidly ("fast polling mode") only + * when absolutely necessary. Typically, that means only after the + * card has been reset, but before the request queues have finished + * being built by the 3B2 host. The UNIX NI driver expects and + * requires this behavior! + * + * Open Issues + * ----------- + * + * 1. The simulated card does not yet support setting or removing + * multicast Ethernet addresses. ioctl operations that attempt to + * set or remove multicast Ethernet addresses should silently + * fail. This will be supported in a future release. + * + */ + +#include "3b2_ni.h" + +#include "3b2_io.h" +#include "3b2_mem.h" +#include "3b2_stddev.h" + +/* State container for the card */ +NI_STATE ni; + +t_bool ni_conf = FALSE; + +/* Static Function Declarations */ +static void dump_packet(const char *direction, ETH_PACK *pkt); +static void ni_enable(); +static void ni_disable(); +static void ni_cmd(uint8 slot, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp); + +/* + * A list of pumped code CRCs that will cause Force Function Call to + * respond with "Test Passed". Must be null-terminated. + */ +static const uint32 NI_DIAG_CRCS[] = { + 0x795268a4, + 0xfab1057c, + 0x10ca00cd, + 0x9b3ddeda, + 0x267b19a0, + 0x123f36c0, + 0xc04ca0ab, + 0x96d0506e, /* Rev 3 NI, SVR 3.2.3 */ + 0x9d3fafde, /* Rev 3 NI, SVR 3.2.3 */ + 0, +}; + +/* + * A list of pumped code CRCs that will cause the sysgen routine to + * respond with a full completion request instead of an express + * completion request. Must be null-terminated. + */ +static const uint32 NI_PUMP_CRCS[] = { + 0xfab1057c, /* Rev 2 NI, SVR 3.x */ + 0xf6744bed, /* Rev 2 NI, SVR 3.x */ + 0x96d0506e, /* Rev 3 NI, SVR 3.2.3 */ + 0x9d3fafde, /* Rev 3 NI, SVR 3.2.3 */ + 0x3553230a, /* Rev 3 NI, SVR 3.2.3 */ + 0, +}; + +/* + * Unit 0: Packet reception. + * Unit 1: Sanity timer. + * Unit 2: Request Queue poller. + * Unit 3: CIO requests. + */ +UNIT ni_unit[] = { + { UDATA(&ni_rcv_svc, UNIT_IDLE|UNIT_ATTABLE, 0) }, + { UDATA(&ni_sanity_svc, UNIT_IDLE|UNIT_DIS, 0) }, + { UDATA(&ni_rq_svc, UNIT_IDLE|UNIT_DIS, 0) }, + { UDATA(&ni_cio_svc, UNIT_DIS, 0) }, + { 0 } +}; + +static UNIT *rcv_unit = &ni_unit[0]; +static UNIT *sanity_unit = &ni_unit[1]; +static UNIT *rq_unit = &ni_unit[2]; +static UNIT *cio_unit = &ni_unit[3]; + +MTAB ni_mod[] = { + { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "STATS", "STATS", + &ni_set_stats, &ni_show_stats, NULL, "Display or reset statistics" }, + { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "POLL", NULL, + NULL, &ni_show_poll, NULL, "Display the current polling mode" }, + { MTAB_XTD|MTAB_VDV|MTAB_VALR|MTAB_NC, 0, "MAC", "MAC=xx:xx:xx:xx:xx:xx", + &ni_setmac, &ni_showmac, NULL, "MAC address" }, + { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "FILTERS", NULL, + NULL, &ni_show_filters, NULL, "Display address filters" }, + { 0 } +}; + +static DEBTAB ni_debug[] = { + { "TRACE", DBG_TRACE, "trace routine calls" }, + { "IO", DBG_IO, "debug i/o" }, + { "CACHE", DBG_CACHE, "debug job cache" }, + { "PACKET", DBG_DAT, "display packet data" }, + { "ERR", DBG_ERR, "display errors" }, + { "ETH", DBG_ETH, "debug ethernet device" }, + { 0 } +}; + +DEVICE ni_dev = { + "NI", /* name */ + ni_unit, /* units */ + NULL, /* registers */ + ni_mod, /* modifiers */ + 4, /* #units */ + 16, /* address radix */ + 32, /* address width */ + 1, /* address incr. */ + 16, /* data radix */ + 8, /* data width */ + NULL, /* examine routine */ + NULL, /* deposit routine */ + &ni_reset, /* reset routine */ + NULL, /* boot routine */ + &ni_attach, /* attach routine */ + &ni_detach, /* detach routine */ + NULL, /* context */ + DEV_DISABLE|DEV_DIS| + DEV_DEBUG|DEV_ETHER, /* flags */ + 0, /* debug control flags */ + ni_debug, /* debug flag names */ + NULL, /* memory size change */ + NULL, /* logical name */ + &ni_help, /* help routine */ + NULL, /* attach help routine */ + NULL, /* help context */ + &ni_description, /* device description */ + NULL, +}; + +static void dump_packet(const char *direction, ETH_PACK *pkt) +{ + char dumpline[82]; + char *p; + uint32 char_offset, i; + + if (!direction) { + return; + } + + snprintf(dumpline, 10, "%08x ", 0); + char_offset = 9; + + for (i = 0; i < pkt->len; i++) { + snprintf(dumpline + char_offset, 4, "%02x ", pkt->msg[i]); + snprintf(dumpline + 61 + (i % 16), 2, "%c", CHAR(pkt->msg[i])); + char_offset += 3; + + if ((i + 1) % 16 == 0) { + + snprintf(dumpline + 56, 5, " |"); + snprintf(dumpline + 78, 2, "|"); + + for (p = dumpline; p < (dumpline + 80); p++) { + if (*p == '\0') { + *p = ' '; + } + } + *p = '\0'; + sim_debug(DBG_DAT, &ni_dev, + "[%s packet]: %s\n", direction, dumpline); + memset(dumpline, 0, 80); + snprintf(dumpline, 10, "%08x ", i + 1); + char_offset = 9; + } + } + + /* Finish any leftover bits */ + if ((i + 1) % 16 != 0) { + snprintf(dumpline + 56, 5, " |"); + snprintf(dumpline + 78, 2, "|"); + + for (p = dumpline; p < (dumpline + 80); p++) { + if (*p == '\0') { + *p = ' '; + } + } + *p = '\0'; + + sim_debug(DBG_DAT, &ni_dev, + "[%s packet]: %s\n", direction, dumpline); + } +} + +static void ni_enable() +{ + sim_debug(DBG_TRACE, &ni_dev, + "[ni_enable] Enabling the interface.\n"); + + /* Reset Statistics */ + memset(&ni.stats, 0, sizeof(ni_stat_info)); + + /* Clear out job cache */ + memset(&ni.job_cache, 0, sizeof(ni_job_cache) * 2); + + /* Enter fast polling mode */ + ni.poll_rate = NI_QPOLL_FAST; + + /* Start the queue poller in fast poll mode */ + sim_activate_abs(rq_unit, NI_QPOLL_FAST); + + /* Start the sanity timer */ + sim_activate_after(sanity_unit, NI_SANITY_INTERVAL_US); + + /* Enable the interface */ + ni.enabled = TRUE; +} + +static void ni_disable() +{ + sim_debug(DBG_TRACE, &ni_dev, + "[ni_disable] Disabling the interface.\n"); + ni.enabled = FALSE; + sim_cancel(ni_unit); + sim_cancel(rcv_unit); + sim_cancel(rq_unit); + sim_cancel(cio_unit); + sim_cancel(sanity_unit); + CIO_CLR_INT(ni.slot); +} + +static void ni_cmd(uint8 slot, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp) +{ + int i, j; + int32 delay; + uint16 hdrsize; + t_stat status; + int prot_info_offset; + cio_entry centry = {0}; + uint8 app_data[4] = {rapp_data[0], rapp_data[1], rapp_data[2], rapp_data[3]}; + + /* Assume some default values, but let the handlers below + * override these where appropriate */ + centry.opcode = CIO_SUCCESS; + centry.subdevice = rentry->subdevice; + centry.address = rentry->address; + + cio[slot].op = rentry->opcode; + + delay = NI_INT_DELAY; + + switch(rentry->opcode) { + case CIO_DLM: + for (i = 0; i < rentry->byte_count; i++) { + ni.crc = cio_crc32_shift(ni.crc, pread_b(rentry->address + i, BUS_PER)); + } + + centry.address = rentry->address + rentry->byte_count; + sim_debug(DBG_TRACE, &ni_dev, + "[ni_cmd] CIO Download Memory: bytecnt=%04x " + "addr=%08x return_addr=%08x subdev=%02x (CRC=%08x)\n", + rentry->byte_count, rentry->address, + centry.address, centry.subdevice, ni.crc); + + if (is_exp) { + cio_cexpress(slot, NIQESIZE, ¢ry, app_data); + } else { + cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); + } + + break; + case CIO_FCF: + sim_debug(DBG_TRACE, &ni_dev, + "[ni_cmd] CIO Force Function Call (CRC=%08x)\n", + ni.crc); + + /* If the currently running program is a diagnostics program, + * we are expected to write results into memory at address + * 0x200f000 */ + for (i = 0; NI_DIAG_CRCS[i] != 0; i++) { + if (ni.crc == NI_DIAG_CRCS[i]) { + pwrite_h(0x200f000, 0x1, BUS_PER); /* Test success */ + pwrite_h(0x200f002, 0x0, BUS_PER); /* Test Number */ + pwrite_h(0x200f004, 0x0, BUS_PER); /* Actual */ + pwrite_h(0x200f006, 0x0, BUS_PER); /* Expected */ + pwrite_b(0x200f008, 0x1, BUS_PER); /* Success flag again */ + break; + } + } + + /* Store the sequence byte we were sent for later reply. */ + ni.fcf_seq = rapp_data[3]; + + /* "Force Function Call" causes the CIO card to start running + * pumped code as a new process, taking over from its firmware + * ROM. As a result, a new sysgen is necessary to get the card + * in the right state. */ + + ni_disable(); + cio[slot].sysgen_s = 0; + + if (cio[slot].ivec == 0 || cio[slot].ivec == 3) { + cio_cexpress(slot, NIQESIZE, ¢ry, app_data); + } else { + cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); + } + + break; + case CIO_DSD: + /* Determine Sub-Devices. We have none. */ + sim_debug(DBG_TRACE, &ni_dev, + "[ni_cmd] Determine Sub-Devices.\n"); + + /* The system wants us to write sub-device structures at the + * supplied address */ + pwrite_h(rentry->address, 0x0, BUS_PER); + + if (is_exp) { + cio_cexpress(slot, NIQESIZE, ¢ry, app_data); + } else { + cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); + } + + break; + case NI_SETID: + sim_debug(DBG_TRACE, &ni_dev, + "[ni_cmd] NI SETID Operation\n"); + + /* Try to read the mac from memory */ + for (i = 0; i < MAC_SIZE_BYTES; i++) { + ni.mac_bytes[i] = pread_b(rentry->address + i, BUS_PER); + } + + snprintf(ni.mac_str, MAC_SIZE_CHARS, "%02x:%02x:%02x:%02x:%02x:%02x", + ni.mac_bytes[0], ni.mac_bytes[1], ni.mac_bytes[2], + ni.mac_bytes[3], ni.mac_bytes[4], ni.mac_bytes[5]); + + sim_debug(DBG_TRACE, &ni_dev, + "[ni_cmd] NI SETID: New MAC: %s\n", + ni.mac_str); + + status = ni_setmac(ni_dev.units, 0, ni.mac_str, NULL); + + cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); + + break; + case NI_TURNOFF: + sim_debug(DBG_TRACE, &ni_dev, + "[ni_cmd] NI TURNOFF Operation\n"); + + ni_disable(); + + cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); + + break; + case NI_TURNON: + sim_debug(DBG_TRACE, &ni_dev, + "[ni_cmd] NI TURNON Operation\n"); + + ni_enable(); + + cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); + + break; + case NI_STATS: + sim_debug(DBG_TRACE, &ni_dev, + "[ni_cmd] NI STATS Operation\n"); + + cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); + + break; + case NI_SEND: + case NI_SEND_A: + sim_debug(DBG_TRACE, &ni_dev, + "[ni_cmd] NI SEND Operation (opcode=%d)\n", + rentry->opcode); + + /* TODO: Why is this always 4 for a send? */ + centry.subdevice = 4; + + /* TODO: On the real 3B2, this appears to be some sort of + * checksum. Perhaps the packet checksum? I'm not sure. I need + * to run Wireshark on the real 3B2 and investigate. + * + * However, we're in luck: The driver code doesn't seem to + * validate it or check against it in any way, so we can + * put anything in there. + */ + centry.address = rentry->address; + centry.byte_count = rentry->byte_count; + + + /* If the interface is not attached, we can't actually send + * any packets. */ + if (!(rcv_unit->flags & UNIT_ATT)) { + ni.stats.tx_fail++; + centry.opcode = CIO_FAILURE; + sim_debug(DBG_TRACE, &ni_dev, + "[ni_cmd] NI SEND failure. Not attached. tx_fail=%d\n", + ni.stats.tx_fail); + break; + } + + /* Reset the write packet */ + ni.wr_buf.len = 0; + ni.wr_buf.oversize = NULL; + + /* Read the size of the header */ + hdrsize = pread_h(rentry->address + EIG_TABLE_SIZE, BUS_PER); + + /* Read out the packet frame */ + for (i = 0; i < rentry->byte_count; i++) { + ni.wr_buf.msg[i] = pread_b(rentry->address + PKT_START_OFFSET + i, BUS_PER); + } + + /* Get a pointer to the buffer containing the protocol data */ + prot_info_offset = 0; + i = 0; + do { + ni.prot.addr = pread_w(rentry->address + prot_info_offset, BUS_PER); + ni.prot.size = pread_h(rentry->address + prot_info_offset + 4, BUS_PER); + ni.prot.last = pread_h(rentry->address + prot_info_offset + 6, BUS_PER); + prot_info_offset += 8; + + /* Fill in the frame from this buffer */ + for (j=0; j < ni.prot.size; i++, j++) { + ni.wr_buf.msg[hdrsize + i] = pread_b(ni.prot.addr + j, BUS_PER); + } + } while (!ni.prot.last); + + /* Fill in packet details */ + ni.wr_buf.len = rentry->byte_count; + + sim_debug(DBG_IO, &ni_dev, + "[XMT] Transmitting a packet of size %d (0x%x)\n", + ni.wr_buf.len, ni.wr_buf.len); + + /* Send it */ + status = eth_write(ni.eth, &ni.wr_buf, NULL); + + if (status == SCPE_OK) { + if (ni_dev.dctrl & DBG_DAT) { + dump_packet("XMT", &ni.wr_buf); + } + ni.stats.tx_bytes += ni.wr_buf.len; + ni.stats.tx_pkt++; + } else { + ni.stats.tx_fail++; + centry.opcode = CIO_FAILURE; + } + + /* Weird behavior seen on the real 3B2's completion queue: If + * the byte count value is < 0xff, shift it! I really wish I + * understood this card... */ + if (centry.byte_count < 0xff) { + centry.byte_count <<= 8; + } + + cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); + + delay = 0; + + break; + default: + sim_debug(DBG_TRACE, &ni_dev, + "[ni_cmd] Opcode %d Not Handled Yet\n", + rentry->opcode); + + cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); + + break; + } + + sim_activate_abs(cio_unit, delay); +} + +t_stat ni_setmac(UNIT *uptr, int32 val, CONST char* cptr, void* desc) +{ + t_stat status; + + UNUSED(val); + UNUSED(desc); + + status = SCPE_OK; + status = eth_mac_scan_ex(&ni.macs[NI_NIC_MAC], cptr, uptr); + + if (status == SCPE_OK) { + eth_filter(ni.eth, ni.filter_count, ni.macs, 0, 0); + } else { + sim_debug(DBG_ERR, &ni_dev, + "[ni_setmac] Error in eth_mac_scan_ex. status=%d\n", status); + } + + return status; +} + +t_stat ni_showmac(FILE* st, UNIT* uptr, int32 val, CONST void* desc) +{ + char buffer[20]; + + UNUSED(uptr); + UNUSED(val); + UNUSED(desc); + + eth_mac_fmt(&ni.macs[NI_NIC_MAC], buffer); + fprintf(st, "MAC=%s", buffer); + return SCPE_OK; +} + +t_stat ni_show_filters(FILE* st, UNIT* uptr, int32 val, CONST void* desc) +{ + char buffer[20]; + int i; + + UNUSED(uptr); + UNUSED(val); + UNUSED(desc); + + eth_mac_fmt(&ni.macs[NI_NIC_MAC], buffer); + fprintf(st, "Physical Address=%s\n", buffer); + if (ni.filter_count > 0) { + fprintf(st, "Filters:\n"); + for (i=0; i < ni.filter_count; i++) { + eth_mac_fmt((ETH_MAC *) ni.macs[i], buffer); + fprintf(st, "[%2d]: %s\n", i, buffer); + } + fprintf(st, "\n"); + } + + return SCPE_OK; +} + +void ni_sysgen(uint8 slot) +{ + int i; + t_bool pumped = FALSE; + cio_entry cqe = {0}; + uint8 app_data[4] = {0}; + + ni_disable(); + + app_data[3] = 0x64; + cqe.opcode = CIO_SYSGEN_OK; + + sim_debug(DBG_TRACE, &ni_dev, + "[ni_sysgen] CIO SYSGEN. rqp=%08x, cqp=%08x, nrq=%d, rqs=%d cqs=%d\n", + cio[slot].rqp, cio[slot].cqp, cio[slot].no_rque, cio[slot].rqs, cio[slot].cqs); + + /* If the card has been successfully pumped, then we respond with + * a full completion queue entry. Otherwise, an express entry is + * used. */ + for (i = 0; NI_PUMP_CRCS[i] != 0; i++) { + if (NI_PUMP_CRCS[i] == ni.crc) { + cio_cqueue(slot, CIO_STAT, NIQESIZE, &cqe, app_data); + pumped = TRUE; + break; + } + } + + if (!pumped) { + cio_cexpress(slot, NIQESIZE, &cqe, app_data); + } + + /* Now clear out the old CRC value, in case the card needs to be + * sysgen'ed again later. */ + ni.crc = 0; + + sim_activate_abs(cio_unit, NI_INT_DELAY); +} + +/* + * Handler for CIO INT0 (express job) requests. + */ +void ni_express(uint8 slot) +{ + cio_entry rqe = {0}; + uint8 app_data[4] = {0}; + + sim_debug(DBG_TRACE, &ni_dev, + "[ni_express] Handling express CIO request.\n"); + + cio_rexpress(slot, NIQESIZE, &rqe, app_data); + ni_cmd(slot, &rqe, app_data, TRUE); +} + +/* + * Handler for CIO INT1 (full job) requests. + */ +void ni_full(uint8 slot) +{ + cio_entry rqe = {0}; + uint8 app_data[4] = {0}; + + sim_debug(DBG_TRACE, &ni_dev, + "[ni_full] INT1 received. Handling full CIO request.\n"); + + while (cio_cqueue_avail(slot, NIQESIZE) && + cio_rqueue(slot, GE_QUEUE, NIQESIZE, &rqe, app_data) == SCPE_OK) { + ni_cmd(slot, &rqe, app_data, FALSE); + } +} + +/* + * Handler for CIO RESET requests. + */ +void ni_cio_reset(uint8 slot) +{ + UNUSED(slot); + + ni_disable(); +} + +t_stat ni_reset(DEVICE *dptr) +{ + t_stat r; + uint8 slot; + char uname[16]; + + if (dptr->flags & DEV_DIS) { + cio_remove_all(NI_ID); + ni_conf = FALSE; + return SCPE_OK; + } + + if (!ni_conf) { + r = cio_install(NI_ID, "NI", NI_IPL, + &ni_express, &ni_full, &ni_sysgen, &ni_cio_reset, + &slot); + if (r != SCPE_OK) { + return r; + } + /* Remember the card slot */ + ni.slot = slot; + ni_conf = TRUE; + } + + /* Set an initial MAC address in the AT&T NI range */ + ni_setmac(ni_dev.units, 0, "80:00:10:03:00:00/32", NULL); + + /* Set up unit names */ + snprintf(uname, 16, "%s-RCV", dptr->name); + sim_set_uname(rcv_unit, uname); + snprintf(uname, 16, "%s-SANITY", dptr->name); + sim_set_uname(sanity_unit, uname); + snprintf(uname, 16, "%s-RQ", dptr->name); + sim_set_uname(rq_unit, uname); + snprintf(uname, 16, "%s-CIO", dptr->name); + sim_set_uname(cio_unit, uname); + + /* Ensure that the broadcast address is configured, and that we + * have a minmimum of two filters set. */ + memset(&ni.macs[NI_BCST_MAC], 0xff, sizeof(ETH_MAC)); + ni.filter_count = NI_FILTER_MIN; + + ni.poll_rate = NI_QPOLL_FAST; + + /* Make sure the transceiver is disabled and all + * polling activity and interrupts are disabled. */ + ni_disable(); + + return SCPE_OK; +} + +t_stat ni_rcv_svc(UNIT *uptr) +{ + t_stat read_succ; + + UNUSED(uptr); + + /* Since we cannot know which queue (large packet or small packet + * queue) will have room for the next packet that we read, for + * safety reasons we will not call eth_read() until we're certain + * there's room available in BOTH queues. */ + while (ni.enabled && NI_BUFFERS_AVAIL) { + read_succ = eth_read(ni.eth, &ni.rd_buf, NULL); + if (!read_succ) { + break; + } + /* Attempt to process the packet that was received. */ + ni_process_packet(); + } + + return SCPE_OK; +} + +/* + * Service used by the card to poll for available request queue + * entries. + */ +t_stat ni_rq_svc(UNIT *uptr) +{ + t_bool rq_taken; + int i, wp, no_rque; + cio_entry rqe = {0}; + uint8 slot[4] = {0}; + + UNUSED(uptr); + + rq_taken = FALSE; + no_rque = cio[ni.slot].no_rque - 1; + + for (i = 0; i < no_rque; i++) { + while (NI_CACHE_HAS_SPACE(i) && cio_rqueue(ni.slot, i+1, NIQESIZE, &rqe, slot) == SCPE_OK) { + sim_debug(DBG_CACHE, &ni_dev, + "[cache - FILL] %s packet entry. lp=%02x ulp=%02x " + "slot=%d addr=0x%08x\n", + i == 0 ? "Small" : "Large", + cio_r_lp(ni.slot, i+1, NIQESIZE), + cio_r_ulp(ni.slot, i+1, NIQESIZE), + slot[3], rqe.address); + wp = ni.job_cache[i].wp; + ni.job_cache[i].req[wp].addr = rqe.address; + ni.job_cache[i].req[wp].slot = slot[3]; + ni.job_cache[i].wp = (wp + 1) % NI_CACHE_LEN; + ni.stats.rq_taken++; + rq_taken = TRUE; + } + } + + /* Somewhat of a kludge, unfortunately. */ + if (ni.poll_rate == NI_QPOLL_FAST && ni.stats.rq_taken >= 6) { + sim_debug(DBG_TRACE, &ni_dev, + "[ni_rq_svc] Switching to slow poll mode.\n"); + ni.poll_rate = NI_QPOLL_SLOW; + } + + /* If any receive jobs were found, schedule a packet read right + away */ + if (rq_taken) { + sim_activate_abs(rcv_unit, 0); + } + + /* Reactivate the poller. */ + if (ni.poll_rate == NI_QPOLL_FAST) { + sim_activate_abs(rq_unit, NI_QPOLL_FAST); + } else { + sim_clock_coschedule(rq_unit, 1000); + } + + return SCPE_OK; +} + +/* + * The NI card uses a sanity timer to poke the host every few seconds + * and let it know that it is still alive. This service handling + * routine is responsible for scheduling these notifications. + * + * The NI driver expects these notifications to happen no more than 15 + * seconds apart. Unfortunately, I do not yet know the exact frequency + * with which the real hardware sends these updates, but it appears + * not to happen very frequently, so we'll have to settle for an + * educated guess of 10 seconds. + */ +t_stat ni_sanity_svc(UNIT *uptr) +{ + cio_entry cqe = {0}; + uint8 app_data[4] = {0}; + + UNUSED(uptr); + + sim_debug(DBG_TRACE, &ni_dev, + "[ni_sanity_svc] Firing sanity timer.\n"); + + cqe.opcode = NI_SANITY; + cio_cqueue(ni.slot, CIO_STAT, NIQESIZE, &cqe, app_data); + + CIO_SET_INT(ni.slot); + + sim_activate_after(sanity_unit, NI_SANITY_INTERVAL_US); + + return SCPE_OK; +} + +t_stat ni_cio_svc(UNIT *uptr) +{ + UNUSED(uptr); + + sim_debug(DBG_TRACE, &ni_dev, + "[ni_cio_svc] Handling a CIO service (Setting Interrupt) for board %d\n", ni.slot); + CIO_SET_INT(ni.slot); + + return SCPE_OK; +} + +/* + * Do the work of trying to process the most recently received packet + */ +void ni_process_packet() +{ + int i, rp; + uint32 addr; + uint8 slot; + cio_entry centry = {0}; + uint8 capp_data[4] = {0}; + int len = 0; + int que_num = 0; + uint8 *rbuf; + + len = ni.rd_buf.len; + rbuf = ni.rd_buf.msg; + que_num = len > SM_PKT_MAX ? LG_QUEUE : SM_QUEUE; + + sim_debug(DBG_IO, &ni_dev, + "[ni_process_packet] Receiving a packet of size %d (0x%x)\n", + len, len); + + /* Availability of a job in the job cache was checked before + * calling ni_process_packet(), so there is no need to check it + * again. */ + rp = ni.job_cache[que_num].rp; + addr = ni.job_cache[que_num].req[rp].addr; + slot = ni.job_cache[que_num].req[rp].slot; + ni.job_cache[que_num].rp = (rp + 1) % NI_CACHE_LEN; + sim_debug(DBG_CACHE, &ni_dev, + "[cache - DRAIN] %s packet entry. lp=%02x ulp=%02x " + "slot=%d addr=0x%08x\n", + que_num == 0 ? "Small" : "Large", + cio_r_lp(ni.slot, que_num+1, NIQESIZE), + cio_r_ulp(ni.slot, que_num+1, NIQESIZE), + slot, addr); + + /* Store the packet into main memory */ + for (i = 0; i < len; i++) { + pwrite_b(addr + i, rbuf[i], BUS_PER); + } + + if (ni_dev.dctrl & DBG_DAT) { + dump_packet("RCV", &ni.rd_buf); + } + + ni.stats.rx_pkt++; + ni.stats.rx_bytes += len; + + /* Build a reply CIO message */ + centry.subdevice = 4; /* TODO: Why is it always 4? */ + centry.opcode = 0; + centry.address = addr + len; + centry.byte_count = len; + capp_data[3] = slot; + + /* TODO: We should probably also check status here. */ + cio_cqueue(ni.slot, CIO_STAT, NIQESIZE, ¢ry, capp_data); + + /* Trigger an interrupt */ + CIO_SET_INT(ni.slot); +} + +t_stat ni_attach(UNIT *uptr, CONST char *cptr) +{ + t_stat status; + char *tptr; + + sim_debug(DBG_TRACE, &ni_dev, "ni_attach()\n"); + + tptr = (char *) malloc(strlen(cptr) + 1); + if (tptr == NULL) { + return SCPE_MEM; + } + strcpy(tptr, cptr); + + ni.eth = (ETH_DEV *) malloc(sizeof(ETH_DEV)); + if (!ni.eth) { + free(tptr); + return SCPE_MEM; + } + + status = eth_open(ni.eth, cptr, &ni_dev, DBG_ETH); + if (status != SCPE_OK) { + sim_debug(DBG_ERR, &ni_dev, "ni_attach failure: open\n"); + free(tptr); + free(ni.eth); + ni.eth = NULL; + return status; + } + + status = eth_check_address_conflict(ni.eth, &ni.macs[NI_NIC_MAC]); + if (status != SCPE_OK) { + sim_debug(DBG_ERR, &ni_dev, "ni_attach failure: mac check\n"); + eth_close(ni.eth); + free(tptr); + free(ni.eth); + ni.eth = NULL; + return status; + } + + /* Ensure the ethernet device is in async mode */ + /* TODO: Determine best latency */ + status = eth_set_async(ni.eth, 1000); + if (status != SCPE_OK) { + sim_debug(DBG_ERR, &ni_dev, "ni_attach failure: eth_set_async\n"); + eth_close(ni.eth); + free(tptr); + free(ni.eth); + ni.eth = NULL; + return status; + } + + uptr->filename = tptr; + uptr->flags |= UNIT_ATT; + + eth_filter(ni.eth, ni.filter_count, ni.macs, 0, 0); + + return SCPE_OK; +} + +t_stat ni_detach(UNIT *uptr) +{ + sim_debug(DBG_TRACE, &ni_dev, "ni_detach()\n"); + + if (uptr->flags & UNIT_ATT) { + /* TODO: Do we really want to disable here? Or is that ONLY FCF's job? */ + /* ni_disable(); */ + eth_close(ni.eth); + free(ni.eth); + ni.eth = NULL; + free(uptr->filename); + uptr->filename = NULL; + uptr->flags &= ~UNIT_ATT; + } + + return SCPE_OK; +} + +t_stat ni_set_stats(UNIT* uptr, int32 val, CONST char* cptr, void* desc) +{ + int init, elements, i; + uint32 *stats_array; + + UNUSED(uptr); + UNUSED(val); + UNUSED(cptr); + UNUSED(desc); + + if (cptr) { + init = atoi(cptr); + stats_array = (uint32 *)&ni.stats; + elements = sizeof(ni_stat_info) / sizeof(uint32); + + for (i = 0 ; i < elements; i++) { + stats_array[i] = init; + } + } else { + memset(&ni.stats, 0, sizeof(ni_stat_info)); + } + + + return SCPE_OK; +} + +t_stat ni_show_stats(FILE* st, UNIT* uptr, int32 val, CONST void* desc) +{ + const char *fmt = " %-15s%d\n"; + + UNUSED(uptr); + UNUSED(val); + UNUSED(desc); + + fprintf(st, "NI Ethernet statistics:\n"); + fprintf(st, fmt, "Recv:", ni.stats.rx_pkt); + fprintf(st, fmt, "Recv Bytes:", ni.stats.rx_bytes); + fprintf(st, fmt, "Xmit:", ni.stats.tx_pkt); + fprintf(st, fmt, "Xmit Bytes:", ni.stats.tx_bytes); + fprintf(st, fmt, "Xmit Fail:", ni.stats.tx_fail); + + eth_show_dev(st, ni.eth); + + return SCPE_OK; +} + +t_stat ni_show_poll(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + UNUSED(uptr); + UNUSED(val); + UNUSED(desc); + + if (ni.poll_rate == NI_QPOLL_FAST) { + fprintf(st, "polling=fast"); + } else { + fprintf(st, "polling=slow"); + } + + return SCPE_OK; +} + + +t_stat ni_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + const char help_string[] = + /****************************************************************************/ + " The Network Interface (NI) 10BASE5 controller is a Common I/O card for\n" + " the AT&T 3B2 that allows the 3B2 to connect to an Ethernet Local Area\n" + " Network (LAN).\n" + "1 Enabling\n" + " The simulator allows a single NI card to be configured in the first\n" + " available slot. The NI card is disabled at startup. To use the card\n" + " you must first enable it with the command:\n" + "\n" + "+sim> SET %D ENABLE\n" + "1 Configuration\n" + " By default, the card uses a self-assigned MAC address in the AT&T address\n" + " range (beginning with 80:00:10:03), however, another MAC may be set by\n" + " using the SET %D MAC command, e.g.:\n" + "\n" + "+sim> SET %D MAC=\n" + "\n" + " Please note, however, that the %D driver for AT&T System V R3 UNIX\n" + " always sets a MAC in the AT&T range through a software command.\n" + "1 Attaching\n" + " The %D card must be attached to a LAN device to communicate with systems\n" + " on the LAN.\n" + "\n" + " To get a list of available devices on your host platform, use the command:\n" + "\n" + "+sim> SHOW ETH\n" + "\n" + " After enabling the card, it can be attached to one of the host's\n" + " Ethernet devices with the ATTACH command. For example, depending on your\n" + " platform:\n" + "\n" + "+sim> ATTACH %D eth0\n" + "+sim> ATTACH %D en0\n" + "1 Dependencies\n" +#if defined(_WIN32) + " The WinPcap package must be installed in order to enable\n" + " communication with other computers on the local LAN.\n" + "\n" + " The WinPcap package is available from http://www.winpcap.org/\n" +#else + " To build simulators with the ability to communicate to other computers\n" + " on the local LAN, the libpcap development package must be installed on\n" + " the system which builds the simulator.\n" +#endif + "1 Performance\n" + " The simulated NI device is capable of much faster transfer speeds than\n" + " the real NI card in a 3B2, which was limited to a 10 Mbit pipe shared\n" + " between all hosts on the LAN.\n"; + + return scp_help(st, dptr, uptr, flag, help_string, cptr); +} + +const char *ni_description(DEVICE *dptr) +{ + UNUSED(dptr); + + return "NI 10BASE5 Ethernet controller"; +} diff --git a/3B2/3b2_ni.h b/3B2/3b2_ni.h index 99234104..2e146ea8 100644 --- a/3B2/3b2_ni.h +++ b/3B2/3b2_ni.h @@ -1,210 +1,210 @@ -/* 3b2_ni.h: CM195A Network Interface CIO Card - - Copyright (c) 2018-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#ifndef _3B2_NI_H_ -#define _3B2_NI_H_ - -#include "3b2_defs.h" -#include "sim_ether.h" - -#define NI_ID 0x0002 -#define NI_IPL 12 - -/* Opcodes for NI card */ - -#define NI_SETID 6 -#define NI_TURNOFF 7 -#define NI_TURNON 8 -#define NI_SEND 11 -#define NI_RECV 12 -#define NI_STATS 13 -#define NI_SANITY 15 -#define NI_SEND_A 22 - -#define MAC_SIZE_BYTES 6 -#define MAC_SIZE_CHARS 20 - -#define NIQESIZE 12 -#define NI_QUE_MAX 1024 -#define NI_INT_DELAY 10000 -#define NI_SANITY_INTERVAL_US 5000000 - -/* Maximum allowed number of multicast addresses */ -#define NI_MULTI_MAX 64 - -/* At least two filter addresses are always configured: - * 1. The host MAC - * 2. The broadcast address */ -#define NI_FILTER_MIN 2 - -/* Maximum total allowed number of filter addresses, including the - * host's MAC and the broadcast address. */ -#define NI_FILTER_MAX NI_MULTI_MAX + NI_FILTER_MIN - -/* Indexes in the internal filter address table of the - * host's MAC and the broadcast address */ -#define NI_NIC_MAC 0 -#define NI_BCST_MAC 1 - -/* - * For performance reasons, there are two modes of polling the receive - * queues. Initially, polling is VERY aggressive as we race the - * filling of the receive queues. Once we've taken three jobs from - * each of the two receive queues, we switch to slow polling, - * which uses coscheduling. - */ - -#define NI_QPOLL_FAST 100 -#define NI_QPOLL_SLOW 50000 - -#define EIG_TABLE_SIZE 40 -#define PKT_HEADER_LEN_OFFSET EIG_TABLE_SIZE -#define PKT_START_OFFSET (PKT_HEADER_LEN_OFFSET + 4) - -/* - * The NI card has two request queues for packet receive: One for - * small packets, and one for large packets. The small queue is meant - * for packets smaller than 128 bytes. The large queue is meant for - * packets up to 1500 bytes (no jumbo frames allowed) - */ - -#define GE_QUEUE 0 /* General request CIO queue */ -#define SM_QUEUE 0 /* Small packet receive queue number */ -#define LG_QUEUE 1 /* Large packet receive queue number */ -#define SM_PKT_MAX 106 /* Max size of small packets (excluding CRC) */ -#define LG_PKT_MAX 1514 /* Max size of large packets (excluding CRC) */ - -/* - * NI-specific debugging flags - */ -#define DBG_TRACE 0x01 -#define DBG_IO 0x02 -#define DBG_CACHE 0x04 -#define DBG_DAT 0x08 -#define DBG_ERR 0x10 -#define DBG_ETH 0x20 - -#define CHAR(c) ((((c) >= 0x20) && ((c) < 0x7f)) ? (c) : '.') - -#define NI_CACHE_HAS_SPACE(i) (((ni.job_cache[(i)].wp + 1) % NI_CACHE_LEN) != ni.job_cache[(i)].rp) -/* Determine whether both job caches have available slots */ -#define NI_BUFFERS_AVAIL ((ni.job_cache[0].wp != ni.job_cache[0].rp) && \ - (ni.job_cache[1].wp != ni.job_cache[1].rp)) - -/* - * The NI card caches up to three jobs taken from each of the two - * packet receive queues so that they are available immediately after - * receipt of a packet. These jobs are kept in small circular buffers. - * Each job is represented by an ni_rec_job structure, containing a - * buffer pointer and a slot number. The slot number is used by both - * the driver and the firmware to correlate a packet receive buffer - * with a completion queue event. - */ -typedef struct { - uint32 addr; /* address of job's buffer */ - uint8 slot; /* slot number of the job */ -} ni_rec_job; - -#define NI_CACHE_LEN 4 - -typedef struct { - ni_rec_job req[NI_CACHE_LEN]; /* the cache */ - int wp; /* write pointer */ - int rp; /* read pointer */ -} ni_job_cache; - -/* - * When the NI driver submits a packet send request to the general - * request queue, it constructs one or more ni_prot_info structs in - * main memory that point to the protocol-specific byte data of the - * packet (minus the Ethernet frame). These structs are packed one - * after the other following the Ethernet frame header in the job's - * request buffer. The last entry has its "last" bit set to non-zero. - */ -typedef struct { - uint32 addr; /* Physical address of the buffer in system RAM */ - uint16 size; /* Length of the buffer */ - uint16 last; /* Is this the last entry in the list? */ -} ni_prot_info; - -typedef struct { - uint32 rq_taken; - uint32 tx_fail; - uint32 rx_dropped; - uint32 rx_pkt; - uint32 tx_pkt; - uint32 rx_bytes; - uint32 tx_bytes; -} ni_stat_info; - -typedef struct { - uint8 slot; - t_bool enabled; - uint32 crc; - uint32 poll_rate; - char mac_str[MAC_SIZE_CHARS]; - uint8 mac_bytes[MAC_SIZE_BYTES]; - ni_job_cache job_cache[2]; - ni_prot_info prot; - ni_stat_info stats; - uint8 fcf_seq; - ETH_DEV* eth; - ETH_PACK rd_buf; - ETH_PACK wr_buf; - ETH_MAC macs[NI_FILTER_MAX]; /* List of all filter addresses */ - int filter_count; /* Number of filters available */ - ETH_PCALLBACK callback; -} NI_STATE; - -void ni_recv_callback(int status); -t_stat ni_reset(DEVICE *dptr); -t_stat ni_rcv_svc(UNIT *uptr); -t_stat ni_sanity_svc(UNIT *uptr); -t_stat ni_rq_svc(UNIT *uptr); -t_stat ni_cio_svc(UNIT *uptr); -t_stat ni_attach(UNIT *uptr, CONST char *cptr); -t_stat ni_detach(UNIT *uptr); -t_stat ni_setmac(UNIT *uptr, int32 val, CONST char *cptr, void *desc); -t_stat ni_showmac(FILE* st, UNIT *uptr, int32 val, CONST void *desc); -t_stat ni_try_job(uint8 slot); -t_stat ni_show_stats(FILE *st, UNIT *uptr, int32 val, CONST void *desc); -t_stat ni_set_stats(UNIT *uptr, int32 val, CONST char *cptr, void *desc); -t_stat ni_show_poll(FILE *st, UNIT *uptr, int32 val, CONST void *desc); -t_stat ni_show_filters(FILE *st, UNIT *uptr, int32 val, CONST void *desc); -const char *ni_description(DEVICE *dptr); -t_stat ni_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); -void ni_cio_reset(uint8 slot); -void ni_process_packet(); -void ni_int_ack(uint8 slot); -void ni_sysgen(uint8 slot); -void ni_express(uint8 slot); -void ni_full(uint8 slot); - -#endif /* _3B2_NI_H_ */ +/* 3b2_ni.h: CM195A Network Interface CIO Card + + Copyright (c) 2018-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#ifndef _3B2_NI_H_ +#define _3B2_NI_H_ + +#include "3b2_defs.h" +#include "sim_ether.h" + +#define NI_ID 0x0002 +#define NI_IPL 12 + +/* Opcodes for NI card */ + +#define NI_SETID 6 +#define NI_TURNOFF 7 +#define NI_TURNON 8 +#define NI_SEND 11 +#define NI_RECV 12 +#define NI_STATS 13 +#define NI_SANITY 15 +#define NI_SEND_A 22 + +#define MAC_SIZE_BYTES 6 +#define MAC_SIZE_CHARS 20 + +#define NIQESIZE 12 +#define NI_QUE_MAX 1024 +#define NI_INT_DELAY 10000 +#define NI_SANITY_INTERVAL_US 5000000 + +/* Maximum allowed number of multicast addresses */ +#define NI_MULTI_MAX 64 + +/* At least two filter addresses are always configured: + * 1. The host MAC + * 2. The broadcast address */ +#define NI_FILTER_MIN 2 + +/* Maximum total allowed number of filter addresses, including the + * host's MAC and the broadcast address. */ +#define NI_FILTER_MAX NI_MULTI_MAX + NI_FILTER_MIN + +/* Indexes in the internal filter address table of the + * host's MAC and the broadcast address */ +#define NI_NIC_MAC 0 +#define NI_BCST_MAC 1 + +/* + * For performance reasons, there are two modes of polling the receive + * queues. Initially, polling is VERY aggressive as we race the + * filling of the receive queues. Once we've taken three jobs from + * each of the two receive queues, we switch to slow polling, + * which uses coscheduling. + */ + +#define NI_QPOLL_FAST 100 +#define NI_QPOLL_SLOW 50000 + +#define EIG_TABLE_SIZE 40 +#define PKT_HEADER_LEN_OFFSET EIG_TABLE_SIZE +#define PKT_START_OFFSET (PKT_HEADER_LEN_OFFSET + 4) + +/* + * The NI card has two request queues for packet receive: One for + * small packets, and one for large packets. The small queue is meant + * for packets smaller than 128 bytes. The large queue is meant for + * packets up to 1500 bytes (no jumbo frames allowed) + */ + +#define GE_QUEUE 0 /* General request CIO queue */ +#define SM_QUEUE 0 /* Small packet receive queue number */ +#define LG_QUEUE 1 /* Large packet receive queue number */ +#define SM_PKT_MAX 106 /* Max size of small packets (excluding CRC) */ +#define LG_PKT_MAX 1514 /* Max size of large packets (excluding CRC) */ + +/* + * NI-specific debugging flags + */ +#define DBG_TRACE 0x01 +#define DBG_IO 0x02 +#define DBG_CACHE 0x04 +#define DBG_DAT 0x08 +#define DBG_ERR 0x10 +#define DBG_ETH 0x20 + +#define CHAR(c) ((((c) >= 0x20) && ((c) < 0x7f)) ? (c) : '.') + +#define NI_CACHE_HAS_SPACE(i) (((ni.job_cache[(i)].wp + 1) % NI_CACHE_LEN) != ni.job_cache[(i)].rp) +/* Determine whether both job caches have available slots */ +#define NI_BUFFERS_AVAIL ((ni.job_cache[0].wp != ni.job_cache[0].rp) && \ + (ni.job_cache[1].wp != ni.job_cache[1].rp)) + +/* + * The NI card caches up to three jobs taken from each of the two + * packet receive queues so that they are available immediately after + * receipt of a packet. These jobs are kept in small circular buffers. + * Each job is represented by an ni_rec_job structure, containing a + * buffer pointer and a slot number. The slot number is used by both + * the driver and the firmware to correlate a packet receive buffer + * with a completion queue event. + */ +typedef struct { + uint32 addr; /* address of job's buffer */ + uint8 slot; /* slot number of the job */ +} ni_rec_job; + +#define NI_CACHE_LEN 4 + +typedef struct { + ni_rec_job req[NI_CACHE_LEN]; /* the cache */ + int wp; /* write pointer */ + int rp; /* read pointer */ +} ni_job_cache; + +/* + * When the NI driver submits a packet send request to the general + * request queue, it constructs one or more ni_prot_info structs in + * main memory that point to the protocol-specific byte data of the + * packet (minus the Ethernet frame). These structs are packed one + * after the other following the Ethernet frame header in the job's + * request buffer. The last entry has its "last" bit set to non-zero. + */ +typedef struct { + uint32 addr; /* Physical address of the buffer in system RAM */ + uint16 size; /* Length of the buffer */ + uint16 last; /* Is this the last entry in the list? */ +} ni_prot_info; + +typedef struct { + uint32 rq_taken; + uint32 tx_fail; + uint32 rx_dropped; + uint32 rx_pkt; + uint32 tx_pkt; + uint32 rx_bytes; + uint32 tx_bytes; +} ni_stat_info; + +typedef struct { + uint8 slot; + t_bool enabled; + uint32 crc; + uint32 poll_rate; + char mac_str[MAC_SIZE_CHARS]; + uint8 mac_bytes[MAC_SIZE_BYTES]; + ni_job_cache job_cache[2]; + ni_prot_info prot; + ni_stat_info stats; + uint8 fcf_seq; + ETH_DEV* eth; + ETH_PACK rd_buf; + ETH_PACK wr_buf; + ETH_MAC macs[NI_FILTER_MAX]; /* List of all filter addresses */ + int filter_count; /* Number of filters available */ + ETH_PCALLBACK callback; +} NI_STATE; + +void ni_recv_callback(int status); +t_stat ni_reset(DEVICE *dptr); +t_stat ni_rcv_svc(UNIT *uptr); +t_stat ni_sanity_svc(UNIT *uptr); +t_stat ni_rq_svc(UNIT *uptr); +t_stat ni_cio_svc(UNIT *uptr); +t_stat ni_attach(UNIT *uptr, CONST char *cptr); +t_stat ni_detach(UNIT *uptr); +t_stat ni_setmac(UNIT *uptr, int32 val, CONST char *cptr, void *desc); +t_stat ni_showmac(FILE* st, UNIT *uptr, int32 val, CONST void *desc); +t_stat ni_try_job(uint8 slot); +t_stat ni_show_stats(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +t_stat ni_set_stats(UNIT *uptr, int32 val, CONST char *cptr, void *desc); +t_stat ni_show_poll(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +t_stat ni_show_filters(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +const char *ni_description(DEVICE *dptr); +t_stat ni_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); +void ni_cio_reset(uint8 slot); +void ni_process_packet(); +void ni_int_ack(uint8 slot); +void ni_sysgen(uint8 slot); +void ni_express(uint8 slot); +void ni_full(uint8 slot); + +#endif /* _3B2_NI_H_ */ diff --git a/3B2/3b2_ports.c b/3B2/3b2_ports.c index 4ddb7218..3b5152c9 100644 --- a/3B2/3b2_ports.c +++ b/3B2/3b2_ports.c @@ -1,838 +1,838 @@ -/* 3b2_ports.c: CM195B 4-Port Serial CIO Card - - Copyright (c) 2018-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -/* - * PORTS is an intelligent feature card for the 3B2 that supports four - * serial lines and one Centronics parallel port. - * - * The PORTS card is based on the Common I/O (CIO) platform. It uses - * two SCN2681A DUARTs to supply the four serial lines, and uses the - * SCN2681A parallel I/O pins for the Centronics parallel port. - * - * We make no attempt to emulate a PORTS card's internal workings - * precisely. Instead, we treat it as a black box as seen from the 3B2 - * system board's point of view. - * - */ - -#include "3b2_ports.h" - -#include "sim_tmxr.h" - -#include "3b2_cpu.h" -#include "3b2_io.h" -#include "3b2_mem.h" -#include "3b2_stddev.h" - -#define IO_SCHED 1000 - -/* Device and units for PORTS cards - * -------------------------------- - * - * A 3B2/400 system can have up to 12 PORTS cards installed. Each - * card, in turn, has 5 TTY devices - four serial TTYs and one - * parallel printer port (the printer port is not supported at this - * time, and is a no-op). - * - * The PORTS emulator is backed by a terminal multiplexer with up to - * 48 (12 * 4) serial lines. Lines can be specified with: - * - * SET PORTS LINES=n - * - * Lines must be specified in multiples of 4. - * - * Implementation8 - * -------------- - * - * Each set of 4 lines is mapped to a CIO_STATE struct in the "cio" - * CIO_STATE structure. - * - */ - -#define MAX_LINES 32 - -#define PPQESIZE 12 -#define DELAY_ASYNC 25 -#define DELAY_DLM 100 -#define DELAY_ULM 100 -#define DELAY_FCF 100 -#define DELAY_DOS 100 -#define DELAY_DSD 100 -#define DELAY_OPTIONS 100 -#define DELAY_VERS 100 -#define DELAY_CONN 100 -#define DELAY_XMIT 50 -#define DELAY_RECV 25 -#define DELAY_DEVICE 25 -#define DELAY_STD 100 - -#define PORTS_DIAG_CRC1 0x7ceec900 -#define PORTS_DIAG_CRC2 0x77a1ea56 -#define PORTS_DIAG_CRC3 0x84cf938b -#define PORTS_DIAG_CRC4 0x31b32383 /* Used by SVR 2.0.5 */ -#define PORTS_DIAG_CRC5 0x4be7bccc /* Used by SVR 2.0.5 */ -#define PORTS_DIAG_CRC6 0x3197f6dd /* Used by SVR 2.0.5 */ - -#define PORTS_DFLT_LINES 4 -#define PORTS_DFLT_CARDS 1 - -#define LN(slot,port) (ports_slot_ln[(slot)] + (port)) -#define LSLOT(ln) (ports_ln_slot[ln]) -/* #define LN(slot,port) ((PORTS_LINES * ((slot) - ports_base_slot)) + (port)) */ -/* #define LSLOT(ln) (((ln) / PORTS_LINES) + ports_base_slot) */ -#define LPORT(ln) ((ln) % PORTS_LINES) - -int8 ports_base_slot; /* First slot in our contiguous block */ -uint8 ports_int_slot; /* Interrupting card ID */ -uint8 ports_int_subdev; /* Interrupting subdevice */ -t_bool ports_conf = FALSE; /* Have PORTS cards been configured? */ -uint32 ports_crc; /* CRC32 of downloaded memory */ - -/* Mapping of line number to CIO card slot. Up to 32 lines spread over - 8 slots are supported. */ -uint8 ports_ln_slot[MAX_LINES]; - -/* Mapping of slot number to base line number belonging to the card in - that slot. I.e., if there are two PORTS cards, one in slot 3 and - one in slot 5, index 3 will have starting line 0, index 5 will have - starting line 4. */ -uint32 ports_slot_ln[CIO_SLOTS]; - -/* PORTS-specific state for each slot */ -PORTS_LINE_STATE *ports_state = NULL; - -/* Baud rates determined by the low nybble - * of the PORT_OPTIONS cflag */ -CONST char *ports_baud[16] = { - "75", "110", "134", "150", - "300", "600", "1200", "2000", - "2400", "4800", "1800", "9600", - "19200", "9600", "9600", "9600" -}; - -TMLN *ports_ldsc = NULL; -TMXR ports_desc = { 0, 0, 0, NULL }; - -/* Three units service the Receive, Transmit, and CIO */ -UNIT ports_unit[3] = { - { UDATA(&ports_rcv_svc, UNIT_IDLE|UNIT_ATTABLE|TT_MODE_8B, 0) }, - { UDATA(&ports_xmt_svc, UNIT_DIS, 0), SERIAL_OUT_WAIT }, - { UDATA(&ports_cio_svc, UNIT_DIS, 0) } -}; - -MTAB ports_mod[] = { - { TT_MODE, TT_MODE_7B, "7b", "7B", NULL, NULL, NULL, "7 bit mode" }, - { TT_MODE, TT_MODE_8B, "8b", "8B", NULL, NULL, NULL, "8 bit mode" }, - { TT_MODE, TT_MODE_7P, "7p", "7P", NULL, NULL, NULL, "7 bit mode - non printing suppressed" }, - { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "LINES", "LINES=n", - &ports_setnl, &tmxr_show_lines, (void *) &ports_desc, "Show or set number of lines" }, - { 0 } -}; - -static DEBTAB ports_debug[] = { - { "IO", IO_DBG, "I/O Character Trace" }, - { "TRACE", TRACE_DBG, "Call Trace" }, - { "XMT", TMXR_DBG_XMT, "TMXR Transmit Data" }, - { "RCV", TMXR_DBG_RCV, "TMXR Received Data" }, - { "RET", TMXR_DBG_RET, "TMXR Returned Received Data" }, - { "MDM", TMXR_DBG_XMT, "TMXR Modem Signals" }, - { "CON", TMXR_DBG_XMT, "TMXR Connection Activity" }, - { "ASY", TMXR_DBG_ASY, "TMXR Async Activity" }, - { "PXMT", TMXR_DBG_PXMT, "TMXR Transmit Packets" }, - { "PRCV", TMXR_DBG_PRCV, "TMXR Received Packets" }, - { NULL } -}; - -DEVICE ports_dev = { - "PORTS", /* name */ - ports_unit, /* units */ - NULL, /* registers */ - ports_mod, /* modifiers */ - 3, /* #units */ - 16, /* address radix */ - 32, /* address width */ - 1, /* address incr. */ - 16, /* data radix */ - 8, /* data width */ - NULL, /* examine routine */ - NULL, /* deposit routine */ - &ports_reset, /* reset routine */ - NULL, /* boot routine */ - &ports_attach, /* attach routine */ - &ports_detach, /* detach routine */ - NULL, /* context */ - DEV_DISABLE|DEV_DIS|DEV_DEBUG|DEV_MUX, /* flags */ - 0, /* debug control flags */ - ports_debug, /* debug flag names */ - NULL, /* memory size change */ - NULL, /* logical name */ - NULL, /* help routine */ - NULL, /* attach help routine */ - (void *)&ports_desc, /* help context */ - NULL, /* device description */ -}; - - -static void cio_irq(uint8 slot, uint8 dev, int32 delay) -{ - ports_int_slot = slot; - ports_int_subdev = dev & 0xf; - sim_activate(&ports_unit[2], delay); -} - -/* - * Set the number of lines for the PORTS mux. This will add or remove - * cards as necessary. The number of lines must be a multiple of 4. - */ -t_stat ports_setnl(UNIT *uptr, int32 val, CONST char *cptr, void *desc) -{ - int32 newln, i, t; - t_stat r = SCPE_OK; - - if (cptr == NULL) { - return SCPE_ARG; - } - - newln = (int32) get_uint(cptr, 10, (MAX_CARDS * PORTS_LINES), &r); - - if ((r != SCPE_OK) || (newln == ports_desc.lines)) { - return r; - } - - if ((newln == 0) || LPORT(newln) != 0 || newln > MAX_LINES) { - return SCPE_ARG; - } - - sim_debug(TRACE_DBG, &ports_dev, - "[ports_setnl] Setting line count to %d\n", - newln); - - if (newln < ports_desc.lines) { - for (i = newln, t = 0; i < ports_desc.lines; i++) { - t = t | ports_ldsc[i].conn; - } - - if (t && !get_yn("This will disconnect users; proceed [N]?", FALSE)) { - return SCPE_OK; - } - - for (i = newln; i < ports_desc.lines; i++) { - if (ports_ldsc[i].conn) { - tmxr_linemsg(&ports_ldsc[i], "\r\nOperator disconnected line\r\n"); - tmxr_send_buffered_data(&ports_ldsc[i]); - } - /* completely reset line */ - tmxr_detach_ln(&ports_ldsc[i]); - } - } - - ports_desc.ldsc = ports_ldsc = (TMLN *)realloc(ports_ldsc, newln*sizeof(*ports_ldsc)); - ports_state = (PORTS_LINE_STATE *)realloc(ports_state, newln*sizeof(*ports_state)); - - if (ports_desc.lines < newln) { - memset(ports_ldsc + ports_desc.lines, 0, sizeof(*ports_ldsc)*(newln-ports_desc.lines)); - memset(ports_state + ports_desc.lines, 0, sizeof(*ports_state)*(newln-ports_desc.lines)); - } - - ports_desc.lines = newln; - - /* setup lines and auto config */ - ports_conf = FALSE; - return ports_reset(&ports_dev); -} - - -static void ports_cmd(uint8 slot, cio_entry *rentry, uint8 *rapp_data) -{ - cio_entry centry = {0}; - uint32 ln, i; - PORTS_OPTIONS opts; - char line_config[16]; - uint8 app_data[4] = {0}; - - centry.address = rentry->address; - cio[slot].op = rentry->opcode; - ln = LN(slot, rentry->subdevice & 0xf); - - switch(rentry->opcode) { - case CIO_DLM: - for (i = 0; i < rentry->byte_count; i++) { - ports_crc = cio_crc32_shift(ports_crc, pread_b(rentry->address + i, BUS_PER)); - } - centry.address = rentry->address + rentry->byte_count; - sim_debug(TRACE_DBG, &ports_dev, - "[ports_cmd] CIO Download Memory: bytecnt=%04x " - "addr=%08x return_addr=%08x subdev=%02x (CRC=%08x)\n", - rentry->byte_count, rentry->address, - centry.address, centry.subdevice, ports_crc); - /* We intentionally do not set the subdevice in - * the completion entry */ - cio_cexpress(slot, PPQESIZE, ¢ry, app_data); - cio_irq(slot, rentry->subdevice, DELAY_DLM); - break; - case CIO_ULM: - sim_debug(TRACE_DBG, &ports_dev, - "[ports_cmd] CIO Upload Memory\n"); - cio_cexpress(slot, PPQESIZE, ¢ry, app_data); - cio_irq(slot, rentry->subdevice, DELAY_ULM); - break; - case CIO_FCF: - sim_debug(TRACE_DBG, &ports_dev, - "[ports_cmd] CIO Force Function Call (CRC=%08x)\n", - ports_crc); - - /* If the currently running program is a diagnostics program, - * we are expected to write results into memory at address - * 0x200f000 */ - if (ports_crc == PORTS_DIAG_CRC1 || - ports_crc == PORTS_DIAG_CRC2 || - ports_crc == PORTS_DIAG_CRC3 || - ports_crc == PORTS_DIAG_CRC4 || - ports_crc == PORTS_DIAG_CRC5 || - ports_crc == PORTS_DIAG_CRC6) { - pwrite_h(0x200f000, 0x1, BUS_PER); /* Test success */ - pwrite_h(0x200f002, 0x0, BUS_PER); /* Test Number */ - pwrite_h(0x200f004, 0x0, BUS_PER); /* Actual */ - pwrite_h(0x200f006, 0x0, BUS_PER); /* Expected */ - pwrite_b(0x200f008, 0x1, BUS_PER); /* Success flag again */ - } - - /* An interesting (?) side-effect of FORCE FUNCTION CALL is - * that it resets the card state such that a new SYSGEN is - * required in order for new commands to work. In fact, an - * INT0/INT1 combo _without_ a RESET can sysgen the board. So, - * we reset the command bits here. */ - cio[slot].sysgen_s = 0; - cio_cexpress(slot, PPQESIZE, ¢ry, app_data); - cio_irq(slot, rentry->subdevice, DELAY_FCF); - break; - case CIO_DOS: - sim_debug(TRACE_DBG, &ports_dev, - "[ports_cmd] CIO Determine Op Status\n"); - cio_cexpress(slot, PPQESIZE, ¢ry, app_data); - cio_irq(slot, rentry->subdevice, DELAY_DOS); - break; - case CIO_DSD: - /* Determine Sub-Devices. We have none. */ - sim_debug(TRACE_DBG, &ports_dev, - "[ports_cmd] Determine Sub-Devices.\n"); - - /* The system wants us to write sub-device structures - * at the supplied address */ - - pwrite_h(rentry->address, 0x0, BUS_PER); - cio_cexpress(slot, PPQESIZE, ¢ry, app_data); - cio_irq(slot, rentry->subdevice, DELAY_DSD); - break; - case PPC_OPTIONS: - sim_debug(TRACE_DBG, &ports_dev, - "[ports_cmd] PPC Options Operation\n"); - - opts.line = pread_h(rentry->address, BUS_PER); - opts.iflag = pread_h(rentry->address + 4, BUS_PER); - opts.oflag = pread_h(rentry->address + 6, BUS_PER); - opts.cflag = pread_h(rentry->address + 8, BUS_PER); - opts.lflag = pread_h(rentry->address + 10, BUS_PER); - opts.cerase = pread_b(rentry->address + 11, BUS_PER); - opts.ckill = pread_b(rentry->address + 12, BUS_PER); - opts.cinter = pread_b(rentry->address + 13, BUS_PER); - opts.cquit = pread_b(rentry->address + 14, BUS_PER); - opts.ceof = pread_b(rentry->address + 15, BUS_PER); - opts.ceol = pread_b(rentry->address + 16, BUS_PER); - opts.itime = pread_b(rentry->address + 17, BUS_PER); - opts.vtime = pread_b(rentry->address + 18, BUS_PER); - opts.vcount = pread_b(rentry->address + 19, BUS_PER); - - sim_debug(TRACE_DBG, &ports_dev, " PPC Options: iflag=%04x\n", opts.iflag); - sim_debug(TRACE_DBG, &ports_dev, " PPC Options: oflag=%04x\n", opts.oflag); - sim_debug(TRACE_DBG, &ports_dev, " PPC Options: cflag=%04x\n", opts.cflag); - sim_debug(TRACE_DBG, &ports_dev, " PPC Options: lflag=%04x\n", opts.lflag); - sim_debug(TRACE_DBG, &ports_dev, " PPC Options: itime=%02x\n", opts.itime); - sim_debug(TRACE_DBG, &ports_dev, " PPC Options: vtime=%02x\n", opts.vtime); - sim_debug(TRACE_DBG, &ports_dev, " PPC Options: vcount=%02x\n", opts.vcount); - - ports_state[ln].iflag = opts.iflag; - ports_state[ln].oflag = opts.oflag; - - if ((rentry->subdevice & 0xf) < PORTS_LINES) { - /* Adjust baud rate */ - sprintf(line_config, "%s-8N1", - ports_baud[opts.cflag&0xf]); - - sim_debug(TRACE_DBG, &ports_dev, - "Setting PORTS line %d to %s\n", - ln, line_config); - - tmxr_set_config_line(&ports_ldsc[ln], line_config); - } - - centry.byte_count = 20; - centry.opcode = PPC_OPTIONS; - centry.subdevice = rentry->subdevice; - centry.address = rentry->address; - cio_cqueue(slot, CIO_STAT, PPQESIZE, ¢ry, app_data); - cio_irq(slot, rentry->subdevice, DELAY_OPTIONS); - break; - case PPC_VERS: - sim_debug(TRACE_DBG, &ports_dev, - "[ports_cmd] PPC Version\n"); - - /* Write the version number at the supplied address */ - pwrite_b(rentry->address, PORTS_VERSION, BUS_PER); - - centry.opcode = CIO_ULM; - - /* TODO: It's unknown what the value 0x50 means, but this - * is what a real board sends. */ - app_data[0] = 0x50; - cio_cqueue(slot, CIO_STAT, PPQESIZE, ¢ry, app_data); - cio_irq(slot, rentry->subdevice, DELAY_VERS); - break; - case PPC_CONN: - /* CONNECT - Full request and completion queues */ - sim_debug(TRACE_DBG, &ports_dev, - "[ports_cmd] PPC CONNECT - subdevice = %02x\n", - rentry->subdevice); - - ports_state[ln].conn = TRUE; - - centry.opcode = PPC_CONN; - centry.subdevice = rentry->subdevice; - centry.address = rentry->address; - cio_cqueue(slot, CIO_STAT, PPQESIZE, ¢ry, app_data); - cio_irq(slot, rentry->subdevice, DELAY_CONN); - break; - case PPC_XMIT: - /* XMIT - Full request and completion queues */ - - /* The port being referred to is in the subdevice. */ - sim_debug(TRACE_DBG, &ports_dev, - "[ports_cmd] PPC XMIT - subdevice = %02x, address=%08x, byte_count=%d\n", - rentry->subdevice, rentry->address, rentry->byte_count); - - /* Set state for xmit */ - ports_state[ln].tx_addr = rentry->address; - ports_state[ln].tx_req_addr = rentry->address; - ports_state[ln].tx_chars = rentry->byte_count + 1; - ports_state[ln].tx_req_chars = rentry->byte_count + 1; - - sim_activate_after(&ports_unit[1], ports_unit[1].wait); - - break; - case PPC_DEVICE: - /* DEVICE Control - Express request and completion queues */ - /* The port being referred to is in the subdevice. */ - sim_debug(TRACE_DBG, &ports_dev, - "[ports_cmd] PPC DEVICE - subdevice = %02x\n", - rentry->subdevice); - centry.subdevice = rentry->subdevice; - centry.opcode = PPC_DEVICE; - cio_cexpress(slot, PPQESIZE, ¢ry, app_data); - cio_irq(slot, rentry->subdevice, DELAY_DEVICE); - break; - case PPC_RECV: - /* RECV - Full request and completion queues */ - - /* The port being referred to is in the subdevice. */ - sim_debug(TRACE_DBG, &ports_dev, - "[ports_cmd] PPC RECV - subdevice = %02x addr=%08x\n", - rentry->subdevice, rentry->address); - - break; - case PPC_DISC: - /* Disconnect */ - centry.subdevice = rentry->subdevice; - centry.opcode = PPC_DISC; - ports_ldsc[ln].rcve = 0; - cio_cqueue(slot, CIO_STAT, PPQESIZE, ¢ry, app_data); - cio_irq(slot, rentry->subdevice, DELAY_STD); - break; - case PPC_BRK: - case PPC_CLR: - default: - sim_debug(TRACE_DBG, &ports_dev, - ">>> Op %d Not Handled Yet\n", - rentry->opcode); - - cio_cexpress(slot, PPQESIZE, ¢ry, app_data); - cio_irq(slot, rentry->subdevice, DELAY_STD); - break; - } -} - -/* - * Update the connection status of the given port. - */ -static void ports_update_conn(uint32 ln) -{ - cio_entry centry = {0}; - uint8 slot; - uint8 app_data[4] = {0}; - - slot = LSLOT(ln); - - /* If the card hasn't sysgened, there's no way to write a - * completion queue entry */ - if (cio[slot].sysgen_s != CIO_SYSGEN) { - return; - } - - if (ports_ldsc[ln].conn) { - app_data[0] = AC_CON; - ports_state[ln].conn = TRUE; - } else { - if (ports_state[ln].conn) { - app_data[0] = AC_DIS; - ports_state[ln].conn = FALSE; - } else { - app_data[0] = 0; - } - } - - centry.opcode = PPC_ASYNC; - centry.subdevice = LPORT(ln); - cio_cqueue(slot, CIO_CMD, PPQESIZE, ¢ry, app_data); - - /* Interrupt */ - CIO_SET_INT(slot); -} - -void ports_sysgen(uint8 slot) -{ - cio_entry cqe = {0}; - uint8 app_data[4] = {0}; - - ports_crc = 0; - - cqe.opcode = 3; /* Sysgen success! */ - - /* It's not clear why we put a response in both the express - * and the full queue. */ - cio_cexpress(slot, PPQESIZE, &cqe, app_data); - cio_cqueue(slot, CIO_STAT, PPQESIZE, &cqe, app_data); - - ports_int_slot = slot; - sim_activate(&ports_unit[2], DELAY_STD); -} - -void ports_express(uint8 slot) -{ - cio_entry rqe = {0}; - uint8 app_data[4] = {0}; - cio_rexpress(slot, PPQESIZE, &rqe, app_data); - ports_cmd(slot, &rqe, app_data); -} - -void ports_full(uint8 slot) -{ - uint32 i; - cio_entry rqe = {0}; - uint8 app_data[4] = {0}; - - for (i = 0; i < PORTS_LINES; i++) { - if (cio_rqueue(slot, i, PPQESIZE, &rqe, app_data) == SCPE_OK) { - ports_cmd(slot, &rqe, app_data); - } - } -} - -t_stat ports_reset(DEVICE *dptr) -{ - int32 i, j; - uint8 slot; - t_stat r; - - ports_crc = 0; - - if (ports_ldsc == NULL) { - sim_set_uname(&ports_unit[0], "PORTS-RCV"); - sim_set_uname(&ports_unit[1], "PORTS-XMT"); - sim_set_uname(&ports_unit[2], "PORTS-CIO"); - - ports_desc.lines = PORTS_DFLT_LINES; - ports_desc.ldsc = ports_ldsc = - (TMLN *)calloc(ports_desc.lines, sizeof(*ports_ldsc)); - } - - if (ports_state == NULL) { - ports_state = (PORTS_LINE_STATE *)calloc(ports_desc.lines, sizeof(*ports_state)); - memset(ports_state, 0, ports_desc.lines*sizeof(*ports_state)); - } - - tmxr_set_port_speed_control(&ports_desc); - - if ((dptr->flags & DEV_DIS)) { - cio_remove_all(PORTS_ID); - ports_conf = FALSE; - return SCPE_OK; - } - - if (!ports_conf) { - /* Clear out any old cards, we're starting fresh */ - cio_remove_all(PORTS_ID); - - memset(ports_slot_ln, 0, sizeof(ports_slot_ln)); - memset(ports_ln_slot, 0, sizeof(ports_ln_slot)); - - /* Insert necessary cards into the backplane */ - j = 0; - for (i = 0; i < ports_desc.lines/PORTS_LINES; i++) { - r = cio_install(PORTS_ID, "PORTS", PORTS_IPL, - &ports_express, &ports_full, &ports_sysgen, NULL, - &slot); - if (r != SCPE_OK) { - return r; - } - /* Remember the port assignments */ - ports_slot_ln[slot] = i * PORTS_LINES; - for (; j < (i * PORTS_LINES) + PORTS_LINES; j++) { - ports_ln_slot[j] = slot; - } - } - - ports_conf = TRUE; - } - - /* If attached, start polling for connections */ - if (ports_unit[0].flags & UNIT_ATT) { - sim_activate_after_abs(&ports_unit[0], ports_unit[0].wait); - } else { - sim_cancel(&ports_unit[0]); - } - - return SCPE_OK; -} - -t_stat ports_cio_svc(UNIT *uptr) -{ - sim_debug(TRACE_DBG, &ports_dev, - "[ports_cio_svc] IRQ for board %d device %d\n", - ports_int_slot, ports_int_subdev); - - CIO_SET_INT(ports_int_slot); - - switch (cio[ports_int_slot].op) { - case PPC_CONN: - cio[ports_int_slot].op = PPC_ASYNC; - ports_ldsc[LN(ports_int_slot, ports_int_subdev)].rcve = 1; - sim_activate(&ports_unit[2], DELAY_ASYNC); - break; - case PPC_ASYNC: - ports_update_conn(LN(ports_int_slot, ports_int_subdev)); - break; - default: - break; - } - - return SCPE_OK; -} - -t_stat ports_rcv_svc(UNIT *uptr) -{ - uint8 slot; - int32 temp, ln; - char c; - cio_entry rentry = {0}; - cio_entry centry = {0}; - uint8 rapp_data[4] = {0}; - uint8 capp_data[4] = {0}; - - if ((uptr->flags & UNIT_ATT) == 0) { - return SCPE_OK; - } - - ln = tmxr_poll_conn(&ports_desc); - if (ln >= 0) { - ports_update_conn(ln); - } - - for (ln = 0; ln < ports_desc.lines; ln++) { - slot = LSLOT(ln); - - if (!ports_ldsc[ln].conn && ports_state[ln].conn) { - ports_update_conn(ln); - } else if (ports_ldsc[ln].conn && ports_state[ln].conn) { - temp = tmxr_getc_ln(&ports_ldsc[ln]); - - if (temp && !(temp & SCPE_BREAK)) { - - c = (char) (temp & 0xff); - - sim_debug(IO_DBG, &ports_dev, - "[LINE %d RECEIVE] char = %02x (%c)\n", - ln, c, c); - - if (c == 0xd && (ports_state[ln].iflag & ICRNL)) { - c = 0xa; - } - - if (cio[slot].ivec > 0 && - cio_rqueue(slot, PORTS_RCV_QUEUE, - PPQESIZE, &rentry, rapp_data) == SCPE_OK) { - CIO_SET_INT(slot); - - /* Write the character to the memory address */ - pwrite_b(rentry.address, c, BUS_PER); - centry.subdevice = LPORT(ln); - centry.opcode = PPC_RECV; - centry.address = rentry.address; - capp_data[3] = RC_TMR; - - cio_cqueue(slot, CIO_STAT, PPQESIZE, ¢ry, capp_data); - } - } - } - } - - tmxr_poll_rx(&ports_desc); - tmxr_poll_tx(&ports_desc); - - tmxr_clock_coschedule(uptr, tmxr_poll); - - return SCPE_OK; -} - -t_stat ports_xmt_svc(UNIT *uptr) -{ - uint8 slot, ln; - char c; - t_bool tx = FALSE; /* Did a tx ever occur? */ - cio_entry centry = {0}; - uint8 app_data[4] = {0}; - uint32 wait = 0x7fffffff; - - /* Scan all lines for output */ - for (ln = 0; ln < ports_desc.lines; ln++) { - slot = LSLOT(ln); - if (ports_ldsc[ln].conn && ports_state[ln].tx_chars > 0) { - tx = TRUE; /* Even an attempt at TX counts for rescheduling */ - c = sim_tt_outcvt(pread_b(ports_state[ln].tx_addr, BUS_PER), - TT_GET_MODE(ports_unit[0].flags)); - - /* The PORTS card optionally handles NL->CRLF */ - if (c == 0xa && - (ports_state[ln].oflag & ONLCR) && - !(ports_state[ln].crlf)) { - if (tmxr_putc_ln(&ports_ldsc[ln], 0xd) == SCPE_OK) { - wait = MIN(wait, ports_ldsc[ln].txdeltausecs); - sim_debug(IO_DBG, &ports_dev, - "[ports_xmt_svc] [LINE %d] XMIT (crlf): %02x (%c)\n", - ln, 0xd, 0xd); - /* Indicate that we're in a CRLF translation */ - ports_state[ln].crlf = TRUE; - } - - break; - } - - ports_state[ln].crlf = FALSE; - - if (tmxr_putc_ln(&ports_ldsc[ln], c) == SCPE_OK) { - wait = MIN(wait, ports_ldsc[ln].txdeltausecs); - ports_state[ln].tx_chars--; - ports_state[ln].tx_addr++; - sim_debug(IO_DBG, &ports_dev, - "[ports_xmt_svc] [LINE %d] XMIT: %02x (%c)\n", - ln, c, c); - } - - if (ports_state[ln].tx_chars == 0) { - sim_debug(TRACE_DBG, &ports_dev, - "[ports_xmt_svc] Done with xmit, card=%d port=%d. Interrupting.\n", - slot, LPORT(ln)); - centry.byte_count = ports_state[ln].tx_req_chars; - centry.subdevice = LPORT(ln); - centry.opcode = PPC_XMIT; - centry.address = ports_state[ln].tx_req_addr; - app_data[0] = RC_FLU; - cio_cqueue(slot, CIO_STAT, PPQESIZE, ¢ry, app_data); - CIO_SET_INT(slot); - } - } - } - - tmxr_poll_tx(&ports_desc); - - if (tx) { - tmxr_activate_after(uptr, wait); - } - - return SCPE_OK; -} - -t_stat ports_attach(UNIT *uptr, CONST char *cptr) -{ - TMLN *lp; - t_stat r; - int i; - - if ((sim_switches & SWMASK('M'))) { - tmxr_set_modem_control_passthru(&ports_desc); - } - - for (i = 0; i < ports_desc.lines; i++) { - sim_debug(TRACE_DBG, &ports_dev, - "[ports_reset] Setting up line %d...\n", i); - tmxr_set_line_output_unit(&ports_desc, i, &ports_unit[1]); - if (!ports_ldsc[i].conn) { - ports_ldsc[i].xmte = 1; - } - ports_ldsc[i].rcve = 0; - tmxr_set_config_line(&ports_ldsc[i], "9600-8N1"); - } - - r = tmxr_attach(&ports_desc, uptr, cptr); - if (r != SCPE_OK) { - tmxr_clear_modem_control_passthru(&ports_desc); - return r; - } - - for (i = 0; i < ports_desc.lines; i++) { - lp = &ports_ldsc[i]; - tmxr_set_get_modem_bits(lp, TMXR_MDM_DTR|TMXR_MDM_RTS, 0, NULL); - } - - return SCPE_OK; -} - -t_stat ports_detach(UNIT *uptr) -{ - t_stat r; - - r = tmxr_detach(&ports_desc, uptr); - - if (r != SCPE_OK) { - return r; - } - - tmxr_clear_modem_control_passthru(&ports_desc); - - return SCPE_OK; -} +/* 3b2_ports.c: CM195B 4-Port Serial CIO Card + + Copyright (c) 2018-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +/* + * PORTS is an intelligent feature card for the 3B2 that supports four + * serial lines and one Centronics parallel port. + * + * The PORTS card is based on the Common I/O (CIO) platform. It uses + * two SCN2681A DUARTs to supply the four serial lines, and uses the + * SCN2681A parallel I/O pins for the Centronics parallel port. + * + * We make no attempt to emulate a PORTS card's internal workings + * precisely. Instead, we treat it as a black box as seen from the 3B2 + * system board's point of view. + * + */ + +#include "3b2_ports.h" + +#include "sim_tmxr.h" + +#include "3b2_cpu.h" +#include "3b2_io.h" +#include "3b2_mem.h" +#include "3b2_stddev.h" + +#define IO_SCHED 1000 + +/* Device and units for PORTS cards + * -------------------------------- + * + * A 3B2/400 system can have up to 12 PORTS cards installed. Each + * card, in turn, has 5 TTY devices - four serial TTYs and one + * parallel printer port (the printer port is not supported at this + * time, and is a no-op). + * + * The PORTS emulator is backed by a terminal multiplexer with up to + * 48 (12 * 4) serial lines. Lines can be specified with: + * + * SET PORTS LINES=n + * + * Lines must be specified in multiples of 4. + * + * Implementation8 + * -------------- + * + * Each set of 4 lines is mapped to a CIO_STATE struct in the "cio" + * CIO_STATE structure. + * + */ + +#define MAX_LINES 32 + +#define PPQESIZE 12 +#define DELAY_ASYNC 25 +#define DELAY_DLM 100 +#define DELAY_ULM 100 +#define DELAY_FCF 100 +#define DELAY_DOS 100 +#define DELAY_DSD 100 +#define DELAY_OPTIONS 100 +#define DELAY_VERS 100 +#define DELAY_CONN 100 +#define DELAY_XMIT 50 +#define DELAY_RECV 25 +#define DELAY_DEVICE 25 +#define DELAY_STD 100 + +#define PORTS_DIAG_CRC1 0x7ceec900 +#define PORTS_DIAG_CRC2 0x77a1ea56 +#define PORTS_DIAG_CRC3 0x84cf938b +#define PORTS_DIAG_CRC4 0x31b32383 /* Used by SVR 2.0.5 */ +#define PORTS_DIAG_CRC5 0x4be7bccc /* Used by SVR 2.0.5 */ +#define PORTS_DIAG_CRC6 0x3197f6dd /* Used by SVR 2.0.5 */ + +#define PORTS_DFLT_LINES 4 +#define PORTS_DFLT_CARDS 1 + +#define LN(slot,port) (ports_slot_ln[(slot)] + (port)) +#define LSLOT(ln) (ports_ln_slot[ln]) +/* #define LN(slot,port) ((PORTS_LINES * ((slot) - ports_base_slot)) + (port)) */ +/* #define LSLOT(ln) (((ln) / PORTS_LINES) + ports_base_slot) */ +#define LPORT(ln) ((ln) % PORTS_LINES) + +int8 ports_base_slot; /* First slot in our contiguous block */ +uint8 ports_int_slot; /* Interrupting card ID */ +uint8 ports_int_subdev; /* Interrupting subdevice */ +t_bool ports_conf = FALSE; /* Have PORTS cards been configured? */ +uint32 ports_crc; /* CRC32 of downloaded memory */ + +/* Mapping of line number to CIO card slot. Up to 32 lines spread over + 8 slots are supported. */ +uint8 ports_ln_slot[MAX_LINES]; + +/* Mapping of slot number to base line number belonging to the card in + that slot. I.e., if there are two PORTS cards, one in slot 3 and + one in slot 5, index 3 will have starting line 0, index 5 will have + starting line 4. */ +uint32 ports_slot_ln[CIO_SLOTS]; + +/* PORTS-specific state for each slot */ +PORTS_LINE_STATE *ports_state = NULL; + +/* Baud rates determined by the low nybble + * of the PORT_OPTIONS cflag */ +CONST char *ports_baud[16] = { + "75", "110", "134", "150", + "300", "600", "1200", "2000", + "2400", "4800", "1800", "9600", + "19200", "9600", "9600", "9600" +}; + +TMLN *ports_ldsc = NULL; +TMXR ports_desc = { 0, 0, 0, NULL }; + +/* Three units service the Receive, Transmit, and CIO */ +UNIT ports_unit[3] = { + { UDATA(&ports_rcv_svc, UNIT_IDLE|UNIT_ATTABLE|TT_MODE_8B, 0) }, + { UDATA(&ports_xmt_svc, UNIT_DIS, 0), SERIAL_OUT_WAIT }, + { UDATA(&ports_cio_svc, UNIT_DIS, 0) } +}; + +MTAB ports_mod[] = { + { TT_MODE, TT_MODE_7B, "7b", "7B", NULL, NULL, NULL, "7 bit mode" }, + { TT_MODE, TT_MODE_8B, "8b", "8B", NULL, NULL, NULL, "8 bit mode" }, + { TT_MODE, TT_MODE_7P, "7p", "7P", NULL, NULL, NULL, "7 bit mode - non printing suppressed" }, + { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "LINES", "LINES=n", + &ports_setnl, &tmxr_show_lines, (void *) &ports_desc, "Show or set number of lines" }, + { 0 } +}; + +static DEBTAB ports_debug[] = { + { "IO", IO_DBG, "I/O Character Trace" }, + { "TRACE", TRACE_DBG, "Call Trace" }, + { "XMT", TMXR_DBG_XMT, "TMXR Transmit Data" }, + { "RCV", TMXR_DBG_RCV, "TMXR Received Data" }, + { "RET", TMXR_DBG_RET, "TMXR Returned Received Data" }, + { "MDM", TMXR_DBG_XMT, "TMXR Modem Signals" }, + { "CON", TMXR_DBG_XMT, "TMXR Connection Activity" }, + { "ASY", TMXR_DBG_ASY, "TMXR Async Activity" }, + { "PXMT", TMXR_DBG_PXMT, "TMXR Transmit Packets" }, + { "PRCV", TMXR_DBG_PRCV, "TMXR Received Packets" }, + { NULL } +}; + +DEVICE ports_dev = { + "PORTS", /* name */ + ports_unit, /* units */ + NULL, /* registers */ + ports_mod, /* modifiers */ + 3, /* #units */ + 16, /* address radix */ + 32, /* address width */ + 1, /* address incr. */ + 16, /* data radix */ + 8, /* data width */ + NULL, /* examine routine */ + NULL, /* deposit routine */ + &ports_reset, /* reset routine */ + NULL, /* boot routine */ + &ports_attach, /* attach routine */ + &ports_detach, /* detach routine */ + NULL, /* context */ + DEV_DISABLE|DEV_DIS|DEV_DEBUG|DEV_MUX, /* flags */ + 0, /* debug control flags */ + ports_debug, /* debug flag names */ + NULL, /* memory size change */ + NULL, /* logical name */ + NULL, /* help routine */ + NULL, /* attach help routine */ + (void *)&ports_desc, /* help context */ + NULL, /* device description */ +}; + + +static void cio_irq(uint8 slot, uint8 dev, int32 delay) +{ + ports_int_slot = slot; + ports_int_subdev = dev & 0xf; + sim_activate(&ports_unit[2], delay); +} + +/* + * Set the number of lines for the PORTS mux. This will add or remove + * cards as necessary. The number of lines must be a multiple of 4. + */ +t_stat ports_setnl(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + int32 newln, i, t; + t_stat r = SCPE_OK; + + if (cptr == NULL) { + return SCPE_ARG; + } + + newln = (int32) get_uint(cptr, 10, (MAX_CARDS * PORTS_LINES), &r); + + if ((r != SCPE_OK) || (newln == ports_desc.lines)) { + return r; + } + + if ((newln == 0) || LPORT(newln) != 0 || newln > MAX_LINES) { + return SCPE_ARG; + } + + sim_debug(TRACE_DBG, &ports_dev, + "[ports_setnl] Setting line count to %d\n", + newln); + + if (newln < ports_desc.lines) { + for (i = newln, t = 0; i < ports_desc.lines; i++) { + t = t | ports_ldsc[i].conn; + } + + if (t && !get_yn("This will disconnect users; proceed [N]?", FALSE)) { + return SCPE_OK; + } + + for (i = newln; i < ports_desc.lines; i++) { + if (ports_ldsc[i].conn) { + tmxr_linemsg(&ports_ldsc[i], "\r\nOperator disconnected line\r\n"); + tmxr_send_buffered_data(&ports_ldsc[i]); + } + /* completely reset line */ + tmxr_detach_ln(&ports_ldsc[i]); + } + } + + ports_desc.ldsc = ports_ldsc = (TMLN *)realloc(ports_ldsc, newln*sizeof(*ports_ldsc)); + ports_state = (PORTS_LINE_STATE *)realloc(ports_state, newln*sizeof(*ports_state)); + + if (ports_desc.lines < newln) { + memset(ports_ldsc + ports_desc.lines, 0, sizeof(*ports_ldsc)*(newln-ports_desc.lines)); + memset(ports_state + ports_desc.lines, 0, sizeof(*ports_state)*(newln-ports_desc.lines)); + } + + ports_desc.lines = newln; + + /* setup lines and auto config */ + ports_conf = FALSE; + return ports_reset(&ports_dev); +} + + +static void ports_cmd(uint8 slot, cio_entry *rentry, uint8 *rapp_data) +{ + cio_entry centry = {0}; + uint32 ln, i; + PORTS_OPTIONS opts; + char line_config[16]; + uint8 app_data[4] = {0}; + + centry.address = rentry->address; + cio[slot].op = rentry->opcode; + ln = LN(slot, rentry->subdevice & 0xf); + + switch(rentry->opcode) { + case CIO_DLM: + for (i = 0; i < rentry->byte_count; i++) { + ports_crc = cio_crc32_shift(ports_crc, pread_b(rentry->address + i, BUS_PER)); + } + centry.address = rentry->address + rentry->byte_count; + sim_debug(TRACE_DBG, &ports_dev, + "[ports_cmd] CIO Download Memory: bytecnt=%04x " + "addr=%08x return_addr=%08x subdev=%02x (CRC=%08x)\n", + rentry->byte_count, rentry->address, + centry.address, centry.subdevice, ports_crc); + /* We intentionally do not set the subdevice in + * the completion entry */ + cio_cexpress(slot, PPQESIZE, ¢ry, app_data); + cio_irq(slot, rentry->subdevice, DELAY_DLM); + break; + case CIO_ULM: + sim_debug(TRACE_DBG, &ports_dev, + "[ports_cmd] CIO Upload Memory\n"); + cio_cexpress(slot, PPQESIZE, ¢ry, app_data); + cio_irq(slot, rentry->subdevice, DELAY_ULM); + break; + case CIO_FCF: + sim_debug(TRACE_DBG, &ports_dev, + "[ports_cmd] CIO Force Function Call (CRC=%08x)\n", + ports_crc); + + /* If the currently running program is a diagnostics program, + * we are expected to write results into memory at address + * 0x200f000 */ + if (ports_crc == PORTS_DIAG_CRC1 || + ports_crc == PORTS_DIAG_CRC2 || + ports_crc == PORTS_DIAG_CRC3 || + ports_crc == PORTS_DIAG_CRC4 || + ports_crc == PORTS_DIAG_CRC5 || + ports_crc == PORTS_DIAG_CRC6) { + pwrite_h(0x200f000, 0x1, BUS_PER); /* Test success */ + pwrite_h(0x200f002, 0x0, BUS_PER); /* Test Number */ + pwrite_h(0x200f004, 0x0, BUS_PER); /* Actual */ + pwrite_h(0x200f006, 0x0, BUS_PER); /* Expected */ + pwrite_b(0x200f008, 0x1, BUS_PER); /* Success flag again */ + } + + /* An interesting (?) side-effect of FORCE FUNCTION CALL is + * that it resets the card state such that a new SYSGEN is + * required in order for new commands to work. In fact, an + * INT0/INT1 combo _without_ a RESET can sysgen the board. So, + * we reset the command bits here. */ + cio[slot].sysgen_s = 0; + cio_cexpress(slot, PPQESIZE, ¢ry, app_data); + cio_irq(slot, rentry->subdevice, DELAY_FCF); + break; + case CIO_DOS: + sim_debug(TRACE_DBG, &ports_dev, + "[ports_cmd] CIO Determine Op Status\n"); + cio_cexpress(slot, PPQESIZE, ¢ry, app_data); + cio_irq(slot, rentry->subdevice, DELAY_DOS); + break; + case CIO_DSD: + /* Determine Sub-Devices. We have none. */ + sim_debug(TRACE_DBG, &ports_dev, + "[ports_cmd] Determine Sub-Devices.\n"); + + /* The system wants us to write sub-device structures + * at the supplied address */ + + pwrite_h(rentry->address, 0x0, BUS_PER); + cio_cexpress(slot, PPQESIZE, ¢ry, app_data); + cio_irq(slot, rentry->subdevice, DELAY_DSD); + break; + case PPC_OPTIONS: + sim_debug(TRACE_DBG, &ports_dev, + "[ports_cmd] PPC Options Operation\n"); + + opts.line = pread_h(rentry->address, BUS_PER); + opts.iflag = pread_h(rentry->address + 4, BUS_PER); + opts.oflag = pread_h(rentry->address + 6, BUS_PER); + opts.cflag = pread_h(rentry->address + 8, BUS_PER); + opts.lflag = pread_h(rentry->address + 10, BUS_PER); + opts.cerase = pread_b(rentry->address + 11, BUS_PER); + opts.ckill = pread_b(rentry->address + 12, BUS_PER); + opts.cinter = pread_b(rentry->address + 13, BUS_PER); + opts.cquit = pread_b(rentry->address + 14, BUS_PER); + opts.ceof = pread_b(rentry->address + 15, BUS_PER); + opts.ceol = pread_b(rentry->address + 16, BUS_PER); + opts.itime = pread_b(rentry->address + 17, BUS_PER); + opts.vtime = pread_b(rentry->address + 18, BUS_PER); + opts.vcount = pread_b(rentry->address + 19, BUS_PER); + + sim_debug(TRACE_DBG, &ports_dev, " PPC Options: iflag=%04x\n", opts.iflag); + sim_debug(TRACE_DBG, &ports_dev, " PPC Options: oflag=%04x\n", opts.oflag); + sim_debug(TRACE_DBG, &ports_dev, " PPC Options: cflag=%04x\n", opts.cflag); + sim_debug(TRACE_DBG, &ports_dev, " PPC Options: lflag=%04x\n", opts.lflag); + sim_debug(TRACE_DBG, &ports_dev, " PPC Options: itime=%02x\n", opts.itime); + sim_debug(TRACE_DBG, &ports_dev, " PPC Options: vtime=%02x\n", opts.vtime); + sim_debug(TRACE_DBG, &ports_dev, " PPC Options: vcount=%02x\n", opts.vcount); + + ports_state[ln].iflag = opts.iflag; + ports_state[ln].oflag = opts.oflag; + + if ((rentry->subdevice & 0xf) < PORTS_LINES) { + /* Adjust baud rate */ + sprintf(line_config, "%s-8N1", + ports_baud[opts.cflag&0xf]); + + sim_debug(TRACE_DBG, &ports_dev, + "Setting PORTS line %d to %s\n", + ln, line_config); + + tmxr_set_config_line(&ports_ldsc[ln], line_config); + } + + centry.byte_count = 20; + centry.opcode = PPC_OPTIONS; + centry.subdevice = rentry->subdevice; + centry.address = rentry->address; + cio_cqueue(slot, CIO_STAT, PPQESIZE, ¢ry, app_data); + cio_irq(slot, rentry->subdevice, DELAY_OPTIONS); + break; + case PPC_VERS: + sim_debug(TRACE_DBG, &ports_dev, + "[ports_cmd] PPC Version\n"); + + /* Write the version number at the supplied address */ + pwrite_b(rentry->address, PORTS_VERSION, BUS_PER); + + centry.opcode = CIO_ULM; + + /* TODO: It's unknown what the value 0x50 means, but this + * is what a real board sends. */ + app_data[0] = 0x50; + cio_cqueue(slot, CIO_STAT, PPQESIZE, ¢ry, app_data); + cio_irq(slot, rentry->subdevice, DELAY_VERS); + break; + case PPC_CONN: + /* CONNECT - Full request and completion queues */ + sim_debug(TRACE_DBG, &ports_dev, + "[ports_cmd] PPC CONNECT - subdevice = %02x\n", + rentry->subdevice); + + ports_state[ln].conn = TRUE; + + centry.opcode = PPC_CONN; + centry.subdevice = rentry->subdevice; + centry.address = rentry->address; + cio_cqueue(slot, CIO_STAT, PPQESIZE, ¢ry, app_data); + cio_irq(slot, rentry->subdevice, DELAY_CONN); + break; + case PPC_XMIT: + /* XMIT - Full request and completion queues */ + + /* The port being referred to is in the subdevice. */ + sim_debug(TRACE_DBG, &ports_dev, + "[ports_cmd] PPC XMIT - subdevice = %02x, address=%08x, byte_count=%d\n", + rentry->subdevice, rentry->address, rentry->byte_count); + + /* Set state for xmit */ + ports_state[ln].tx_addr = rentry->address; + ports_state[ln].tx_req_addr = rentry->address; + ports_state[ln].tx_chars = rentry->byte_count + 1; + ports_state[ln].tx_req_chars = rentry->byte_count + 1; + + sim_activate_after(&ports_unit[1], ports_unit[1].wait); + + break; + case PPC_DEVICE: + /* DEVICE Control - Express request and completion queues */ + /* The port being referred to is in the subdevice. */ + sim_debug(TRACE_DBG, &ports_dev, + "[ports_cmd] PPC DEVICE - subdevice = %02x\n", + rentry->subdevice); + centry.subdevice = rentry->subdevice; + centry.opcode = PPC_DEVICE; + cio_cexpress(slot, PPQESIZE, ¢ry, app_data); + cio_irq(slot, rentry->subdevice, DELAY_DEVICE); + break; + case PPC_RECV: + /* RECV - Full request and completion queues */ + + /* The port being referred to is in the subdevice. */ + sim_debug(TRACE_DBG, &ports_dev, + "[ports_cmd] PPC RECV - subdevice = %02x addr=%08x\n", + rentry->subdevice, rentry->address); + + break; + case PPC_DISC: + /* Disconnect */ + centry.subdevice = rentry->subdevice; + centry.opcode = PPC_DISC; + ports_ldsc[ln].rcve = 0; + cio_cqueue(slot, CIO_STAT, PPQESIZE, ¢ry, app_data); + cio_irq(slot, rentry->subdevice, DELAY_STD); + break; + case PPC_BRK: + case PPC_CLR: + default: + sim_debug(TRACE_DBG, &ports_dev, + ">>> Op %d Not Handled Yet\n", + rentry->opcode); + + cio_cexpress(slot, PPQESIZE, ¢ry, app_data); + cio_irq(slot, rentry->subdevice, DELAY_STD); + break; + } +} + +/* + * Update the connection status of the given port. + */ +static void ports_update_conn(uint32 ln) +{ + cio_entry centry = {0}; + uint8 slot; + uint8 app_data[4] = {0}; + + slot = LSLOT(ln); + + /* If the card hasn't sysgened, there's no way to write a + * completion queue entry */ + if (cio[slot].sysgen_s != CIO_SYSGEN) { + return; + } + + if (ports_ldsc[ln].conn) { + app_data[0] = AC_CON; + ports_state[ln].conn = TRUE; + } else { + if (ports_state[ln].conn) { + app_data[0] = AC_DIS; + ports_state[ln].conn = FALSE; + } else { + app_data[0] = 0; + } + } + + centry.opcode = PPC_ASYNC; + centry.subdevice = LPORT(ln); + cio_cqueue(slot, CIO_CMD, PPQESIZE, ¢ry, app_data); + + /* Interrupt */ + CIO_SET_INT(slot); +} + +void ports_sysgen(uint8 slot) +{ + cio_entry cqe = {0}; + uint8 app_data[4] = {0}; + + ports_crc = 0; + + cqe.opcode = 3; /* Sysgen success! */ + + /* It's not clear why we put a response in both the express + * and the full queue. */ + cio_cexpress(slot, PPQESIZE, &cqe, app_data); + cio_cqueue(slot, CIO_STAT, PPQESIZE, &cqe, app_data); + + ports_int_slot = slot; + sim_activate(&ports_unit[2], DELAY_STD); +} + +void ports_express(uint8 slot) +{ + cio_entry rqe = {0}; + uint8 app_data[4] = {0}; + cio_rexpress(slot, PPQESIZE, &rqe, app_data); + ports_cmd(slot, &rqe, app_data); +} + +void ports_full(uint8 slot) +{ + uint32 i; + cio_entry rqe = {0}; + uint8 app_data[4] = {0}; + + for (i = 0; i < PORTS_LINES; i++) { + if (cio_rqueue(slot, i, PPQESIZE, &rqe, app_data) == SCPE_OK) { + ports_cmd(slot, &rqe, app_data); + } + } +} + +t_stat ports_reset(DEVICE *dptr) +{ + int32 i, j; + uint8 slot; + t_stat r; + + ports_crc = 0; + + if (ports_ldsc == NULL) { + sim_set_uname(&ports_unit[0], "PORTS-RCV"); + sim_set_uname(&ports_unit[1], "PORTS-XMT"); + sim_set_uname(&ports_unit[2], "PORTS-CIO"); + + ports_desc.lines = PORTS_DFLT_LINES; + ports_desc.ldsc = ports_ldsc = + (TMLN *)calloc(ports_desc.lines, sizeof(*ports_ldsc)); + } + + if (ports_state == NULL) { + ports_state = (PORTS_LINE_STATE *)calloc(ports_desc.lines, sizeof(*ports_state)); + memset(ports_state, 0, ports_desc.lines*sizeof(*ports_state)); + } + + tmxr_set_port_speed_control(&ports_desc); + + if ((dptr->flags & DEV_DIS)) { + cio_remove_all(PORTS_ID); + ports_conf = FALSE; + return SCPE_OK; + } + + if (!ports_conf) { + /* Clear out any old cards, we're starting fresh */ + cio_remove_all(PORTS_ID); + + memset(ports_slot_ln, 0, sizeof(ports_slot_ln)); + memset(ports_ln_slot, 0, sizeof(ports_ln_slot)); + + /* Insert necessary cards into the backplane */ + j = 0; + for (i = 0; i < ports_desc.lines/PORTS_LINES; i++) { + r = cio_install(PORTS_ID, "PORTS", PORTS_IPL, + &ports_express, &ports_full, &ports_sysgen, NULL, + &slot); + if (r != SCPE_OK) { + return r; + } + /* Remember the port assignments */ + ports_slot_ln[slot] = i * PORTS_LINES; + for (; j < (i * PORTS_LINES) + PORTS_LINES; j++) { + ports_ln_slot[j] = slot; + } + } + + ports_conf = TRUE; + } + + /* If attached, start polling for connections */ + if (ports_unit[0].flags & UNIT_ATT) { + sim_activate_after_abs(&ports_unit[0], ports_unit[0].wait); + } else { + sim_cancel(&ports_unit[0]); + } + + return SCPE_OK; +} + +t_stat ports_cio_svc(UNIT *uptr) +{ + sim_debug(TRACE_DBG, &ports_dev, + "[ports_cio_svc] IRQ for board %d device %d\n", + ports_int_slot, ports_int_subdev); + + CIO_SET_INT(ports_int_slot); + + switch (cio[ports_int_slot].op) { + case PPC_CONN: + cio[ports_int_slot].op = PPC_ASYNC; + ports_ldsc[LN(ports_int_slot, ports_int_subdev)].rcve = 1; + sim_activate(&ports_unit[2], DELAY_ASYNC); + break; + case PPC_ASYNC: + ports_update_conn(LN(ports_int_slot, ports_int_subdev)); + break; + default: + break; + } + + return SCPE_OK; +} + +t_stat ports_rcv_svc(UNIT *uptr) +{ + uint8 slot; + int32 temp, ln; + char c; + cio_entry rentry = {0}; + cio_entry centry = {0}; + uint8 rapp_data[4] = {0}; + uint8 capp_data[4] = {0}; + + if ((uptr->flags & UNIT_ATT) == 0) { + return SCPE_OK; + } + + ln = tmxr_poll_conn(&ports_desc); + if (ln >= 0) { + ports_update_conn(ln); + } + + for (ln = 0; ln < ports_desc.lines; ln++) { + slot = LSLOT(ln); + + if (!ports_ldsc[ln].conn && ports_state[ln].conn) { + ports_update_conn(ln); + } else if (ports_ldsc[ln].conn && ports_state[ln].conn) { + temp = tmxr_getc_ln(&ports_ldsc[ln]); + + if (temp && !(temp & SCPE_BREAK)) { + + c = (char) (temp & 0xff); + + sim_debug(IO_DBG, &ports_dev, + "[LINE %d RECEIVE] char = %02x (%c)\n", + ln, c, c); + + if (c == 0xd && (ports_state[ln].iflag & ICRNL)) { + c = 0xa; + } + + if (cio[slot].ivec > 0 && + cio_rqueue(slot, PORTS_RCV_QUEUE, + PPQESIZE, &rentry, rapp_data) == SCPE_OK) { + CIO_SET_INT(slot); + + /* Write the character to the memory address */ + pwrite_b(rentry.address, c, BUS_PER); + centry.subdevice = LPORT(ln); + centry.opcode = PPC_RECV; + centry.address = rentry.address; + capp_data[3] = RC_TMR; + + cio_cqueue(slot, CIO_STAT, PPQESIZE, ¢ry, capp_data); + } + } + } + } + + tmxr_poll_rx(&ports_desc); + tmxr_poll_tx(&ports_desc); + + tmxr_clock_coschedule(uptr, tmxr_poll); + + return SCPE_OK; +} + +t_stat ports_xmt_svc(UNIT *uptr) +{ + uint8 slot, ln; + char c; + t_bool tx = FALSE; /* Did a tx ever occur? */ + cio_entry centry = {0}; + uint8 app_data[4] = {0}; + uint32 wait = 0x7fffffff; + + /* Scan all lines for output */ + for (ln = 0; ln < ports_desc.lines; ln++) { + slot = LSLOT(ln); + if (ports_ldsc[ln].conn && ports_state[ln].tx_chars > 0) { + tx = TRUE; /* Even an attempt at TX counts for rescheduling */ + c = sim_tt_outcvt(pread_b(ports_state[ln].tx_addr, BUS_PER), + TT_GET_MODE(ports_unit[0].flags)); + + /* The PORTS card optionally handles NL->CRLF */ + if (c == 0xa && + (ports_state[ln].oflag & ONLCR) && + !(ports_state[ln].crlf)) { + if (tmxr_putc_ln(&ports_ldsc[ln], 0xd) == SCPE_OK) { + wait = MIN(wait, ports_ldsc[ln].txdeltausecs); + sim_debug(IO_DBG, &ports_dev, + "[ports_xmt_svc] [LINE %d] XMIT (crlf): %02x (%c)\n", + ln, 0xd, 0xd); + /* Indicate that we're in a CRLF translation */ + ports_state[ln].crlf = TRUE; + } + + break; + } + + ports_state[ln].crlf = FALSE; + + if (tmxr_putc_ln(&ports_ldsc[ln], c) == SCPE_OK) { + wait = MIN(wait, ports_ldsc[ln].txdeltausecs); + ports_state[ln].tx_chars--; + ports_state[ln].tx_addr++; + sim_debug(IO_DBG, &ports_dev, + "[ports_xmt_svc] [LINE %d] XMIT: %02x (%c)\n", + ln, c, c); + } + + if (ports_state[ln].tx_chars == 0) { + sim_debug(TRACE_DBG, &ports_dev, + "[ports_xmt_svc] Done with xmit, card=%d port=%d. Interrupting.\n", + slot, LPORT(ln)); + centry.byte_count = ports_state[ln].tx_req_chars; + centry.subdevice = LPORT(ln); + centry.opcode = PPC_XMIT; + centry.address = ports_state[ln].tx_req_addr; + app_data[0] = RC_FLU; + cio_cqueue(slot, CIO_STAT, PPQESIZE, ¢ry, app_data); + CIO_SET_INT(slot); + } + } + } + + tmxr_poll_tx(&ports_desc); + + if (tx) { + tmxr_activate_after(uptr, wait); + } + + return SCPE_OK; +} + +t_stat ports_attach(UNIT *uptr, CONST char *cptr) +{ + TMLN *lp; + t_stat r; + int i; + + if ((sim_switches & SWMASK('M'))) { + tmxr_set_modem_control_passthru(&ports_desc); + } + + for (i = 0; i < ports_desc.lines; i++) { + sim_debug(TRACE_DBG, &ports_dev, + "[ports_reset] Setting up line %d...\n", i); + tmxr_set_line_output_unit(&ports_desc, i, &ports_unit[1]); + if (!ports_ldsc[i].conn) { + ports_ldsc[i].xmte = 1; + } + ports_ldsc[i].rcve = 0; + tmxr_set_config_line(&ports_ldsc[i], "9600-8N1"); + } + + r = tmxr_attach(&ports_desc, uptr, cptr); + if (r != SCPE_OK) { + tmxr_clear_modem_control_passthru(&ports_desc); + return r; + } + + for (i = 0; i < ports_desc.lines; i++) { + lp = &ports_ldsc[i]; + tmxr_set_get_modem_bits(lp, TMXR_MDM_DTR|TMXR_MDM_RTS, 0, NULL); + } + + return SCPE_OK; +} + +t_stat ports_detach(UNIT *uptr) +{ + t_stat r; + + r = tmxr_detach(&ports_desc, uptr); + + if (r != SCPE_OK) { + return r; + } + + tmxr_clear_modem_control_passthru(&ports_desc); + + return SCPE_OK; +} diff --git a/3B2/3b2_ports.h b/3B2/3b2_ports.h index a419a928..2e5d7114 100644 --- a/3B2/3b2_ports.h +++ b/3B2/3b2_ports.h @@ -1,229 +1,229 @@ -/* 3b2_ports.h: CM195B 4-Port Serial CIO Card - - Copyright (c) 2018-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -/* - * PORTS is an intelligent feature card for the 3B2 that supports four - * serial lines and one Centronics parallel port. - * - * The PORTS card is based on the Common I/O (CIO) platform. It uses - * two SCN2681A DUARTs to supply the four serial lines, and uses the - * SCN2681A parallel I/O pins for the Centronics parallel port. - * - * This file implements the required logic for the PORTS CIO - * interface. The SCN2681A functionality is implemented in the file - * 3b2_duart.c, and is used by both this feature card and the System - * Board console/contty functionality. - */ - -#ifndef _3B2_PORTS_H_ -#define _3B2_PORTS_H_ - -#include "3b2_defs.h" - -#define PORTS_ID 0x0003 -#define PORTS_IPL 10 -#define PORTS_VERSION 1 - -#define MAX_CARDS 8 /* Up to 8 PORTS cards with 32 lines total - supported */ -#define PORTS_LINES 4 -#define PORTS_RCV_QUEUE 5 - -/* - * Sub-field values for the PPC_DEVICE request entry; these are placed - * in app_data.bt[0] in the PPC_DEVICE application field. The prefix - * DR indicates that this is a code for use in "device" request - * entries only. -*/ - -#define DR_ENA 1 /* enable a device */ -#define DR_DIS 2 /* disable a device */ -#define DR_ABR 3 /* abort reception on a device */ -#define DR_ABX 4 /* abort transmission on a device */ -#define DR_BRK 5 /* transmit "break" on a device */ -#define DR_SUS 6 /* suspend xmit on a device */ -#define DR_RES 7 /* resume xmit on a device */ -#define DR_BLK 8 /* transmit STOP character */ -#define DR_UNB 9 /* transmit START character */ - -/* - * Sub-field values for the PPC_DEVICE completion entry; these appear - * in app_data.bt[0] in the PPC_DEVICE application field. These are - * mutually exclusive and cannot be combined. The prefix DC indicates - * that this is a code for use in "device" completion entries only. - */ - -#define DC_NORM 0x00 /* command executed as requested */ -#define DC_DEV 0x01 /* bad device number */ -#define DC_NON 0x02 /* bad sub-code on request */ -#define DC_FAIL 0x03 /* failed to read express entry */ - -/* - * Sub-field values for the PPC_RECV completion entry; these appear in - * app_data.bt[0] in the PPC_RECV application field. These are NOT - * mutually exclusive and may appear in combination. The prefix RC - * indicates that this is a code for use in "read" completion entries - * only. -*/ - -#define RC_DSR 0x01 /* disruption of service */ -#define RC_FLU 0x02 /* buffer flushed */ -#define RC_TMR 0x04 /* inter-character timer expired */ -#define RC_BQO 0x08 /* PPC buffer queue overflow */ -#define RC_UAO 0x10 /* uart overrun */ -#define RC_PAR 0x20 /* parity error */ -#define RC_FRA 0x40 /* framing error */ -#define RC_BRK 0x80 /* break received */ - -/* - * The following codes are included on the DISC (disconnect) command. - * They are "or"ed into the app_data.bt[1] application field in a - * request. These codes are NOT mutually exclusive and can be used in - * any combination. - */ - -#define GR_DTR 0x01 -#define GR_CREAD 0x02 - -/* - * Sub-field values for the PPC_XMIT and PPC_OPTIONS completion - * entries; these appear in app_data.bt[0] in the application fields. - * These are NOT mutually exclusive and may appear in combination. - * The prefix GC indicates that this is a code for use in "general" - * completion entries only. -*/ - -#define GC_DSR 0x01 /* disruption of service */ -#define GC_FLU 0x02 /* buffer flushed */ - -/* - * Sub-field values for the PPC_ASYNC completion entry; these appear - * in app_data.bt[0] in the PPC_ASYNC application field. These are - * mutually exclusive and cannot be combined. The prefix AC indicates - * that this is a code for use in "asynchronous" completion entries - * only. -*/ - -#define AC_CON 0x01 /* connection detected */ -#define AC_DIS 0x02 /* disconnection detected */ -#define AC_BRK 0x03 /* asynchronous "break" */ -#define AC_FLU 0x04 /* xmit flush complete */ - -/* Line Discipline flags (input and output) */ - -#define IGNBRK 0x0001 -#define BRKINT 0x0002 -#define IGNPAR 0x0004 -#define PARMRK 0x0008 -#define INPCK 0x0010 -#define ISTRIP 0x0020 -#define INLCR 0x0040 -#define IGNCR 0x0080 -#define ICRNL 0x0100 -#define IUCLC 0x0200 -#define IXON 0x0400 -#define IXANY 0x0800 - -#define OPOST 0x0001 -#define OLCUC 0x0002 -#define ONLCR 0x0004 -#define OCRNL 0x0008 -#define ONOCR 0x0010 -#define ONLRET 0x0020 -#define OFILL 0x0040 -#define OFDEL 0x0080 -#define ONLDLY 0x0100 -#define OCRDLY 0x0600 -#define OTABDLY 0x1800 -#define OBSDLY 0x2000 -#define OVTDLY 0x4000 -#define OFFDLY 0x8000 - -/* Opcodes for PORTS card */ - -#define PPC_OPTIONS 32 /* GEN, COMP queues: set PPC options */ -#define PPC_XMIT 33 /* GEN, COMP queues: transmit a buffer */ -#define PPC_CONN 34 /* GEN, COMP queues: connect a device */ -#define PPC_DISC 35 /* GEN, COMP queues: disconnect a device */ -#define PPC_BRK 36 /* GEN, COMP queues: ioctl break */ -#define PPC_DEVICE 40 /* EXP, ECOMP entries: device control command */ -#define PPC_CLR 41 /* EXP, ECOMP entries: board clear */ -#define PPC_RECV 50 /* RECV, COMP queues: receive request */ -#define PPC_ASYNC 60 /* Asynchronous request */ -#define CFW_CONFIG 70 /* GEN, COMP queues: set PPC port 0 hardware options */ -#define CFW_IREAD 71 /* GEN, COMP queues: read immediate one to four bytes */ -#define CFW_IWRITE 72 /* GEN, COMP queues: write immediate one to four bytes */ -#define CFW_WRITE 73 /* GEN, COMP queues: write */ -#define PPC_VERS 80 /* EXP, COMP queues: Version */ - -typedef struct { - uint32 tx_addr; /* Address to next read from */ - uint32 tx_req_addr; /* Original request address */ - uint32 tx_chars; /* Number of chars left to transfer */ - uint32 tx_req_chars; /* Original number of chars */ - uint8 rlp; /* Last known load pointer */ - uint16 iflag; /* Line Discipline: Input flags */ - uint16 oflag; /* Line Discipline: Output flags */ - t_bool crlf; /* Indicates we are in a CRLF output transform */ - t_bool conn; /* TRUE if connected, FALSE otherwise */ -} PORTS_LINE_STATE; - -typedef struct { - uint16 line; /* line discipline */ - uint16 pad1; - uint16 iflag; /* input options word */ - uint16 oflag; /* output options word */ - uint16 cflag; /* hardware options */ - uint16 lflag; /* line discipline options */ - uint8 cerase; /* "erase" character */ - uint8 ckill; /* "kill" character */ - uint8 cinter; /* "interrupt" character */ - uint8 cquit; /* "quit" character */ - uint8 ceof; /* "end of file" character */ - uint8 ceol; /* "end of line" character */ - uint8 itime; /* inter character timer multiplier */ - uint8 vtime; /* user-specified inter char timer */ - uint8 vcount; /* user-specified maximum buffer char count */ - uint8 pad2; - uint16 pad3; -} PORTS_OPTIONS; - -t_stat ports_reset(DEVICE *dptr); -t_stat ports_setnl(UNIT *uptr, int32 val, CONST char *cptr, void *desc); -t_stat ports_rcv_svc(UNIT *uptr); -t_stat ports_xmt_svc(UNIT *uptr); -t_stat ports_cio_svc(UNIT *uptr); -t_stat ports_attach(UNIT *uptr, CONST char *cptr); -t_stat ports_detach(UNIT *uptr); -void ports_sysgen(uint8 slot); -void ports_express(uint8 slot); -void ports_full(uint8 slot); - -#endif /* _3B2_PORTS_H_ */ +/* 3b2_ports.h: CM195B 4-Port Serial CIO Card + + Copyright (c) 2018-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +/* + * PORTS is an intelligent feature card for the 3B2 that supports four + * serial lines and one Centronics parallel port. + * + * The PORTS card is based on the Common I/O (CIO) platform. It uses + * two SCN2681A DUARTs to supply the four serial lines, and uses the + * SCN2681A parallel I/O pins for the Centronics parallel port. + * + * This file implements the required logic for the PORTS CIO + * interface. The SCN2681A functionality is implemented in the file + * 3b2_duart.c, and is used by both this feature card and the System + * Board console/contty functionality. + */ + +#ifndef _3B2_PORTS_H_ +#define _3B2_PORTS_H_ + +#include "3b2_defs.h" + +#define PORTS_ID 0x0003 +#define PORTS_IPL 10 +#define PORTS_VERSION 1 + +#define MAX_CARDS 8 /* Up to 8 PORTS cards with 32 lines total + supported */ +#define PORTS_LINES 4 +#define PORTS_RCV_QUEUE 5 + +/* + * Sub-field values for the PPC_DEVICE request entry; these are placed + * in app_data.bt[0] in the PPC_DEVICE application field. The prefix + * DR indicates that this is a code for use in "device" request + * entries only. +*/ + +#define DR_ENA 1 /* enable a device */ +#define DR_DIS 2 /* disable a device */ +#define DR_ABR 3 /* abort reception on a device */ +#define DR_ABX 4 /* abort transmission on a device */ +#define DR_BRK 5 /* transmit "break" on a device */ +#define DR_SUS 6 /* suspend xmit on a device */ +#define DR_RES 7 /* resume xmit on a device */ +#define DR_BLK 8 /* transmit STOP character */ +#define DR_UNB 9 /* transmit START character */ + +/* + * Sub-field values for the PPC_DEVICE completion entry; these appear + * in app_data.bt[0] in the PPC_DEVICE application field. These are + * mutually exclusive and cannot be combined. The prefix DC indicates + * that this is a code for use in "device" completion entries only. + */ + +#define DC_NORM 0x00 /* command executed as requested */ +#define DC_DEV 0x01 /* bad device number */ +#define DC_NON 0x02 /* bad sub-code on request */ +#define DC_FAIL 0x03 /* failed to read express entry */ + +/* + * Sub-field values for the PPC_RECV completion entry; these appear in + * app_data.bt[0] in the PPC_RECV application field. These are NOT + * mutually exclusive and may appear in combination. The prefix RC + * indicates that this is a code for use in "read" completion entries + * only. +*/ + +#define RC_DSR 0x01 /* disruption of service */ +#define RC_FLU 0x02 /* buffer flushed */ +#define RC_TMR 0x04 /* inter-character timer expired */ +#define RC_BQO 0x08 /* PPC buffer queue overflow */ +#define RC_UAO 0x10 /* uart overrun */ +#define RC_PAR 0x20 /* parity error */ +#define RC_FRA 0x40 /* framing error */ +#define RC_BRK 0x80 /* break received */ + +/* + * The following codes are included on the DISC (disconnect) command. + * They are "or"ed into the app_data.bt[1] application field in a + * request. These codes are NOT mutually exclusive and can be used in + * any combination. + */ + +#define GR_DTR 0x01 +#define GR_CREAD 0x02 + +/* + * Sub-field values for the PPC_XMIT and PPC_OPTIONS completion + * entries; these appear in app_data.bt[0] in the application fields. + * These are NOT mutually exclusive and may appear in combination. + * The prefix GC indicates that this is a code for use in "general" + * completion entries only. +*/ + +#define GC_DSR 0x01 /* disruption of service */ +#define GC_FLU 0x02 /* buffer flushed */ + +/* + * Sub-field values for the PPC_ASYNC completion entry; these appear + * in app_data.bt[0] in the PPC_ASYNC application field. These are + * mutually exclusive and cannot be combined. The prefix AC indicates + * that this is a code for use in "asynchronous" completion entries + * only. +*/ + +#define AC_CON 0x01 /* connection detected */ +#define AC_DIS 0x02 /* disconnection detected */ +#define AC_BRK 0x03 /* asynchronous "break" */ +#define AC_FLU 0x04 /* xmit flush complete */ + +/* Line Discipline flags (input and output) */ + +#define IGNBRK 0x0001 +#define BRKINT 0x0002 +#define IGNPAR 0x0004 +#define PARMRK 0x0008 +#define INPCK 0x0010 +#define ISTRIP 0x0020 +#define INLCR 0x0040 +#define IGNCR 0x0080 +#define ICRNL 0x0100 +#define IUCLC 0x0200 +#define IXON 0x0400 +#define IXANY 0x0800 + +#define OPOST 0x0001 +#define OLCUC 0x0002 +#define ONLCR 0x0004 +#define OCRNL 0x0008 +#define ONOCR 0x0010 +#define ONLRET 0x0020 +#define OFILL 0x0040 +#define OFDEL 0x0080 +#define ONLDLY 0x0100 +#define OCRDLY 0x0600 +#define OTABDLY 0x1800 +#define OBSDLY 0x2000 +#define OVTDLY 0x4000 +#define OFFDLY 0x8000 + +/* Opcodes for PORTS card */ + +#define PPC_OPTIONS 32 /* GEN, COMP queues: set PPC options */ +#define PPC_XMIT 33 /* GEN, COMP queues: transmit a buffer */ +#define PPC_CONN 34 /* GEN, COMP queues: connect a device */ +#define PPC_DISC 35 /* GEN, COMP queues: disconnect a device */ +#define PPC_BRK 36 /* GEN, COMP queues: ioctl break */ +#define PPC_DEVICE 40 /* EXP, ECOMP entries: device control command */ +#define PPC_CLR 41 /* EXP, ECOMP entries: board clear */ +#define PPC_RECV 50 /* RECV, COMP queues: receive request */ +#define PPC_ASYNC 60 /* Asynchronous request */ +#define CFW_CONFIG 70 /* GEN, COMP queues: set PPC port 0 hardware options */ +#define CFW_IREAD 71 /* GEN, COMP queues: read immediate one to four bytes */ +#define CFW_IWRITE 72 /* GEN, COMP queues: write immediate one to four bytes */ +#define CFW_WRITE 73 /* GEN, COMP queues: write */ +#define PPC_VERS 80 /* EXP, COMP queues: Version */ + +typedef struct { + uint32 tx_addr; /* Address to next read from */ + uint32 tx_req_addr; /* Original request address */ + uint32 tx_chars; /* Number of chars left to transfer */ + uint32 tx_req_chars; /* Original number of chars */ + uint8 rlp; /* Last known load pointer */ + uint16 iflag; /* Line Discipline: Input flags */ + uint16 oflag; /* Line Discipline: Output flags */ + t_bool crlf; /* Indicates we are in a CRLF output transform */ + t_bool conn; /* TRUE if connected, FALSE otherwise */ +} PORTS_LINE_STATE; + +typedef struct { + uint16 line; /* line discipline */ + uint16 pad1; + uint16 iflag; /* input options word */ + uint16 oflag; /* output options word */ + uint16 cflag; /* hardware options */ + uint16 lflag; /* line discipline options */ + uint8 cerase; /* "erase" character */ + uint8 ckill; /* "kill" character */ + uint8 cinter; /* "interrupt" character */ + uint8 cquit; /* "quit" character */ + uint8 ceof; /* "end of file" character */ + uint8 ceol; /* "end of line" character */ + uint8 itime; /* inter character timer multiplier */ + uint8 vtime; /* user-specified inter char timer */ + uint8 vcount; /* user-specified maximum buffer char count */ + uint8 pad2; + uint16 pad3; +} PORTS_OPTIONS; + +t_stat ports_reset(DEVICE *dptr); +t_stat ports_setnl(UNIT *uptr, int32 val, CONST char *cptr, void *desc); +t_stat ports_rcv_svc(UNIT *uptr); +t_stat ports_xmt_svc(UNIT *uptr); +t_stat ports_cio_svc(UNIT *uptr); +t_stat ports_attach(UNIT *uptr, CONST char *cptr); +t_stat ports_detach(UNIT *uptr); +void ports_sysgen(uint8 slot); +void ports_express(uint8 slot); +void ports_full(uint8 slot); + +#endif /* _3B2_PORTS_H_ */ diff --git a/3B2/3b2_rev2_csr.c b/3B2/3b2_rev2_csr.c index d9e1924f..babc5d67 100644 --- a/3B2/3b2_rev2_csr.c +++ b/3B2/3b2_rev2_csr.c @@ -1,182 +1,182 @@ -/* 3b2_rev2_csr.c: ED System Board Control and Status Register - - Copyright (c) 2017-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#include "3b2_rev2_csr.h" - -#include "3b2_cpu.h" -#include "3b2_sys.h" -#include "3b2_timer.h" -#include "3b2_sys.h" - -CSR_DATA csr_data; - -BITFIELD csr_bits[] = { - BIT(IOF), - BIT(DMA), - BIT(DISK), - BIT(UART), - BIT(PIR9), - BIT(PIR8), - BIT(CLK), - BIT(IFLT), - BIT(ITIM), - BIT(FLOP), - BIT(NA), - BIT(LED), - BIT(ALGN), - BIT(RRST), - BIT(PARE), - BIT(TIMO), - ENDBITS -}; - -UNIT csr_unit = { - UDATA(NULL, UNIT_FIX, CSRSIZE) -}; - -REG csr_reg[] = { - { HRDATADF(DATA, csr_data, 16, "CSR Data", csr_bits) }, - { NULL } -}; - -DEVICE csr_dev = { - "CSR", &csr_unit, csr_reg, NULL, - 1, 16, 8, 4, 16, 32, - &csr_ex, &csr_dep, &csr_reset, - NULL, NULL, NULL, NULL, - DEV_DEBUG, 0, sys_deb_tab -}; - -t_stat csr_ex(t_value *vptr, t_addr exta, UNIT *uptr, int32 sw) -{ - return SCPE_OK; -} - -t_stat csr_dep(t_value val, t_addr exta, UNIT *uptr, int32 sw) -{ - return SCPE_OK; -} - -t_stat csr_reset(DEVICE *dptr) -{ - csr_data = 0; - return SCPE_OK; -} - -uint32 csr_read(uint32 pa, size_t size) -{ - uint32 reg = pa - CSRBASE; - - sim_debug(READ_MSG, &csr_dev, - "CSR=%04x\n", - csr_data); - - switch (reg) { - case 0x2: - if (size == 8) { - return (csr_data >> 8) & 0xff; - } else { - return csr_data; - } - case 0x3: - return csr_data & 0xff; - default: - return 0; - } -} - -void csr_write(uint32 pa, uint32 val, size_t size) -{ - uint32 reg = pa - CSRBASE; - - switch (reg) { - case 0x03: /* Clear Bus Timeout Error */ - csr_data &= ~CSRTIMO; - break; - case 0x07: /* Clear Memory Parity Error */ - csr_data &= ~CSRPARE; - break; - case 0x0b: /* Set System Reset Request */ - full_reset(); - cpu_boot(0, &cpu_dev); - break; - case 0x0f: /* Clear Memory Alignment Fault */ - csr_data &= ~CSRALGN; - break; - case 0x13: /* Set Failure LED */ - csr_data |= CSRLED; - break; - case 0x17: /* Clear Failure LED */ - csr_data &= ~CSRLED; - break; - case 0x1b: /* Set Floppy Motor On */ - csr_data |= CSRFLOP; - break; - case 0x1f: /* Clear Floppy Motor On */ - csr_data &= ~CSRFLOP; - break; - case 0x23: /* Set Inhibit Timers */ - sim_debug(WRITE_MSG, &csr_dev, - "SET INHIBIT TIMERS\n"); - csr_data |= CSRITIM; - timer_gate(TIMER_INTERVAL, TRUE); - break; - case 0x27: /* Clear Inhibit Timers */ - sim_debug(WRITE_MSG, &csr_dev, - "CLEAR INHIBIT TIMERS\n"); - csr_data &= ~CSRITIM; - timer_gate(TIMER_INTERVAL, FALSE); - break; - case 0x2b: /* Set Inhibit Faults */ - csr_data |= CSRIFLT; - break; - case 0x2f: /* Clear Inhibit Faults */ - csr_data &= ~CSRIFLT; - break; - case 0x33: /* Set PIR9 */ - csr_data |= CSRPIR9; - CPU_SET_INT(INT_PIR9); - break; - case 0x37: /* Clear PIR9 */ - csr_data &= ~CSRPIR9; - CPU_CLR_INT(INT_PIR9); - break; - case 0x3b: /* Set PIR8 */ - csr_data |= CSRPIR8; - CPU_SET_INT(INT_PIR8); - break; - case 0x3f: /* Clear PIR8 */ - csr_data &= ~CSRPIR8; - CPU_CLR_INT(INT_PIR8); - break; - default: - break; - } -} +/* 3b2_rev2_csr.c: ED System Board Control and Status Register + + Copyright (c) 2017-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#include "3b2_rev2_csr.h" + +#include "3b2_cpu.h" +#include "3b2_sys.h" +#include "3b2_timer.h" +#include "3b2_sys.h" + +CSR_DATA csr_data; + +BITFIELD csr_bits[] = { + BIT(IOF), + BIT(DMA), + BIT(DISK), + BIT(UART), + BIT(PIR9), + BIT(PIR8), + BIT(CLK), + BIT(IFLT), + BIT(ITIM), + BIT(FLOP), + BIT(NA), + BIT(LED), + BIT(ALGN), + BIT(RRST), + BIT(PARE), + BIT(TIMO), + ENDBITS +}; + +UNIT csr_unit = { + UDATA(NULL, UNIT_FIX, CSRSIZE) +}; + +REG csr_reg[] = { + { HRDATADF(DATA, csr_data, 16, "CSR Data", csr_bits) }, + { NULL } +}; + +DEVICE csr_dev = { + "CSR", &csr_unit, csr_reg, NULL, + 1, 16, 8, 4, 16, 32, + &csr_ex, &csr_dep, &csr_reset, + NULL, NULL, NULL, NULL, + DEV_DEBUG, 0, sys_deb_tab +}; + +t_stat csr_ex(t_value *vptr, t_addr exta, UNIT *uptr, int32 sw) +{ + return SCPE_OK; +} + +t_stat csr_dep(t_value val, t_addr exta, UNIT *uptr, int32 sw) +{ + return SCPE_OK; +} + +t_stat csr_reset(DEVICE *dptr) +{ + csr_data = 0; + return SCPE_OK; +} + +uint32 csr_read(uint32 pa, size_t size) +{ + uint32 reg = pa - CSRBASE; + + sim_debug(READ_MSG, &csr_dev, + "CSR=%04x\n", + csr_data); + + switch (reg) { + case 0x2: + if (size == 8) { + return (csr_data >> 8) & 0xff; + } else { + return csr_data; + } + case 0x3: + return csr_data & 0xff; + default: + return 0; + } +} + +void csr_write(uint32 pa, uint32 val, size_t size) +{ + uint32 reg = pa - CSRBASE; + + switch (reg) { + case 0x03: /* Clear Bus Timeout Error */ + csr_data &= ~CSRTIMO; + break; + case 0x07: /* Clear Memory Parity Error */ + csr_data &= ~CSRPARE; + break; + case 0x0b: /* Set System Reset Request */ + full_reset(); + cpu_boot(0, &cpu_dev); + break; + case 0x0f: /* Clear Memory Alignment Fault */ + csr_data &= ~CSRALGN; + break; + case 0x13: /* Set Failure LED */ + csr_data |= CSRLED; + break; + case 0x17: /* Clear Failure LED */ + csr_data &= ~CSRLED; + break; + case 0x1b: /* Set Floppy Motor On */ + csr_data |= CSRFLOP; + break; + case 0x1f: /* Clear Floppy Motor On */ + csr_data &= ~CSRFLOP; + break; + case 0x23: /* Set Inhibit Timers */ + sim_debug(WRITE_MSG, &csr_dev, + "SET INHIBIT TIMERS\n"); + csr_data |= CSRITIM; + timer_gate(TIMER_INTERVAL, TRUE); + break; + case 0x27: /* Clear Inhibit Timers */ + sim_debug(WRITE_MSG, &csr_dev, + "CLEAR INHIBIT TIMERS\n"); + csr_data &= ~CSRITIM; + timer_gate(TIMER_INTERVAL, FALSE); + break; + case 0x2b: /* Set Inhibit Faults */ + csr_data |= CSRIFLT; + break; + case 0x2f: /* Clear Inhibit Faults */ + csr_data &= ~CSRIFLT; + break; + case 0x33: /* Set PIR9 */ + csr_data |= CSRPIR9; + CPU_SET_INT(INT_PIR9); + break; + case 0x37: /* Clear PIR9 */ + csr_data &= ~CSRPIR9; + CPU_CLR_INT(INT_PIR9); + break; + case 0x3b: /* Set PIR8 */ + csr_data |= CSRPIR8; + CPU_SET_INT(INT_PIR8); + break; + case 0x3f: /* Clear PIR8 */ + csr_data &= ~CSRPIR8; + CPU_CLR_INT(INT_PIR8); + break; + default: + break; + } +} diff --git a/3B2/3b2_rev2_csr.h b/3B2/3b2_rev2_csr.h index c73d2e6a..cd31ea2f 100644 --- a/3B2/3b2_rev2_csr.h +++ b/3B2/3b2_rev2_csr.h @@ -1,46 +1,46 @@ -/* 3b2_rev2_csr.h: ED System Board Control and Status Register - - Copyright (c) 2021-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#ifndef _3B2_REV2_CSR_H_ -#define _3B2_REV2_CSR_H_ - -#include "3b2_defs.h" - -typedef uint16 CSR_DATA; - -/* CSR */ -t_stat csr_svc(UNIT *uptr); -t_stat csr_ex(t_value *vptr, t_addr exta, UNIT *uptr, int32 sw); -t_stat csr_dep(t_value val, t_addr exta, UNIT *uptr, int32 sw); -t_stat csr_reset(DEVICE *dptr); -uint32 csr_read(uint32 pa, size_t size); -void csr_write(uint32 pa, uint32 val, size_t size); - -#endif /* 3B2_REV2_CSR_H_ */ +/* 3b2_rev2_csr.h: ED System Board Control and Status Register + + Copyright (c) 2021-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#ifndef _3B2_REV2_CSR_H_ +#define _3B2_REV2_CSR_H_ + +#include "3b2_defs.h" + +typedef uint16 CSR_DATA; + +/* CSR */ +t_stat csr_svc(UNIT *uptr); +t_stat csr_ex(t_value *vptr, t_addr exta, UNIT *uptr, int32 sw); +t_stat csr_dep(t_value val, t_addr exta, UNIT *uptr, int32 sw); +t_stat csr_reset(DEVICE *dptr); +uint32 csr_read(uint32 pa, size_t size); +void csr_write(uint32 pa, uint32 val, size_t size); + +#endif /* 3B2_REV2_CSR_H_ */ diff --git a/3B2/3b2_rev2_defs.h b/3B2/3b2_rev2_defs.h index 81281209..e2960257 100644 --- a/3B2/3b2_rev2_defs.h +++ b/3B2/3b2_rev2_defs.h @@ -1,132 +1,132 @@ -/* 3b2_rev2_defs.h: Version 2 (3B2/400) Common Definitions - - Copyright (c) 2017-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. - */ - -#ifndef _3B2_REV2_DEFS_H_ -#define _3B2_REV2_DEFS_H_ - -#define NUM_REGISTERS 16 - -#define DEFMEMSIZE MSIZ_4M -#define MAXMEMSIZE MSIZ_4M - -#define HWORD_OP_COUNT 11 -#define CPU_VERSION 0x1A /* Version encoded in WE32100 */ - -#define TODBASE 0x41000 -#define TODSIZE 0x40 -#define TIMERBASE 0x42000 -#define TIMERSIZE 0x20 -#define NVRBASE 0x43000 -#define NVRSIZE 0x1000 -#define CSRBASE 0x44000 -#define CSRSIZE 0x100 -#define IFBASE 0x4d000 -#define IFSIZE 0x10 -#define IDBASE 0x4a000 -#define IDSIZE 0x2 - -#define IF_STATUS_REG 0 -#define IF_CMD_REG 0 -#define IF_TRACK_REG 1 -#define IF_SECTOR_REG 2 -#define IF_DATA_REG 3 - -#define ID_DATA_REG 0 -#define ID_CMD_STAT_REG 1 - -/* CSR Flags */ -#define CSRTIMO 0x8000 /* Bus Timeout Error */ -#define CSRPARE 0x4000 /* Memory Parity Error */ -#define CSRRRST 0x2000 /* System Reset Request */ -#define CSRALGN 0x1000 /* Memory Alignment Fault */ -#define CSRLED 0x0800 /* Failure LED */ -#define CSRFLOP 0x0400 /* Floppy Motor On */ -#define CSRRES 0x0200 /* Reserved */ -#define CSRITIM 0x0100 /* Inhibit Timers */ -#define CSRIFLT 0x0080 /* Inhibit Faults */ -#define CSRCLK 0x0040 /* Clock Interrupt */ -#define CSRPIR8 0x0020 /* Programmed Interrupt 8 */ -#define CSRPIR9 0x0010 /* Programmed Interrupt 9 */ -#define CSRUART 0x0008 /* UART Interrupt */ -#define CSRDISK 0x0004 /* Floppy Interrupt */ -#define CSRDMA 0x0002 /* DMA Interrupt */ -#define CSRIOF 0x0001 /* I/O Board Fail */ - -/* Interrupt Sources */ -#define INT_SERR 0x01 /* IPL 15 */ -#define INT_CLOCK 0x02 /* IPL 15 */ -#define INT_DMA 0x04 /* IPL 13 */ -#define INT_UART 0x08 /* IPL 13 */ -#define INT_DISK 0x10 /* IPL 11 */ -#define INT_FLOPPY 0x20 /* IPL 11 */ -#define INT_PIR9 0x40 /* IPL 9 */ -#define INT_PIR8 0x80 /* IPL 8 */ - -#define INT_MAP_LEN 0x100 - -/* Memory */ -#define MEMSIZE_REG 0x4C003 -#define MEMID_512K 0 -#define MEMID_1M 2 -#define MEMID_2M 1 -#define MEMID_4M 3 - -/* DMA Controller */ -#define DMACBASE 0x48000 -#define DMACSIZE 0x11 - -/* DMA integrated disk page buffer */ -#define DMAIDBASE 0x45000 -#define DMAIDSIZE 0x5 - -/* DMA integrated uart A page buffer */ -#define DMAIUABASE 0x46000 -#define DMAIUASIZE 0x5 - -/* DMA integrated uart B page buffer */ -#define DMAIUBBASE 0x47000 -#define DMAIUBSIZE 0x5 - -/* DMA integrated floppy page buffer */ -#define DMAIFBASE 0x4E000 -#define DMAIFSIZE 0x5 - -#define DMA_ID_CHAN 0 -#define DMA_IF_CHAN 1 -#define DMA_IUA_CHAN 2 -#define DMA_IUB_CHAN 3 - -#define DMA_ID 0x45 -#define DMA_IUA 0x46 -#define DMA_IUB 0x47 -#define DMA_C 0x48 -#define DMA_IF 0x4E - -#endif /* _3B2_REV2_DEFS_H_ */ +/* 3b2_rev2_defs.h: Version 2 (3B2/400) Common Definitions + + Copyright (c) 2017-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. + */ + +#ifndef _3B2_REV2_DEFS_H_ +#define _3B2_REV2_DEFS_H_ + +#define NUM_REGISTERS 16 + +#define DEFMEMSIZE MSIZ_4M +#define MAXMEMSIZE MSIZ_4M + +#define HWORD_OP_COUNT 11 +#define CPU_VERSION 0x1A /* Version encoded in WE32100 */ + +#define TODBASE 0x41000 +#define TODSIZE 0x40 +#define TIMERBASE 0x42000 +#define TIMERSIZE 0x20 +#define NVRBASE 0x43000 +#define NVRSIZE 0x1000 +#define CSRBASE 0x44000 +#define CSRSIZE 0x100 +#define IFBASE 0x4d000 +#define IFSIZE 0x10 +#define IDBASE 0x4a000 +#define IDSIZE 0x2 + +#define IF_STATUS_REG 0 +#define IF_CMD_REG 0 +#define IF_TRACK_REG 1 +#define IF_SECTOR_REG 2 +#define IF_DATA_REG 3 + +#define ID_DATA_REG 0 +#define ID_CMD_STAT_REG 1 + +/* CSR Flags */ +#define CSRTIMO 0x8000 /* Bus Timeout Error */ +#define CSRPARE 0x4000 /* Memory Parity Error */ +#define CSRRRST 0x2000 /* System Reset Request */ +#define CSRALGN 0x1000 /* Memory Alignment Fault */ +#define CSRLED 0x0800 /* Failure LED */ +#define CSRFLOP 0x0400 /* Floppy Motor On */ +#define CSRRES 0x0200 /* Reserved */ +#define CSRITIM 0x0100 /* Inhibit Timers */ +#define CSRIFLT 0x0080 /* Inhibit Faults */ +#define CSRCLK 0x0040 /* Clock Interrupt */ +#define CSRPIR8 0x0020 /* Programmed Interrupt 8 */ +#define CSRPIR9 0x0010 /* Programmed Interrupt 9 */ +#define CSRUART 0x0008 /* UART Interrupt */ +#define CSRDISK 0x0004 /* Floppy Interrupt */ +#define CSRDMA 0x0002 /* DMA Interrupt */ +#define CSRIOF 0x0001 /* I/O Board Fail */ + +/* Interrupt Sources */ +#define INT_SERR 0x01 /* IPL 15 */ +#define INT_CLOCK 0x02 /* IPL 15 */ +#define INT_DMA 0x04 /* IPL 13 */ +#define INT_UART 0x08 /* IPL 13 */ +#define INT_DISK 0x10 /* IPL 11 */ +#define INT_FLOPPY 0x20 /* IPL 11 */ +#define INT_PIR9 0x40 /* IPL 9 */ +#define INT_PIR8 0x80 /* IPL 8 */ + +#define INT_MAP_LEN 0x100 + +/* Memory */ +#define MEMSIZE_REG 0x4C003 +#define MEMID_512K 0 +#define MEMID_1M 2 +#define MEMID_2M 1 +#define MEMID_4M 3 + +/* DMA Controller */ +#define DMACBASE 0x48000 +#define DMACSIZE 0x11 + +/* DMA integrated disk page buffer */ +#define DMAIDBASE 0x45000 +#define DMAIDSIZE 0x5 + +/* DMA integrated uart A page buffer */ +#define DMAIUABASE 0x46000 +#define DMAIUASIZE 0x5 + +/* DMA integrated uart B page buffer */ +#define DMAIUBBASE 0x47000 +#define DMAIUBSIZE 0x5 + +/* DMA integrated floppy page buffer */ +#define DMAIFBASE 0x4E000 +#define DMAIFSIZE 0x5 + +#define DMA_ID_CHAN 0 +#define DMA_IF_CHAN 1 +#define DMA_IUA_CHAN 2 +#define DMA_IUB_CHAN 3 + +#define DMA_ID 0x45 +#define DMA_IUA 0x46 +#define DMA_IUB 0x47 +#define DMA_C 0x48 +#define DMA_IF 0x4E + +#endif /* _3B2_REV2_DEFS_H_ */ diff --git a/3B2/3b2_rev2_mmu.c b/3B2/3b2_rev2_mmu.c index 7dc95497..88383cb3 100644 --- a/3B2/3b2_rev2_mmu.c +++ b/3B2/3b2_rev2_mmu.c @@ -1,822 +1,822 @@ -/* 3b2_rev2_mmu.c: WE32101 MMU - - Copyright (c) 2017-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - - -#include "3b2_cpu.h" -#include "3b2_mmu.h" -#include "3b2_sys.h" -#include "3b2_mem.h" - -UNIT mmu_unit = { UDATA(NULL, 0, 0) }; - -MMU_STATE mmu_state; - -REG mmu_reg[] = { - { HRDATAD (ENABLE, mmu_state.enabled, 1, "Enabled?") }, - { HRDATAD (CONFIG, mmu_state.conf, 32, "Configuration") }, - { HRDATAD (VAR, mmu_state.var, 32, "Virtual Address") }, - { HRDATAD (FCODE, mmu_state.fcode, 32, "Fault Code") }, - { HRDATAD (FADDR, mmu_state.faddr, 32, "Fault Address") }, - { BRDATA (SDCL, mmu_state.sdcl, 16, 32, MMU_SDCS) }, - { BRDATA (SDCR, mmu_state.sdch, 16, 32, MMU_SDCS) }, - { BRDATA (PDCLL, mmu_state.pdcll, 16, 32, MMU_PDCS) }, - { BRDATA (PDCLH, mmu_state.pdclh, 16, 32, MMU_PDCS) }, - { BRDATA (PDCRL, mmu_state.pdcrl, 16, 32, MMU_PDCS) }, - { BRDATA (PDCRH, mmu_state.pdcrh, 16, 32, MMU_PDCS) }, - { BRDATA (SRAMA, mmu_state.sra, 16, 32, MMU_SRS) }, - { BRDATA (SRAMB, mmu_state.srb, 16, 32, MMU_SRS) }, - { NULL } -}; - -DEVICE mmu_dev = { - "MMU", /* name */ - &mmu_unit, /* units */ - mmu_reg, /* registers */ - NULL, /* modifiers */ - 1, /* #units */ - 16, /* address radix */ - 8, /* address width */ - 4, /* address incr */ - 16, /* data radix */ - 32, /* data width */ - NULL, /* examine routine */ - NULL, /* deposit routine */ - &mmu_init, /* reset routine */ - NULL, /* boot routine */ - NULL, /* attach routine */ - NULL, /* detach routine */ - NULL, /* context */ - DEV_DEBUG, /* flags */ - 0, /* debug control flags */ - sys_deb_tab, /* debug flag names */ - NULL, /* memory size change */ - NULL, /* logical name */ - NULL, /* help routine */ - NULL, /* attach help routine */ - NULL, /* help context */ - &mmu_description /* device description */ -}; - -/* - * Find an SD in the cache. - */ -static SIM_INLINE t_stat get_sdce(uint32 va, uint32 *sd0, uint32 *sd1) -{ - uint32 tag, sdch, sdcl; - uint8 ci; - - ci = (SID(va) * NUM_SDCE) + SD_IDX(va); - tag = SD_TAG(va); - - sdch = mmu_state.sdch[ci]; - sdcl = mmu_state.sdcl[ci]; - - if ((sdch & SD_GOOD_MASK) && SDCE_TAG(sdcl) == tag) { - *sd0 = SDCE_TO_SD0(sdch, sdcl); - *sd1 = SDCE_TO_SD1(sdch); - return SCPE_OK; - } - - return SCPE_NXM; -} - -/* - * Find a PD in the cache. Sets both the PD and the cached access - * permissions. - */ -static SIM_INLINE t_stat get_pdce(uint32 va, uint32 *pd, uint8 *pd_acc) -{ - uint32 tag, pdcll, pdclh, pdcrl, pdcrh; - uint8 ci; - - ci = (SID(va) * NUM_PDCE) + PD_IDX(va); - tag = PD_TAG(va); - - /* Left side */ - pdcll = mmu_state.pdcll[ci]; - pdclh = mmu_state.pdclh[ci]; - - /* Right side */ - pdcrl = mmu_state.pdcrl[ci]; - pdcrh = mmu_state.pdcrh[ci]; - - /* Search L and R to find a good entry with a matching tag. */ - if ((pdclh & PD_GOOD_MASK) && PDCXL_TAG(pdcll) == tag) { - *pd = PDCXH_TO_PD(pdclh); - *pd_acc = PDCXL_TO_ACC(pdcll); - return SCPE_OK; - } else if ((pdcrh & PD_GOOD_MASK) && PDCXL_TAG(pdcrl) == tag) { - *pd = PDCXH_TO_PD(pdcrh); - *pd_acc = PDCXL_TO_ACC(pdcrl); - return SCPE_OK; - } - - return SCPE_NXM; -} - -static SIM_INLINE void put_sdce(uint32 va, uint32 sd0, uint32 sd1) -{ - uint8 ci; - - ci = (SID(va) * NUM_SDCE) + SD_IDX(va); - - mmu_state.sdcl[ci] = SD_TO_SDCL(va, sd0); - mmu_state.sdch[ci] = SD_TO_SDCH(sd0, sd1); -} - - -static SIM_INLINE void put_pdce(uint32 va, uint32 sd0, uint32 pd) -{ - uint8 ci; - - ci = (SID(va) * NUM_PDCE) + PD_IDX(va); - - /* Cache Replacement Algorithm - * (from the WE32101 MMU Information Manual) - * - * 1. If G==0 for the left-hand entry, the new PD is cached in the - * left-hand entry and the U bit (left-hand side) is cleared to - * 0. - * - * 2. If G==1 for the left-hand entry, and G==0 for the right-hand - * entry, the new PD is cached in the right-hand entry and the - * U bit (left-hand side) is set to 1. - * - * 3. If G==1 for both entries, the U bit in the left-hand entry - * is examined. If U==0, the new PD is cached in the right-hand - * entry of the PDC row and U is set to 1. If U==1, it is - * cached in the left-hand entry and U is cleared to 0. - */ - - if ((mmu_state.pdclh[ci] & PD_GOOD_MASK) == 0) { - /* Use the left entry */ - mmu_state.pdcll[ci] = SD_TO_PDCXL(va, sd0); - mmu_state.pdclh[ci] = PD_TO_PDCXH(pd, sd0); - mmu_state.pdclh[ci] &= ~PDCLH_USED_MASK; - } else if ((mmu_state.pdcrh[ci] & PD_GOOD_MASK) == 0) { - /* Use the right entry */ - mmu_state.pdcrl[ci] = SD_TO_PDCXL(va, sd0); - mmu_state.pdcrh[ci] = PD_TO_PDCXH(pd, sd0); - mmu_state.pdclh[ci] |= PDCLH_USED_MASK; - } else { - /* Pick the least-recently-replaced side */ - if (mmu_state.pdclh[ci] & PDCLH_USED_MASK) { - mmu_state.pdcll[ci] = SD_TO_PDCXL(va, sd0); - mmu_state.pdclh[ci] = PD_TO_PDCXH(pd, sd0); - mmu_state.pdclh[ci] &= ~PDCLH_USED_MASK; - } else { - mmu_state.pdcrl[ci] = SD_TO_PDCXL(va, sd0); - mmu_state.pdcrh[ci] = PD_TO_PDCXH(pd, sd0); - mmu_state.pdclh[ci] |= PDCLH_USED_MASK; - } - } -} - -static SIM_INLINE void flush_sdce(uint32 va) -{ - uint8 ci; - - ci = (SID(va) * NUM_SDCE) + SD_IDX(va); - - if (mmu_state.sdch[ci] & SD_GOOD_MASK) { - mmu_state.sdch[ci] &= ~SD_GOOD_MASK; - } -} - -static SIM_INLINE void flush_pdce(uint32 va) -{ - uint32 tag, pdcll, pdclh, pdcrl, pdcrh; - uint8 ci; - - ci = (SID(va) * NUM_PDCE) + PD_IDX(va); - tag = PD_TAG(va); - - /* Left side */ - pdcll = mmu_state.pdcll[ci]; - pdclh = mmu_state.pdclh[ci]; - /* Right side */ - pdcrl = mmu_state.pdcrl[ci]; - pdcrh = mmu_state.pdcrh[ci]; - - /* Search L and R to find a good entry with a matching tag. */ - if ((pdclh & PD_GOOD_MASK) && PDCXL_TAG(pdcll) == tag) { - mmu_state.pdclh[ci] &= ~PD_GOOD_MASK; - } else if ((pdcrh & PD_GOOD_MASK) && PDCXL_TAG(pdcrl) == tag) { - mmu_state.pdcrh[ci] &= ~PD_GOOD_MASK; - } -} - -static SIM_INLINE void flush_cache_sec(uint8 sec) -{ - int i; - - for (i = 0; i < NUM_SDCE; i++) { - mmu_state.sdch[(sec * NUM_SDCE) + i] &= ~SD_GOOD_MASK; - } - for (i = 0; i < NUM_PDCE; i++) { - mmu_state.pdclh[(sec * NUM_PDCE) + i] &= ~PD_GOOD_MASK; - mmu_state.pdcrh[(sec * NUM_PDCE) + i] &= ~PD_GOOD_MASK; - } -} - -static SIM_INLINE void flush_caches() -{ - uint8 i; - - for (i = 0; i < NUM_SEC; i++) { - flush_cache_sec(i); - } -} - -static SIM_INLINE t_stat mmu_check_perm(uint8 flags, uint8 r_acc) -{ - switch(MMU_PERM(flags)) { - case 0: /* No Access */ - return SCPE_NXM; - case 1: /* Exec Only */ - if (r_acc != ACC_IF && - r_acc != ACC_IFAD) { - return SCPE_NXM; - } - return SCPE_OK; - case 2: /* Read / Execute */ - if (r_acc != ACC_IF && - r_acc != ACC_IFAD && - r_acc != ACC_OF && - r_acc != ACC_AF && - r_acc != ACC_MT) { - return SCPE_NXM; - } - return SCPE_OK; - default: - return SCPE_OK; - } -} - -/* - * Update the M (modified) or R (referenced) bit the SD and cache - */ -static SIM_INLINE void mmu_update_sd(uint32 va, uint32 mask) -{ - uint32 sd0; - uint8 ci; - - ci = (SID(va) * NUM_SDCE) + SD_IDX(va); - - /* We go back to main memory to find the SD because the SD may - have been loaded from cache, which is lossy. */ - sd0 = pread_w(SD_ADDR(va), BUS_PER); - pwrite_w(SD_ADDR(va), sd0|mask, BUS_PER); - - /* There is no 'R' bit in the SD cache, only an 'M' bit. */ - if (mask == SD_M_MASK) { - mmu_state.sdch[ci] |= mask; - } -} - -/* - * Update the M (modified) or R (referenced) bit the PD and cache - */ -static SIM_INLINE void mmu_update_pd(uint32 va, uint32 pd_addr, uint32 mask) -{ - uint32 pd, tag, pdcll, pdclh, pdcrl, pdcrh; - uint8 ci; - - tag = PD_TAG(va); - ci = (SID(va) * NUM_PDCE) + PD_IDX(va); - - /* We go back to main memory to find the PD because the PD may - have been loaded from cache, which is lossy. */ - pd = pread_w(pd_addr, BUS_PER); - pwrite_w(pd_addr, pd|mask, BUS_PER); - - /* Update in the cache */ - - /* Left side */ - pdcll = mmu_state.pdcll[ci]; - pdclh = mmu_state.pdclh[ci]; - - /* Right side */ - pdcrl = mmu_state.pdcrl[ci]; - pdcrh = mmu_state.pdcrh[ci]; - - /* Search L and R to find a good entry with a matching tag, then - update the appropriate bit */ - if ((pdclh & PD_GOOD_MASK) && PDCXL_TAG(pdcll) == tag) { - mmu_state.pdclh[ci] |= mask; - } else if ((pdcrh & PD_GOOD_MASK) && PDCXL_TAG(pdcrl) == tag) { - mmu_state.pdcrh[ci] |= mask; - } -} - -t_stat mmu_init(DEVICE *dptr) -{ - flush_caches(); - return SCPE_OK; -} - -uint32 mmu_read(uint32 pa, size_t size) -{ - uint32 offset; - uint32 data = 0; - - offset = (pa >> 2) & 0x1f; - - switch ((pa >> 8) & 0xf) { - case MMU_SDCL: - data = mmu_state.sdcl[offset]; - sim_debug(READ_MSG, &mmu_dev, - "[pa=%08x] MMU_SDCL[%d] = %08x\n", - pa, offset, data); - break; - case MMU_SDCH: - data = mmu_state.sdch[offset]; - sim_debug(READ_MSG, &mmu_dev, - "MMU_SDCH[%d] = %08x\n", - offset, data); - break; - case MMU_PDCRL: - data = mmu_state.pdcrl[offset]; - sim_debug(READ_MSG, &mmu_dev, - "MMU_PDCRL[%d] = %08x\n", - offset, data); - break; - case MMU_PDCRH: - data = mmu_state.pdcrh[offset]; - sim_debug(READ_MSG, &mmu_dev, - "MMU_PDCRH[%d] = %08x\n", - offset, data); - break; - case MMU_PDCLL: - data = mmu_state.pdcll[offset]; - sim_debug(READ_MSG, &mmu_dev, - "MMU_PDCLL[%d] = %08x\n", - offset, data); - break; - case MMU_PDCLH: - data = mmu_state.pdclh[offset]; - sim_debug(READ_MSG, &mmu_dev, - "MMU_PDCLH[%d] = %08x\n", - offset, data); - break; - case MMU_SRAMA: - data = mmu_state.sra[offset]; - sim_debug(READ_MSG, &mmu_dev, - "MMU_SRAMA[%d] = %08x\n", - offset, data); - break; - case MMU_SRAMB: - data = mmu_state.srb[offset]; - sim_debug(READ_MSG, &mmu_dev, - "MMU_SRAMB[%d] = %08x\n", - offset, data); - break; - case MMU_FC: - data = mmu_state.fcode; - break; - case MMU_FA: - data = mmu_state.faddr; - break; - case MMU_CONF: - data = mmu_state.conf & 0x7; - sim_debug(READ_MSG, &mmu_dev, - "MMU_CONF = %08x\n", - data); - break; - case MMU_VAR: - data = mmu_state.var; - sim_debug(READ_MSG, &mmu_dev, - "MMU_VAR = %08x\n", - data); - break; - } - - return data; -} - -void mmu_write(uint32 pa, uint32 val, size_t size) -{ - uint32 offset; - - offset = (pa >> 2) & 0x1f; - - switch ((pa >> 8) & 0xf) { - case MMU_SDCL: - sim_debug(WRITE_MSG, &mmu_dev, - "MMU_SDCL[%d] = %08x\n", - offset, val); - mmu_state.sdcl[offset] = val; - break; - case MMU_SDCH: - sim_debug(WRITE_MSG, &mmu_dev, - "MMU_SDCH[%d] = %08x\n", - offset, val); - mmu_state.sdch[offset] = val; - break; - case MMU_PDCRL: - sim_debug(WRITE_MSG, &mmu_dev, - "MMU_PDCRL[%d] = %08x\n", - offset, val); - mmu_state.pdcrl[offset] = val; - break; - case MMU_PDCRH: - sim_debug(WRITE_MSG, &mmu_dev, - "MMU_PDCRH[%d] = %08x\n", - offset, val); - mmu_state.pdcrh[offset] = val; - break; - case MMU_PDCLL: - sim_debug(WRITE_MSG, &mmu_dev, - "MMU_PDCLL[%d] = %08x\n", - offset, val); - mmu_state.pdcll[offset] = val; - break; - case MMU_PDCLH: - sim_debug(WRITE_MSG, &mmu_dev, - "MMU_PDCLH[%d] = %08x\n", - offset, val); - mmu_state.pdclh[offset] = val; - break; - case MMU_SRAMA: - offset = offset & 3; - mmu_state.sra[offset] = val; - mmu_state.sec[offset].addr = val & 0xffffffe0; - /* We flush the entire section on writing SRAMA */ - sim_debug(WRITE_MSG, &mmu_dev, - "MMU_SRAMA[%d] = %08x\n", - offset, val); - flush_cache_sec((uint8) offset); - break; - case MMU_SRAMB: - offset = offset & 3; - mmu_state.srb[offset] = val; - mmu_state.sec[offset].len = (val >> 10) & 0x1fff; - /* We do not flush the cache on writing SRAMB */ - sim_debug(WRITE_MSG, &mmu_dev, - "MMU_SRAMB[%d] = %08x (len=%06x)\n", - offset, val, mmu_state.sec[offset].len); - break; - case MMU_FC: - mmu_state.fcode = val; - break; - case MMU_FA: - mmu_state.faddr = val; - break; - case MMU_CONF: - mmu_state.conf = val & 0x7; - break; - case MMU_VAR: - mmu_state.var = val; - flush_sdce(val); - flush_pdce(val); - break; - } -} - -/* Helper functions for MMU decode. */ - -/* - * Get the Segment Descriptor for a virtual address on a cache miss. - * - * Returns SCPE_OK on success, SCPE_NXM on failure. - * - * If SCPE_NXM is returned, a failure code and fault address will be - * set in the appropriate registers. - * - * As always, the flag 'fc' may be set to FALSE to avoid certain - * typses of fault checking. - * - */ -t_stat mmu_get_sd(uint32 va, uint8 r_acc, t_bool fc, - uint32 *sd0, uint32 *sd1) -{ - /* We immediately do some bounds checking (fc flag is not checked - * because this is a fatal error) */ - if (SSL(va) > SRAMB_LEN(va)) { - MMU_FAULT(MMU_F_SDTLEN); - sim_debug(EXECUTE_MSG, &mmu_dev, - "SDT Length Fault. sramb_len=%x ssl=%x va=%08x\n", - SRAMB_LEN(va), SSL(va), va); - return SCPE_NXM; - } - - /* sd0 contains the segment descriptor, sd1 contains a pointer to - the PDT or Segment */ - *sd0 = pread_w(SD_ADDR(va), BUS_PER); - *sd1 = pread_w(SD_ADDR(va) + 4, BUS_PER); - - if (!SD_VALID(*sd0)) { - sim_debug(EXECUTE_MSG, &mmu_dev, - "Invalid Segment Descriptor. va=%08x sd0=%08x\n", - va, *sd0); - MMU_FAULT(MMU_F_INV_SD); - return SCPE_NXM; - } - - /* TODO: Handle indirect lookups. */ - if (SD_INDIRECT(*sd0)) { - stop_reason = STOP_MMU; - return SCPE_NXM; - } - - /* If the segment descriptor isn't present, we need to - * fail out */ - if (!SD_PRESENT(*sd0)) { - if (SD_CONTIG(*sd0)) { - sim_debug(EXECUTE_MSG, &mmu_dev, - "Segment Not Present. va=%08x", - va); - MMU_FAULT(MMU_F_SEG_NOT_PRES); - return SCPE_NXM; - } else { - sim_debug(EXECUTE_MSG, &mmu_dev, - "PDT Not Present. va=%08x", - va); - MMU_FAULT(MMU_F_PDT_NOT_PRES); - return SCPE_NXM; - } - } - - if (SHOULD_CACHE_SD(*sd0)) { - put_sdce(va, *sd0, *sd1); - } - - return SCPE_OK; -} - -/* - * Load a page descriptor from memory - */ -t_stat mmu_get_pd(uint32 va, uint8 r_acc, t_bool fc, - uint32 sd0, uint32 sd1, - uint32 *pd, uint8 *pd_acc) -{ - uint32 pd_addr; - - /* Where do we find the page descriptor? */ - pd_addr = SD_SEG_ADDR(sd1) + (PSL(va) * 4); - - /* Bounds checking on length */ - if ((PSL(va) * 4) >= MAX_OFFSET(sd0)) { - sim_debug(EXECUTE_MSG, &mmu_dev, - "PDT Length Fault. " - "PDT Offset=%08x Max Offset=%08x va=%08x\n", - (PSL(va) * 4), - MAX_OFFSET(sd0), va); - MMU_FAULT(MMU_F_PDTLEN); - return SCPE_NXM; - } - - *pd = pread_w(pd_addr, BUS_PER); - - /* Copy the access flags from the SD */ - *pd_acc = SD_ACC(sd0); - - /* Cache it */ - if (SHOULD_CACHE_PD(*pd)) { - put_pdce(va, sd0, *pd); - } - - return SCPE_OK; -} - -/* - * Decode an address from a contiguous segment. - */ -t_stat mmu_decode_contig(uint32 va, uint8 r_acc, - uint32 sd0, uint32 sd1, - t_bool fc, uint32 *pa) -{ - if (fc) { - /* Update R and M bits if configured */ - if (SHOULD_UPDATE_SD_R_BIT(sd0)) { - sim_debug(EXECUTE_MSG, &mmu_dev, - "Updating R bit in SD\n"); - mmu_update_sd(va, SD_R_MASK); - } - - if (SHOULD_UPDATE_SD_M_BIT(sd0)) { - sim_debug(EXECUTE_MSG, &mmu_dev, - "Updating M bit in SD\n"); - mmu_update_sd(va, SD_M_MASK); - } - - /* Generate object trap if needed */ - if (SD_TRAP(sd0)) { - sim_debug(EXECUTE_MSG, &mmu_dev, - "Object Trap. va=%08x", - va); - MMU_FAULT(MMU_F_OTRAP); - return SCPE_NXM; - } - } - - *pa = SD_SEG_ADDR(sd1) + SOT(va); - return SCPE_OK; -} - -t_stat mmu_decode_paged(uint32 va, uint8 r_acc, t_bool fc, - uint32 sd1, uint32 pd, - uint8 pd_acc, uint32 *pa) -{ - /* If the PD is not marked present, fail */ - if (!PD_PRESENT(pd)) { - sim_debug(EXECUTE_MSG, &mmu_dev, - "Page Not Present. " - "pd=%08x r_acc=%x va=%08x\n", - pd, r_acc, va); - MMU_FAULT(MMU_F_PAGE_NOT_PRES); - return SCPE_NXM; - } - - if (fc) { - /* If this is a write or interlocked read access, and - the 'W' bit is set, trigger a write fault */ - if ((r_acc == ACC_W || r_acc == ACC_IR) && PD_WFAULT(pd)) { - sim_debug(EXECUTE_MSG, &mmu_dev, - "Page Write Fault. va=%08x\n", - va); - MMU_FAULT(MMU_F_PW); - return SCPE_NXM; - } - - /* If this is a write, modify the M bit */ - if (SHOULD_UPDATE_PD_M_BIT(pd)) { - sim_debug(EXECUTE_MSG, &mmu_dev, - "Updating M bit in PD\n"); - mmu_update_pd(va, PD_LOC(sd1, va), PD_M_MASK); - } - - /* Modify the R bit and write it back */ - if (SHOULD_UPDATE_PD_R_BIT(pd)) { - sim_debug(EXECUTE_MSG, &mmu_dev, - "Updating R bit in PD\n"); - mmu_update_pd(va, PD_LOC(sd1, va), PD_R_MASK); - } - } - - *pa = PD_ADDR(pd) + POT(va); - return SCPE_OK; -} - -/* - * Translate a virtual address into a physical address. - * - * If "fc" is false, this function will bypass: - * - * - Access flag checks - * - Cache insertion - * - Setting MMU fault registers - * - Modifying segment and page descriptor bits - */ - -t_stat mmu_decode_va(uint32 va, uint8 r_acc, t_bool fc, uint32 *pa) -{ - uint32 sd0, sd1, pd; - uint8 pd_acc; - t_stat sd_cached, pd_cached; - - if (!mmu_state.enabled) { - *pa = va; - return SCPE_OK; - } - - /* We must check both caches first to determine what kind of miss - processing to do. */ - - sd_cached = get_sdce(va, &sd0, &sd1); - pd_cached = get_pdce(va, &pd, &pd_acc); - - if (sd_cached == SCPE_OK && pd_cached != SCPE_OK) { - if (SD_PAGED(sd0) && mmu_get_pd(va, r_acc, fc, sd0, sd1, &pd, &pd_acc) != SCPE_OK) { - sim_debug(EXECUTE_MSG, &mmu_dev, - "Could not get PD (partial miss). r_acc=%d, fc=%d, va=%08x\n", - r_acc, fc, va); - return SCPE_NXM; - } - } else if (sd_cached != SCPE_OK && pd_cached == SCPE_OK) { - if (mmu_get_sd(va, r_acc, fc, &sd0, &sd1) != SCPE_OK) { - sim_debug(EXECUTE_MSG, &mmu_dev, - "Could not get SD (partial miss). r_acc=%d, fc=%d, va=%08x\n", - r_acc, fc, va); - return SCPE_NXM; - } - } else if (sd_cached != SCPE_OK && pd_cached != SCPE_OK) { - if (mmu_get_sd(va, r_acc, fc, &sd0, &sd1) != SCPE_OK) { - sim_debug(EXECUTE_MSG, &mmu_dev, - "Could not get SD (full miss). r_acc=%d, fc=%d, va=%08x\n", - r_acc, fc, va); - return SCPE_NXM; - } - - if (SD_PAGED(sd0) && mmu_get_pd(va, r_acc, fc, sd0, sd1, &pd, &pd_acc) != SCPE_OK) { - sim_debug(EXECUTE_MSG, &mmu_dev, - "Could not get PD (full miss). r_acc=%d, fc=%d, va=%08x\n", - r_acc, fc, va); - return SCPE_NXM; - } - } - - if (SD_PAGED(sd0)) { - if (fc && mmu_check_perm(pd_acc, r_acc) != SCPE_OK) { - sim_debug(EXECUTE_MSG, &mmu_dev, - "PAGED: NO ACCESS TO MEMORY AT %08x.\n" - "\t\tcpu_cm=%d r_acc=%x pd_acc=%02x\n" - "\t\tpd=%08x psw=%08x\n", - va, CPU_CM, r_acc, pd_acc, - pd, R[NUM_PSW]); - MMU_FAULT(MMU_F_ACC); - return SCPE_NXM; - } - if (PD_LAST(pd) && (PSL_C(va) | POT(va)) >= MAX_OFFSET(sd0)) { - sim_debug(EXECUTE_MSG, &mmu_dev, - "PAGED: Segment Offset Fault.\n"); - MMU_FAULT(MMU_F_SEG_OFFSET); - return SCPE_NXM; - } - return mmu_decode_paged(va, r_acc, fc, sd1, pd, pd_acc, pa); - } else { - if (fc && mmu_check_perm(SD_ACC(sd0), r_acc) != SCPE_OK) { - sim_debug(EXECUTE_MSG, &mmu_dev, - "CONTIGUOUS: NO ACCESS TO MEMORY AT %08x.\n" - "\t\tsd0=%08x sd0_addr=%08x\n" - "\t\tcpu_cm=%d acc_req=%x sd_acc=%02x\n", - va, sd0, SD_ADDR(va), - CPU_CM, r_acc, SD_ACC(sd0)); - MMU_FAULT(MMU_F_ACC); - return SCPE_NXM; - } - if (SOT(va) >= MAX_OFFSET(sd0)) { - sim_debug(EXECUTE_MSG, &mmu_dev, - "CONTIGUOUS: Segment Offset Fault. " - "sd0=%08x sd_addr=%08x SOT=%08x len=%08x va=%08x\n", - sd0, SD_ADDR(va), SOT(va), - MAX_OFFSET(sd0), va); - MMU_FAULT(MMU_F_SEG_OFFSET); - return SCPE_NXM; - } - return mmu_decode_contig(va, r_acc, sd0, sd1, fc, pa); - } -} - -uint32 mmu_xlate_addr(uint32 va, uint8 r_acc) -{ - uint32 pa; - t_stat succ; - - succ = mmu_decode_va(va, r_acc, TRUE, &pa); - - if (succ == SCPE_OK) { - mmu_state.var = va; - return pa; - } else { - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); - return 0; - } -} - -void mmu_enable() -{ - sim_debug(EXECUTE_MSG, &mmu_dev, - "Enabling MMU.\n"); - mmu_state.enabled = TRUE; -} - -void mmu_disable() -{ - sim_debug(EXECUTE_MSG, &mmu_dev, - "Disabling MMU.\n"); - mmu_state.enabled = FALSE; -} - -CONST char *mmu_description(DEVICE *dptr) -{ - return "WE32101"; -} +/* 3b2_rev2_mmu.c: WE32101 MMU + + Copyright (c) 2017-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + + +#include "3b2_cpu.h" +#include "3b2_mmu.h" +#include "3b2_sys.h" +#include "3b2_mem.h" + +UNIT mmu_unit = { UDATA(NULL, 0, 0) }; + +MMU_STATE mmu_state; + +REG mmu_reg[] = { + { HRDATAD (ENABLE, mmu_state.enabled, 1, "Enabled?") }, + { HRDATAD (CONFIG, mmu_state.conf, 32, "Configuration") }, + { HRDATAD (VAR, mmu_state.var, 32, "Virtual Address") }, + { HRDATAD (FCODE, mmu_state.fcode, 32, "Fault Code") }, + { HRDATAD (FADDR, mmu_state.faddr, 32, "Fault Address") }, + { BRDATA (SDCL, mmu_state.sdcl, 16, 32, MMU_SDCS) }, + { BRDATA (SDCR, mmu_state.sdch, 16, 32, MMU_SDCS) }, + { BRDATA (PDCLL, mmu_state.pdcll, 16, 32, MMU_PDCS) }, + { BRDATA (PDCLH, mmu_state.pdclh, 16, 32, MMU_PDCS) }, + { BRDATA (PDCRL, mmu_state.pdcrl, 16, 32, MMU_PDCS) }, + { BRDATA (PDCRH, mmu_state.pdcrh, 16, 32, MMU_PDCS) }, + { BRDATA (SRAMA, mmu_state.sra, 16, 32, MMU_SRS) }, + { BRDATA (SRAMB, mmu_state.srb, 16, 32, MMU_SRS) }, + { NULL } +}; + +DEVICE mmu_dev = { + "MMU", /* name */ + &mmu_unit, /* units */ + mmu_reg, /* registers */ + NULL, /* modifiers */ + 1, /* #units */ + 16, /* address radix */ + 8, /* address width */ + 4, /* address incr */ + 16, /* data radix */ + 32, /* data width */ + NULL, /* examine routine */ + NULL, /* deposit routine */ + &mmu_init, /* reset routine */ + NULL, /* boot routine */ + NULL, /* attach routine */ + NULL, /* detach routine */ + NULL, /* context */ + DEV_DEBUG, /* flags */ + 0, /* debug control flags */ + sys_deb_tab, /* debug flag names */ + NULL, /* memory size change */ + NULL, /* logical name */ + NULL, /* help routine */ + NULL, /* attach help routine */ + NULL, /* help context */ + &mmu_description /* device description */ +}; + +/* + * Find an SD in the cache. + */ +static SIM_INLINE t_stat get_sdce(uint32 va, uint32 *sd0, uint32 *sd1) +{ + uint32 tag, sdch, sdcl; + uint8 ci; + + ci = (SID(va) * NUM_SDCE) + SD_IDX(va); + tag = SD_TAG(va); + + sdch = mmu_state.sdch[ci]; + sdcl = mmu_state.sdcl[ci]; + + if ((sdch & SD_GOOD_MASK) && SDCE_TAG(sdcl) == tag) { + *sd0 = SDCE_TO_SD0(sdch, sdcl); + *sd1 = SDCE_TO_SD1(sdch); + return SCPE_OK; + } + + return SCPE_NXM; +} + +/* + * Find a PD in the cache. Sets both the PD and the cached access + * permissions. + */ +static SIM_INLINE t_stat get_pdce(uint32 va, uint32 *pd, uint8 *pd_acc) +{ + uint32 tag, pdcll, pdclh, pdcrl, pdcrh; + uint8 ci; + + ci = (SID(va) * NUM_PDCE) + PD_IDX(va); + tag = PD_TAG(va); + + /* Left side */ + pdcll = mmu_state.pdcll[ci]; + pdclh = mmu_state.pdclh[ci]; + + /* Right side */ + pdcrl = mmu_state.pdcrl[ci]; + pdcrh = mmu_state.pdcrh[ci]; + + /* Search L and R to find a good entry with a matching tag. */ + if ((pdclh & PD_GOOD_MASK) && PDCXL_TAG(pdcll) == tag) { + *pd = PDCXH_TO_PD(pdclh); + *pd_acc = PDCXL_TO_ACC(pdcll); + return SCPE_OK; + } else if ((pdcrh & PD_GOOD_MASK) && PDCXL_TAG(pdcrl) == tag) { + *pd = PDCXH_TO_PD(pdcrh); + *pd_acc = PDCXL_TO_ACC(pdcrl); + return SCPE_OK; + } + + return SCPE_NXM; +} + +static SIM_INLINE void put_sdce(uint32 va, uint32 sd0, uint32 sd1) +{ + uint8 ci; + + ci = (SID(va) * NUM_SDCE) + SD_IDX(va); + + mmu_state.sdcl[ci] = SD_TO_SDCL(va, sd0); + mmu_state.sdch[ci] = SD_TO_SDCH(sd0, sd1); +} + + +static SIM_INLINE void put_pdce(uint32 va, uint32 sd0, uint32 pd) +{ + uint8 ci; + + ci = (SID(va) * NUM_PDCE) + PD_IDX(va); + + /* Cache Replacement Algorithm + * (from the WE32101 MMU Information Manual) + * + * 1. If G==0 for the left-hand entry, the new PD is cached in the + * left-hand entry and the U bit (left-hand side) is cleared to + * 0. + * + * 2. If G==1 for the left-hand entry, and G==0 for the right-hand + * entry, the new PD is cached in the right-hand entry and the + * U bit (left-hand side) is set to 1. + * + * 3. If G==1 for both entries, the U bit in the left-hand entry + * is examined. If U==0, the new PD is cached in the right-hand + * entry of the PDC row and U is set to 1. If U==1, it is + * cached in the left-hand entry and U is cleared to 0. + */ + + if ((mmu_state.pdclh[ci] & PD_GOOD_MASK) == 0) { + /* Use the left entry */ + mmu_state.pdcll[ci] = SD_TO_PDCXL(va, sd0); + mmu_state.pdclh[ci] = PD_TO_PDCXH(pd, sd0); + mmu_state.pdclh[ci] &= ~PDCLH_USED_MASK; + } else if ((mmu_state.pdcrh[ci] & PD_GOOD_MASK) == 0) { + /* Use the right entry */ + mmu_state.pdcrl[ci] = SD_TO_PDCXL(va, sd0); + mmu_state.pdcrh[ci] = PD_TO_PDCXH(pd, sd0); + mmu_state.pdclh[ci] |= PDCLH_USED_MASK; + } else { + /* Pick the least-recently-replaced side */ + if (mmu_state.pdclh[ci] & PDCLH_USED_MASK) { + mmu_state.pdcll[ci] = SD_TO_PDCXL(va, sd0); + mmu_state.pdclh[ci] = PD_TO_PDCXH(pd, sd0); + mmu_state.pdclh[ci] &= ~PDCLH_USED_MASK; + } else { + mmu_state.pdcrl[ci] = SD_TO_PDCXL(va, sd0); + mmu_state.pdcrh[ci] = PD_TO_PDCXH(pd, sd0); + mmu_state.pdclh[ci] |= PDCLH_USED_MASK; + } + } +} + +static SIM_INLINE void flush_sdce(uint32 va) +{ + uint8 ci; + + ci = (SID(va) * NUM_SDCE) + SD_IDX(va); + + if (mmu_state.sdch[ci] & SD_GOOD_MASK) { + mmu_state.sdch[ci] &= ~SD_GOOD_MASK; + } +} + +static SIM_INLINE void flush_pdce(uint32 va) +{ + uint32 tag, pdcll, pdclh, pdcrl, pdcrh; + uint8 ci; + + ci = (SID(va) * NUM_PDCE) + PD_IDX(va); + tag = PD_TAG(va); + + /* Left side */ + pdcll = mmu_state.pdcll[ci]; + pdclh = mmu_state.pdclh[ci]; + /* Right side */ + pdcrl = mmu_state.pdcrl[ci]; + pdcrh = mmu_state.pdcrh[ci]; + + /* Search L and R to find a good entry with a matching tag. */ + if ((pdclh & PD_GOOD_MASK) && PDCXL_TAG(pdcll) == tag) { + mmu_state.pdclh[ci] &= ~PD_GOOD_MASK; + } else if ((pdcrh & PD_GOOD_MASK) && PDCXL_TAG(pdcrl) == tag) { + mmu_state.pdcrh[ci] &= ~PD_GOOD_MASK; + } +} + +static SIM_INLINE void flush_cache_sec(uint8 sec) +{ + int i; + + for (i = 0; i < NUM_SDCE; i++) { + mmu_state.sdch[(sec * NUM_SDCE) + i] &= ~SD_GOOD_MASK; + } + for (i = 0; i < NUM_PDCE; i++) { + mmu_state.pdclh[(sec * NUM_PDCE) + i] &= ~PD_GOOD_MASK; + mmu_state.pdcrh[(sec * NUM_PDCE) + i] &= ~PD_GOOD_MASK; + } +} + +static SIM_INLINE void flush_caches() +{ + uint8 i; + + for (i = 0; i < NUM_SEC; i++) { + flush_cache_sec(i); + } +} + +static SIM_INLINE t_stat mmu_check_perm(uint8 flags, uint8 r_acc) +{ + switch(MMU_PERM(flags)) { + case 0: /* No Access */ + return SCPE_NXM; + case 1: /* Exec Only */ + if (r_acc != ACC_IF && + r_acc != ACC_IFAD) { + return SCPE_NXM; + } + return SCPE_OK; + case 2: /* Read / Execute */ + if (r_acc != ACC_IF && + r_acc != ACC_IFAD && + r_acc != ACC_OF && + r_acc != ACC_AF && + r_acc != ACC_MT) { + return SCPE_NXM; + } + return SCPE_OK; + default: + return SCPE_OK; + } +} + +/* + * Update the M (modified) or R (referenced) bit the SD and cache + */ +static SIM_INLINE void mmu_update_sd(uint32 va, uint32 mask) +{ + uint32 sd0; + uint8 ci; + + ci = (SID(va) * NUM_SDCE) + SD_IDX(va); + + /* We go back to main memory to find the SD because the SD may + have been loaded from cache, which is lossy. */ + sd0 = pread_w(SD_ADDR(va), BUS_PER); + pwrite_w(SD_ADDR(va), sd0|mask, BUS_PER); + + /* There is no 'R' bit in the SD cache, only an 'M' bit. */ + if (mask == SD_M_MASK) { + mmu_state.sdch[ci] |= mask; + } +} + +/* + * Update the M (modified) or R (referenced) bit the PD and cache + */ +static SIM_INLINE void mmu_update_pd(uint32 va, uint32 pd_addr, uint32 mask) +{ + uint32 pd, tag, pdcll, pdclh, pdcrl, pdcrh; + uint8 ci; + + tag = PD_TAG(va); + ci = (SID(va) * NUM_PDCE) + PD_IDX(va); + + /* We go back to main memory to find the PD because the PD may + have been loaded from cache, which is lossy. */ + pd = pread_w(pd_addr, BUS_PER); + pwrite_w(pd_addr, pd|mask, BUS_PER); + + /* Update in the cache */ + + /* Left side */ + pdcll = mmu_state.pdcll[ci]; + pdclh = mmu_state.pdclh[ci]; + + /* Right side */ + pdcrl = mmu_state.pdcrl[ci]; + pdcrh = mmu_state.pdcrh[ci]; + + /* Search L and R to find a good entry with a matching tag, then + update the appropriate bit */ + if ((pdclh & PD_GOOD_MASK) && PDCXL_TAG(pdcll) == tag) { + mmu_state.pdclh[ci] |= mask; + } else if ((pdcrh & PD_GOOD_MASK) && PDCXL_TAG(pdcrl) == tag) { + mmu_state.pdcrh[ci] |= mask; + } +} + +t_stat mmu_init(DEVICE *dptr) +{ + flush_caches(); + return SCPE_OK; +} + +uint32 mmu_read(uint32 pa, size_t size) +{ + uint32 offset; + uint32 data = 0; + + offset = (pa >> 2) & 0x1f; + + switch ((pa >> 8) & 0xf) { + case MMU_SDCL: + data = mmu_state.sdcl[offset]; + sim_debug(READ_MSG, &mmu_dev, + "[pa=%08x] MMU_SDCL[%d] = %08x\n", + pa, offset, data); + break; + case MMU_SDCH: + data = mmu_state.sdch[offset]; + sim_debug(READ_MSG, &mmu_dev, + "MMU_SDCH[%d] = %08x\n", + offset, data); + break; + case MMU_PDCRL: + data = mmu_state.pdcrl[offset]; + sim_debug(READ_MSG, &mmu_dev, + "MMU_PDCRL[%d] = %08x\n", + offset, data); + break; + case MMU_PDCRH: + data = mmu_state.pdcrh[offset]; + sim_debug(READ_MSG, &mmu_dev, + "MMU_PDCRH[%d] = %08x\n", + offset, data); + break; + case MMU_PDCLL: + data = mmu_state.pdcll[offset]; + sim_debug(READ_MSG, &mmu_dev, + "MMU_PDCLL[%d] = %08x\n", + offset, data); + break; + case MMU_PDCLH: + data = mmu_state.pdclh[offset]; + sim_debug(READ_MSG, &mmu_dev, + "MMU_PDCLH[%d] = %08x\n", + offset, data); + break; + case MMU_SRAMA: + data = mmu_state.sra[offset]; + sim_debug(READ_MSG, &mmu_dev, + "MMU_SRAMA[%d] = %08x\n", + offset, data); + break; + case MMU_SRAMB: + data = mmu_state.srb[offset]; + sim_debug(READ_MSG, &mmu_dev, + "MMU_SRAMB[%d] = %08x\n", + offset, data); + break; + case MMU_FC: + data = mmu_state.fcode; + break; + case MMU_FA: + data = mmu_state.faddr; + break; + case MMU_CONF: + data = mmu_state.conf & 0x7; + sim_debug(READ_MSG, &mmu_dev, + "MMU_CONF = %08x\n", + data); + break; + case MMU_VAR: + data = mmu_state.var; + sim_debug(READ_MSG, &mmu_dev, + "MMU_VAR = %08x\n", + data); + break; + } + + return data; +} + +void mmu_write(uint32 pa, uint32 val, size_t size) +{ + uint32 offset; + + offset = (pa >> 2) & 0x1f; + + switch ((pa >> 8) & 0xf) { + case MMU_SDCL: + sim_debug(WRITE_MSG, &mmu_dev, + "MMU_SDCL[%d] = %08x\n", + offset, val); + mmu_state.sdcl[offset] = val; + break; + case MMU_SDCH: + sim_debug(WRITE_MSG, &mmu_dev, + "MMU_SDCH[%d] = %08x\n", + offset, val); + mmu_state.sdch[offset] = val; + break; + case MMU_PDCRL: + sim_debug(WRITE_MSG, &mmu_dev, + "MMU_PDCRL[%d] = %08x\n", + offset, val); + mmu_state.pdcrl[offset] = val; + break; + case MMU_PDCRH: + sim_debug(WRITE_MSG, &mmu_dev, + "MMU_PDCRH[%d] = %08x\n", + offset, val); + mmu_state.pdcrh[offset] = val; + break; + case MMU_PDCLL: + sim_debug(WRITE_MSG, &mmu_dev, + "MMU_PDCLL[%d] = %08x\n", + offset, val); + mmu_state.pdcll[offset] = val; + break; + case MMU_PDCLH: + sim_debug(WRITE_MSG, &mmu_dev, + "MMU_PDCLH[%d] = %08x\n", + offset, val); + mmu_state.pdclh[offset] = val; + break; + case MMU_SRAMA: + offset = offset & 3; + mmu_state.sra[offset] = val; + mmu_state.sec[offset].addr = val & 0xffffffe0; + /* We flush the entire section on writing SRAMA */ + sim_debug(WRITE_MSG, &mmu_dev, + "MMU_SRAMA[%d] = %08x\n", + offset, val); + flush_cache_sec((uint8) offset); + break; + case MMU_SRAMB: + offset = offset & 3; + mmu_state.srb[offset] = val; + mmu_state.sec[offset].len = (val >> 10) & 0x1fff; + /* We do not flush the cache on writing SRAMB */ + sim_debug(WRITE_MSG, &mmu_dev, + "MMU_SRAMB[%d] = %08x (len=%06x)\n", + offset, val, mmu_state.sec[offset].len); + break; + case MMU_FC: + mmu_state.fcode = val; + break; + case MMU_FA: + mmu_state.faddr = val; + break; + case MMU_CONF: + mmu_state.conf = val & 0x7; + break; + case MMU_VAR: + mmu_state.var = val; + flush_sdce(val); + flush_pdce(val); + break; + } +} + +/* Helper functions for MMU decode. */ + +/* + * Get the Segment Descriptor for a virtual address on a cache miss. + * + * Returns SCPE_OK on success, SCPE_NXM on failure. + * + * If SCPE_NXM is returned, a failure code and fault address will be + * set in the appropriate registers. + * + * As always, the flag 'fc' may be set to FALSE to avoid certain + * typses of fault checking. + * + */ +t_stat mmu_get_sd(uint32 va, uint8 r_acc, t_bool fc, + uint32 *sd0, uint32 *sd1) +{ + /* We immediately do some bounds checking (fc flag is not checked + * because this is a fatal error) */ + if (SSL(va) > SRAMB_LEN(va)) { + MMU_FAULT(MMU_F_SDTLEN); + sim_debug(EXECUTE_MSG, &mmu_dev, + "SDT Length Fault. sramb_len=%x ssl=%x va=%08x\n", + SRAMB_LEN(va), SSL(va), va); + return SCPE_NXM; + } + + /* sd0 contains the segment descriptor, sd1 contains a pointer to + the PDT or Segment */ + *sd0 = pread_w(SD_ADDR(va), BUS_PER); + *sd1 = pread_w(SD_ADDR(va) + 4, BUS_PER); + + if (!SD_VALID(*sd0)) { + sim_debug(EXECUTE_MSG, &mmu_dev, + "Invalid Segment Descriptor. va=%08x sd0=%08x\n", + va, *sd0); + MMU_FAULT(MMU_F_INV_SD); + return SCPE_NXM; + } + + /* TODO: Handle indirect lookups. */ + if (SD_INDIRECT(*sd0)) { + stop_reason = STOP_MMU; + return SCPE_NXM; + } + + /* If the segment descriptor isn't present, we need to + * fail out */ + if (!SD_PRESENT(*sd0)) { + if (SD_CONTIG(*sd0)) { + sim_debug(EXECUTE_MSG, &mmu_dev, + "Segment Not Present. va=%08x", + va); + MMU_FAULT(MMU_F_SEG_NOT_PRES); + return SCPE_NXM; + } else { + sim_debug(EXECUTE_MSG, &mmu_dev, + "PDT Not Present. va=%08x", + va); + MMU_FAULT(MMU_F_PDT_NOT_PRES); + return SCPE_NXM; + } + } + + if (SHOULD_CACHE_SD(*sd0)) { + put_sdce(va, *sd0, *sd1); + } + + return SCPE_OK; +} + +/* + * Load a page descriptor from memory + */ +t_stat mmu_get_pd(uint32 va, uint8 r_acc, t_bool fc, + uint32 sd0, uint32 sd1, + uint32 *pd, uint8 *pd_acc) +{ + uint32 pd_addr; + + /* Where do we find the page descriptor? */ + pd_addr = SD_SEG_ADDR(sd1) + (PSL(va) * 4); + + /* Bounds checking on length */ + if ((PSL(va) * 4) >= MAX_OFFSET(sd0)) { + sim_debug(EXECUTE_MSG, &mmu_dev, + "PDT Length Fault. " + "PDT Offset=%08x Max Offset=%08x va=%08x\n", + (PSL(va) * 4), + MAX_OFFSET(sd0), va); + MMU_FAULT(MMU_F_PDTLEN); + return SCPE_NXM; + } + + *pd = pread_w(pd_addr, BUS_PER); + + /* Copy the access flags from the SD */ + *pd_acc = SD_ACC(sd0); + + /* Cache it */ + if (SHOULD_CACHE_PD(*pd)) { + put_pdce(va, sd0, *pd); + } + + return SCPE_OK; +} + +/* + * Decode an address from a contiguous segment. + */ +t_stat mmu_decode_contig(uint32 va, uint8 r_acc, + uint32 sd0, uint32 sd1, + t_bool fc, uint32 *pa) +{ + if (fc) { + /* Update R and M bits if configured */ + if (SHOULD_UPDATE_SD_R_BIT(sd0)) { + sim_debug(EXECUTE_MSG, &mmu_dev, + "Updating R bit in SD\n"); + mmu_update_sd(va, SD_R_MASK); + } + + if (SHOULD_UPDATE_SD_M_BIT(sd0)) { + sim_debug(EXECUTE_MSG, &mmu_dev, + "Updating M bit in SD\n"); + mmu_update_sd(va, SD_M_MASK); + } + + /* Generate object trap if needed */ + if (SD_TRAP(sd0)) { + sim_debug(EXECUTE_MSG, &mmu_dev, + "Object Trap. va=%08x", + va); + MMU_FAULT(MMU_F_OTRAP); + return SCPE_NXM; + } + } + + *pa = SD_SEG_ADDR(sd1) + SOT(va); + return SCPE_OK; +} + +t_stat mmu_decode_paged(uint32 va, uint8 r_acc, t_bool fc, + uint32 sd1, uint32 pd, + uint8 pd_acc, uint32 *pa) +{ + /* If the PD is not marked present, fail */ + if (!PD_PRESENT(pd)) { + sim_debug(EXECUTE_MSG, &mmu_dev, + "Page Not Present. " + "pd=%08x r_acc=%x va=%08x\n", + pd, r_acc, va); + MMU_FAULT(MMU_F_PAGE_NOT_PRES); + return SCPE_NXM; + } + + if (fc) { + /* If this is a write or interlocked read access, and + the 'W' bit is set, trigger a write fault */ + if ((r_acc == ACC_W || r_acc == ACC_IR) && PD_WFAULT(pd)) { + sim_debug(EXECUTE_MSG, &mmu_dev, + "Page Write Fault. va=%08x\n", + va); + MMU_FAULT(MMU_F_PW); + return SCPE_NXM; + } + + /* If this is a write, modify the M bit */ + if (SHOULD_UPDATE_PD_M_BIT(pd)) { + sim_debug(EXECUTE_MSG, &mmu_dev, + "Updating M bit in PD\n"); + mmu_update_pd(va, PD_LOC(sd1, va), PD_M_MASK); + } + + /* Modify the R bit and write it back */ + if (SHOULD_UPDATE_PD_R_BIT(pd)) { + sim_debug(EXECUTE_MSG, &mmu_dev, + "Updating R bit in PD\n"); + mmu_update_pd(va, PD_LOC(sd1, va), PD_R_MASK); + } + } + + *pa = PD_ADDR(pd) + POT(va); + return SCPE_OK; +} + +/* + * Translate a virtual address into a physical address. + * + * If "fc" is false, this function will bypass: + * + * - Access flag checks + * - Cache insertion + * - Setting MMU fault registers + * - Modifying segment and page descriptor bits + */ + +t_stat mmu_decode_va(uint32 va, uint8 r_acc, t_bool fc, uint32 *pa) +{ + uint32 sd0, sd1, pd; + uint8 pd_acc; + t_stat sd_cached, pd_cached; + + if (!mmu_state.enabled) { + *pa = va; + return SCPE_OK; + } + + /* We must check both caches first to determine what kind of miss + processing to do. */ + + sd_cached = get_sdce(va, &sd0, &sd1); + pd_cached = get_pdce(va, &pd, &pd_acc); + + if (sd_cached == SCPE_OK && pd_cached != SCPE_OK) { + if (SD_PAGED(sd0) && mmu_get_pd(va, r_acc, fc, sd0, sd1, &pd, &pd_acc) != SCPE_OK) { + sim_debug(EXECUTE_MSG, &mmu_dev, + "Could not get PD (partial miss). r_acc=%d, fc=%d, va=%08x\n", + r_acc, fc, va); + return SCPE_NXM; + } + } else if (sd_cached != SCPE_OK && pd_cached == SCPE_OK) { + if (mmu_get_sd(va, r_acc, fc, &sd0, &sd1) != SCPE_OK) { + sim_debug(EXECUTE_MSG, &mmu_dev, + "Could not get SD (partial miss). r_acc=%d, fc=%d, va=%08x\n", + r_acc, fc, va); + return SCPE_NXM; + } + } else if (sd_cached != SCPE_OK && pd_cached != SCPE_OK) { + if (mmu_get_sd(va, r_acc, fc, &sd0, &sd1) != SCPE_OK) { + sim_debug(EXECUTE_MSG, &mmu_dev, + "Could not get SD (full miss). r_acc=%d, fc=%d, va=%08x\n", + r_acc, fc, va); + return SCPE_NXM; + } + + if (SD_PAGED(sd0) && mmu_get_pd(va, r_acc, fc, sd0, sd1, &pd, &pd_acc) != SCPE_OK) { + sim_debug(EXECUTE_MSG, &mmu_dev, + "Could not get PD (full miss). r_acc=%d, fc=%d, va=%08x\n", + r_acc, fc, va); + return SCPE_NXM; + } + } + + if (SD_PAGED(sd0)) { + if (fc && mmu_check_perm(pd_acc, r_acc) != SCPE_OK) { + sim_debug(EXECUTE_MSG, &mmu_dev, + "PAGED: NO ACCESS TO MEMORY AT %08x.\n" + "\t\tcpu_cm=%d r_acc=%x pd_acc=%02x\n" + "\t\tpd=%08x psw=%08x\n", + va, CPU_CM, r_acc, pd_acc, + pd, R[NUM_PSW]); + MMU_FAULT(MMU_F_ACC); + return SCPE_NXM; + } + if (PD_LAST(pd) && (PSL_C(va) | POT(va)) >= MAX_OFFSET(sd0)) { + sim_debug(EXECUTE_MSG, &mmu_dev, + "PAGED: Segment Offset Fault.\n"); + MMU_FAULT(MMU_F_SEG_OFFSET); + return SCPE_NXM; + } + return mmu_decode_paged(va, r_acc, fc, sd1, pd, pd_acc, pa); + } else { + if (fc && mmu_check_perm(SD_ACC(sd0), r_acc) != SCPE_OK) { + sim_debug(EXECUTE_MSG, &mmu_dev, + "CONTIGUOUS: NO ACCESS TO MEMORY AT %08x.\n" + "\t\tsd0=%08x sd0_addr=%08x\n" + "\t\tcpu_cm=%d acc_req=%x sd_acc=%02x\n", + va, sd0, SD_ADDR(va), + CPU_CM, r_acc, SD_ACC(sd0)); + MMU_FAULT(MMU_F_ACC); + return SCPE_NXM; + } + if (SOT(va) >= MAX_OFFSET(sd0)) { + sim_debug(EXECUTE_MSG, &mmu_dev, + "CONTIGUOUS: Segment Offset Fault. " + "sd0=%08x sd_addr=%08x SOT=%08x len=%08x va=%08x\n", + sd0, SD_ADDR(va), SOT(va), + MAX_OFFSET(sd0), va); + MMU_FAULT(MMU_F_SEG_OFFSET); + return SCPE_NXM; + } + return mmu_decode_contig(va, r_acc, sd0, sd1, fc, pa); + } +} + +uint32 mmu_xlate_addr(uint32 va, uint8 r_acc) +{ + uint32 pa; + t_stat succ; + + succ = mmu_decode_va(va, r_acc, TRUE, &pa); + + if (succ == SCPE_OK) { + mmu_state.var = va; + return pa; + } else { + cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); + return 0; + } +} + +void mmu_enable() +{ + sim_debug(EXECUTE_MSG, &mmu_dev, + "Enabling MMU.\n"); + mmu_state.enabled = TRUE; +} + +void mmu_disable() +{ + sim_debug(EXECUTE_MSG, &mmu_dev, + "Disabling MMU.\n"); + mmu_state.enabled = FALSE; +} + +CONST char *mmu_description(DEVICE *dptr) +{ + return "WE32101"; +} diff --git a/3B2/3b2_rev2_mmu.h b/3B2/3b2_rev2_mmu.h index af211f2f..30b1d116 100644 --- a/3B2/3b2_rev2_mmu.h +++ b/3B2/3b2_rev2_mmu.h @@ -1,358 +1,358 @@ -/* 3b2_rev2_mmu.c: WE32101 MMU - - Copyright (c) 2017-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#ifndef _3B2_REV2_MMU_H_ -#define _3B2_REV2_MMU_H_ - -#include "sim_defs.h" - -/************************************************************************ - * - * Vocabulary - * ---------- - * - * PD: Page Descriptor (in main memory) - * PDT: Page Descriptor Table (in main memory) - * POT: Page Offset. Bits 0-10 of a Paged virtual address. - * PSL: Page Select. Bits 11-16 of a Paged virtual address. - * SD: Segment Descriptor (in main memory) - * SDT: Segment Descriptor Table (in main memory) - * SID: Section ID. Bits 30-31 of all virtual addresses - * SOT: Segment Offset. Bits 0-16 of a Contiguous virtual address. - * SSL: Segment Select. Bits 17-29 of all virtual addresses. - * - * - * The WE32101 MMU divides the virtual address space into four - * Sections with 8K Segments per section. Virtual address bits 30 and - * 31 determine the section, bits 17-29 determine the Segment within - * the section. - * - * There are two kinds of address translation: Contiguous Translation - * and Paged Translation. Contiguous Translation just uses an offset - * (bits 0-16 of the virtual address) into each Segment to find an - * address, allowing for 128K bytes per Segment. Paged translation - * further break Segments down into 64 Pages of 2K each. - * - * Details about how to do translation are held in main memory in - * Segment Descriptors and Page Descriptors. These are located in - * Segment Descriptor Tables and Page Descriptor Tables set up by the - * computer before enabling the MMU. - * - * In addition to details in main memory, the MMU has a small cache - * of both Segment Descriptors and Page Descriptors. This is NOT just - * used for performance reasons! Various features of the cache, - * such as updating R and M bits in Segment and Page Descriptors, - * are used by various operating system features. - * - * - * Virtual Address Fields - * ---------------------- - * - * 31 30 29 17 16 0 - * +-----+-------------------+-----------------------------+ - * Contig: | SID | SSL | SOT | - * +-----+-------------------+-----------------------------+ - * - * 31 30 29 17 16 11 10 0 - * +-----+-------------------+---------+-------------------+ - * Paged: | SID | SSL | PSL | POT | - * +-----+-------------------+---------+-------------------+ - * - * - * Segment Descriptor Fields - * ------------------------- - * - * 31 24 23 10 9 8 7 6 5 4 3 2 1 0 - * +-------+---------+-----+---+---+---+---+---+---+---+---+ - * sd0: | Acc | Max Off | Res | I | V | R | T | $ | C | M | P | - * +-------+---------+-----+---+---+---+---+---+---+---+---+ - * - * +-----------------------------------------------+-------+ - * sd1: | Address (high-order 27 or 29 bits) | Soft | - * +-----------------------------------------------+-------+ - * - * - * Segment Descriptor Cache Entry - * ------------------------------ - * - * 31 24 23 10 9 0 - * +-------+-------------------------+---------------------+ - * Low: | Acc | Max Off | Tag | - * +-------+-------------------------+---------------------+ - * - * 31 5 4 3 2 1 0 - * +-----------------------------------+---+---+---+---+---+ - * High: | Address | T | $ | C | M | G | - * +-----------------------------------+---+---+---+---+---+ - * - * - * Page Descriptor Fields - * ---------------------- - * - * 31 11 10 8 7 6 5 4 3 2 1 0 - * +----------------+------+-----+---+---+-----+---+---+---+ - * | Page Address | Soft | Res | R | W | Res | L | M | P | - * +----------------+------+-----+---+---+-----+---+---+---+ - * - * - * Page Descriptor Cache Entry - * --------------------------- - * - * 31 24 23 16 15 0 - * +-----+------------------+------------------------------+ - * Low: | Acc | Res | Tag | - * +-----+------------------+------------------------------+ - * - * 31 11 10 7 6 5 4 3 2 1 0 - * +---------------------+-----+---+---+---+---+---+---+---+ - * High: | Address | Res | U | R | W | $ | L | M | G | - * +---------------------+-----+---+---+---+---+---+---+---+ - * - * "U" is only set in the left cache entry, and indicates - * which slot (left or right) was most recently updated. - * - ***********************************************************************/ - -#define MMUBASE 0x40000 -#define MMUSIZE 0x1000 - -#define MMU_SRS 0x04 /* Section RAM array size (words) */ -#define MMU_SDCS 0x20 /* Segment Descriptor Cache H/L array size - (words) */ -#define MMU_PDCS 0x20 /* Page Descriptor Cache H/L array size - (words) */ - -/* Register address offsets */ -#define MMU_SDCL 0 -#define MMU_SDCH 1 -#define MMU_PDCRL 2 -#define MMU_PDCRH 3 -#define MMU_PDCLL 4 -#define MMU_PDCLH 5 -#define MMU_SRAMA 6 -#define MMU_SRAMB 7 -#define MMU_FC 8 -#define MMU_FA 9 -#define MMU_CONF 10 -#define MMU_VAR 11 - -#define MMU_CONF_M (mmu_state.conf & 0x1) -#define MMU_CONF_R (mmu_state.conf & 0x2) - -/* Caching */ -#define NUM_SEC 4u /* Number of memory sections */ -#define NUM_SDCE 8 /* SD cache entries per section */ -#define NUM_PDCE 8 /* PD cache entries per section per side (l/r) */ -#define SET_SIZE 2 /* PDs are held in a 2-way associative set */ - -/* Cache Tag for SDs */ -#define SD_TAG(vaddr) ((vaddr >> 20) & 0x3ff) - -/* Cache Tag for PDs */ -#define PD_TAG(vaddr) (((vaddr >> 13) & 0xf) | ((vaddr >> 14) & 0xfff0)) - -/* Index of entry in the SD cache */ -#define SD_IDX(vaddr) ((vaddr >> 17) & 7) - -/* Index of entry in the PD cache */ -#define PD_IDX(vaddr) (((vaddr >> 11) & 3) | ((vaddr >> 15) & 4)) - -/* Shift and mask the flag bits for the current CPU mode */ -#define MMU_PERM(f) ((f >> ((3 - CPU_CM) * 2)) & 3) - -/* Codes set in the MMU Fault register */ -#define MMU_F_SDTLEN 0x03 -#define MMU_F_PW 0x04 -#define MMU_F_PDTLEN 0x05 -#define MMU_F_INV_SD 0x06 -#define MMU_F_SEG_NOT_PRES 0x07 -#define MMU_F_OTRAP 0x08 -#define MMU_F_PDT_NOT_PRES 0x09 -#define MMU_F_PAGE_NOT_PRES 0x0a -#define MMU_F_ACC 0x0d -#define MMU_F_SEG_OFFSET 0x0e - -/* Access Request types */ -#define ACC_MT 0 /* Move Translated */ -#define ACC_SPW 1 /* Support processor write */ -#define ACC_SPF 3 /* Support processor fetch */ -#define ACC_IR 7 /* Interlocked read */ -#define ACC_AF 8 /* Address fetch */ -#define ACC_OF 9 /* Operand fetch */ -#define ACC_W 10 /* Write */ -#define ACC_IFAD 12 /* Instruction fetch after discontinuity */ -#define ACC_IF 13 /* Instruction fetch */ - -/* Pluck out Virtual Address fields */ -#define SID(va) (((va) >> 30) & 3) -#define SSL(va) (((va) >> 17) & 0x1fff) -#define SOT(va) (va & 0x1ffff) -#define PSL(va) (((va) >> 11) & 0x3f) -#define PSL_C(va) ((va) & 0x1f800) -#define POT(va) (va & 0x7ff) - -/* Get the maximum length of an SSL from SRAMB */ -#define SRAMB_LEN(va) (mmu_state.sec[SID(va)].len + 1) - -/* Pluck out Segment Descriptor fields */ -#define SD_PRESENT(sd0) ((sd0) & 1) -#define SD_MODIFIED(sd0) (((sd0) >> 1) & 1) -#define SD_CONTIG(sd0) (((sd0) >> 2) & 1) -#define SD_PAGED(sd0) ((((sd0) >> 2) & 1) == 0) -#define SD_CACHE(sd0) (((sd0) >> 3) & 1) -#define SD_TRAP(sd0) (((sd0) >> 4) & 1) -#define SD_REF(sd0) (((sd0) >> 5) & 1) -#define SD_VALID(sd0) (((sd0) >> 6) & 1) -#define SD_INDIRECT(sd0) (((sd0) >> 7) & 1) -#define SD_SEG_ADDR(sd1) ((sd1) & 0xffffffe0) -#define SD_MAX_OFF(sd0) (((sd0) >> 10) & 0x3fff) -#define SD_ACC(sd0) (((sd0) >> 24) & 0xff) -#define SD_R_MASK 0x20 -#define SD_M_MASK 0x2 -#define SD_GOOD_MASK 0x1u -#define SDCE_TAG(sdcl) ((sdcl) & 0x3ff) - -#define SD_ADDR(va) (mmu_state.sec[SID(va)].addr + (SSL(va) * 8)) - - -/* Convert from sd to sd cache entry */ -#define SD_TO_SDCL(va,sd0) ((sd0 & 0xfffffc00)|SD_TAG(va)) -#define SD_TO_SDCH(sd0,sd1) (SD_SEG_ADDR(sd1)|(sd0 & 0x1e)|1) - -/* Note that this is a lossy transform. We will lose the state of the - I and R flags, as well as the software flags. We don't need them. - The V and P flags can be inferred as set. */ - -#define SDCE_TO_SD0(sdch,sdcl) ((sdcl & 0xfffffc00)|0x40|(sdch & 0x1e)|1) -#define SDCE_TO_SD1(sdch) (sdch & 0xffffffe0) - -/* Maximum size (in bytes) of a segment */ -#define MAX_OFFSET(sd0) ((SD_MAX_OFF(sd0) + 1) * 8) - -#define PD_PRESENT(pd) (pd & 1) -#define PD_MODIFIED(pd) ((pd >> 1) & 1) -#define PD_LAST(pd) ((pd >> 2) & 1) -#define PD_WFAULT(pd) ((pd >> 4) & 1) -#define PD_REF(pd) ((pd >> 5) & 1) -#define PD_ADDR(pd) (pd & 0xfffff800) /* Address portion of PD */ -#define PD_R_MASK 0x20 -#define PD_M_MASK 0x2 -#define PD_GOOD_MASK 0x1u -#define PDCLH_USED_MASK 0x40u -#define PDCXL_TAG(pdcxl) (pdcxl & 0xffff) - -#define PD_LOC(sd1,va) SD_SEG_ADDR(sd1) + (PSL(va) * 4) - -/* Page Descriptor Cache Entry - * - */ - -/* Convert from pd to pd cache entry. Alwasy sets "Good" bit. */ -#define SD_TO_PDCXL(va,sd0) ((sd0 & 0xff000000)|PD_TAG(va)) -#define PD_TO_PDCXH(pd,sd0) ((pd & 0xfffff836)|(sd0 & 0x8)|1) - -/* Always set 'present' to true on conversion */ -#define PDCXH_TO_PD(pdch) ((pdch & 0xfffff836)|1) -#define PDCXL_TO_ACC(pdcl) (((pdcl & 0xff000000) >> 24) & 0xff) - -/* Fault codes */ -#define MMU_FAULT(f) { \ - if (fc) { \ - mmu_state.fcode = ((((uint32)r_acc)<<7)| \ - (((uint32)(CPU_CM))<<5)|f); \ - mmu_state.faddr = va; \ - } \ - } - -typedef struct _mmu_sec { - uint32 addr; - uint32 len; -} mmu_sec; - -typedef struct _mmu_state { - t_bool enabled; /* Global enabled/disabled flag */ - - uint32 sdcl[MMU_SDCS]; /* SDC low bits (0-31) */ - uint32 sdch[MMU_SDCS]; /* SDC high bits (32-63) */ - - uint32 pdcll[MMU_PDCS]; /* PDC low bits (left) (0-31) */ - uint32 pdclh[MMU_PDCS]; /* PDC high bits (left) (32-63) */ - - uint32 pdcrl[MMU_PDCS]; /* PDC low bits (right) (0-31) */ - uint32 pdcrh[MMU_PDCS]; /* PDC high bits (right) (32-63) */ - - uint32 sra[MMU_SRS]; /* Section RAM A */ - uint32 srb[MMU_SRS]; /* Section RAM B */ - - mmu_sec sec[MMU_SRS]; /* Section descriptors decoded from - Section RAM A and B */ - - uint32 fcode; /* Fault Code Register */ - uint32 faddr; /* Fault Address Register */ - uint32 conf; /* Configuration Register */ - uint32 var; /* Virtual Address Register */ - -} MMU_STATE; - -t_stat mmu_init(DEVICE *dptr); -uint32 mmu_read(uint32 pa, size_t size); -void mmu_write(uint32 pa, uint32 val, size_t size); -CONST char *mmu_description(DEVICE *dptr); - -/* Virtual memory translation */ -uint32 mmu_xlate_addr(uint32 va, uint8 r_acc); -t_stat mmu_decode_vaddr(uint32 vaddr, uint8 r_acc, - t_bool fc, uint32 *pa); - -#define SHOULD_CACHE_PD(pd) \ - (fc && PD_PRESENT(pd)) - -#define SHOULD_CACHE_SD(sd) \ - (fc && SD_VALID(sd) && SD_PRESENT(sd)) - -#define SHOULD_UPDATE_SD_R_BIT(sd) \ - (MMU_CONF_R && !((sd) & SD_R_MASK)) - -#define SHOULD_UPDATE_SD_M_BIT(sd) \ - (MMU_CONF_M && r_acc == ACC_W && !((sd) & SD_M_MASK)) - -#define SHOULD_UPDATE_PD_R_BIT(pd) \ - (!((pd) & PD_R_MASK)) - -#define SHOULD_UPDATE_PD_M_BIT(pd) \ - (r_acc == ACC_W && !((pd) & PD_M_MASK)) - -t_stat mmu_decode_va(uint32 va, uint8 r_acc, t_bool fc, uint32 *pa); -void mmu_enable(); -void mmu_disable(); - -extern MMU_STATE mmu_state; - -#endif /* _3B2_REV2_MMU_H_ */ +/* 3b2_rev2_mmu.c: WE32101 MMU + + Copyright (c) 2017-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#ifndef _3B2_REV2_MMU_H_ +#define _3B2_REV2_MMU_H_ + +#include "sim_defs.h" + +/************************************************************************ + * + * Vocabulary + * ---------- + * + * PD: Page Descriptor (in main memory) + * PDT: Page Descriptor Table (in main memory) + * POT: Page Offset. Bits 0-10 of a Paged virtual address. + * PSL: Page Select. Bits 11-16 of a Paged virtual address. + * SD: Segment Descriptor (in main memory) + * SDT: Segment Descriptor Table (in main memory) + * SID: Section ID. Bits 30-31 of all virtual addresses + * SOT: Segment Offset. Bits 0-16 of a Contiguous virtual address. + * SSL: Segment Select. Bits 17-29 of all virtual addresses. + * + * + * The WE32101 MMU divides the virtual address space into four + * Sections with 8K Segments per section. Virtual address bits 30 and + * 31 determine the section, bits 17-29 determine the Segment within + * the section. + * + * There are two kinds of address translation: Contiguous Translation + * and Paged Translation. Contiguous Translation just uses an offset + * (bits 0-16 of the virtual address) into each Segment to find an + * address, allowing for 128K bytes per Segment. Paged translation + * further break Segments down into 64 Pages of 2K each. + * + * Details about how to do translation are held in main memory in + * Segment Descriptors and Page Descriptors. These are located in + * Segment Descriptor Tables and Page Descriptor Tables set up by the + * computer before enabling the MMU. + * + * In addition to details in main memory, the MMU has a small cache + * of both Segment Descriptors and Page Descriptors. This is NOT just + * used for performance reasons! Various features of the cache, + * such as updating R and M bits in Segment and Page Descriptors, + * are used by various operating system features. + * + * + * Virtual Address Fields + * ---------------------- + * + * 31 30 29 17 16 0 + * +-----+-------------------+-----------------------------+ + * Contig: | SID | SSL | SOT | + * +-----+-------------------+-----------------------------+ + * + * 31 30 29 17 16 11 10 0 + * +-----+-------------------+---------+-------------------+ + * Paged: | SID | SSL | PSL | POT | + * +-----+-------------------+---------+-------------------+ + * + * + * Segment Descriptor Fields + * ------------------------- + * + * 31 24 23 10 9 8 7 6 5 4 3 2 1 0 + * +-------+---------+-----+---+---+---+---+---+---+---+---+ + * sd0: | Acc | Max Off | Res | I | V | R | T | $ | C | M | P | + * +-------+---------+-----+---+---+---+---+---+---+---+---+ + * + * +-----------------------------------------------+-------+ + * sd1: | Address (high-order 27 or 29 bits) | Soft | + * +-----------------------------------------------+-------+ + * + * + * Segment Descriptor Cache Entry + * ------------------------------ + * + * 31 24 23 10 9 0 + * +-------+-------------------------+---------------------+ + * Low: | Acc | Max Off | Tag | + * +-------+-------------------------+---------------------+ + * + * 31 5 4 3 2 1 0 + * +-----------------------------------+---+---+---+---+---+ + * High: | Address | T | $ | C | M | G | + * +-----------------------------------+---+---+---+---+---+ + * + * + * Page Descriptor Fields + * ---------------------- + * + * 31 11 10 8 7 6 5 4 3 2 1 0 + * +----------------+------+-----+---+---+-----+---+---+---+ + * | Page Address | Soft | Res | R | W | Res | L | M | P | + * +----------------+------+-----+---+---+-----+---+---+---+ + * + * + * Page Descriptor Cache Entry + * --------------------------- + * + * 31 24 23 16 15 0 + * +-----+------------------+------------------------------+ + * Low: | Acc | Res | Tag | + * +-----+------------------+------------------------------+ + * + * 31 11 10 7 6 5 4 3 2 1 0 + * +---------------------+-----+---+---+---+---+---+---+---+ + * High: | Address | Res | U | R | W | $ | L | M | G | + * +---------------------+-----+---+---+---+---+---+---+---+ + * + * "U" is only set in the left cache entry, and indicates + * which slot (left or right) was most recently updated. + * + ***********************************************************************/ + +#define MMUBASE 0x40000 +#define MMUSIZE 0x1000 + +#define MMU_SRS 0x04 /* Section RAM array size (words) */ +#define MMU_SDCS 0x20 /* Segment Descriptor Cache H/L array size + (words) */ +#define MMU_PDCS 0x20 /* Page Descriptor Cache H/L array size + (words) */ + +/* Register address offsets */ +#define MMU_SDCL 0 +#define MMU_SDCH 1 +#define MMU_PDCRL 2 +#define MMU_PDCRH 3 +#define MMU_PDCLL 4 +#define MMU_PDCLH 5 +#define MMU_SRAMA 6 +#define MMU_SRAMB 7 +#define MMU_FC 8 +#define MMU_FA 9 +#define MMU_CONF 10 +#define MMU_VAR 11 + +#define MMU_CONF_M (mmu_state.conf & 0x1) +#define MMU_CONF_R (mmu_state.conf & 0x2) + +/* Caching */ +#define NUM_SEC 4u /* Number of memory sections */ +#define NUM_SDCE 8 /* SD cache entries per section */ +#define NUM_PDCE 8 /* PD cache entries per section per side (l/r) */ +#define SET_SIZE 2 /* PDs are held in a 2-way associative set */ + +/* Cache Tag for SDs */ +#define SD_TAG(vaddr) ((vaddr >> 20) & 0x3ff) + +/* Cache Tag for PDs */ +#define PD_TAG(vaddr) (((vaddr >> 13) & 0xf) | ((vaddr >> 14) & 0xfff0)) + +/* Index of entry in the SD cache */ +#define SD_IDX(vaddr) ((vaddr >> 17) & 7) + +/* Index of entry in the PD cache */ +#define PD_IDX(vaddr) (((vaddr >> 11) & 3) | ((vaddr >> 15) & 4)) + +/* Shift and mask the flag bits for the current CPU mode */ +#define MMU_PERM(f) ((f >> ((3 - CPU_CM) * 2)) & 3) + +/* Codes set in the MMU Fault register */ +#define MMU_F_SDTLEN 0x03 +#define MMU_F_PW 0x04 +#define MMU_F_PDTLEN 0x05 +#define MMU_F_INV_SD 0x06 +#define MMU_F_SEG_NOT_PRES 0x07 +#define MMU_F_OTRAP 0x08 +#define MMU_F_PDT_NOT_PRES 0x09 +#define MMU_F_PAGE_NOT_PRES 0x0a +#define MMU_F_ACC 0x0d +#define MMU_F_SEG_OFFSET 0x0e + +/* Access Request types */ +#define ACC_MT 0 /* Move Translated */ +#define ACC_SPW 1 /* Support processor write */ +#define ACC_SPF 3 /* Support processor fetch */ +#define ACC_IR 7 /* Interlocked read */ +#define ACC_AF 8 /* Address fetch */ +#define ACC_OF 9 /* Operand fetch */ +#define ACC_W 10 /* Write */ +#define ACC_IFAD 12 /* Instruction fetch after discontinuity */ +#define ACC_IF 13 /* Instruction fetch */ + +/* Pluck out Virtual Address fields */ +#define SID(va) (((va) >> 30) & 3) +#define SSL(va) (((va) >> 17) & 0x1fff) +#define SOT(va) (va & 0x1ffff) +#define PSL(va) (((va) >> 11) & 0x3f) +#define PSL_C(va) ((va) & 0x1f800) +#define POT(va) (va & 0x7ff) + +/* Get the maximum length of an SSL from SRAMB */ +#define SRAMB_LEN(va) (mmu_state.sec[SID(va)].len + 1) + +/* Pluck out Segment Descriptor fields */ +#define SD_PRESENT(sd0) ((sd0) & 1) +#define SD_MODIFIED(sd0) (((sd0) >> 1) & 1) +#define SD_CONTIG(sd0) (((sd0) >> 2) & 1) +#define SD_PAGED(sd0) ((((sd0) >> 2) & 1) == 0) +#define SD_CACHE(sd0) (((sd0) >> 3) & 1) +#define SD_TRAP(sd0) (((sd0) >> 4) & 1) +#define SD_REF(sd0) (((sd0) >> 5) & 1) +#define SD_VALID(sd0) (((sd0) >> 6) & 1) +#define SD_INDIRECT(sd0) (((sd0) >> 7) & 1) +#define SD_SEG_ADDR(sd1) ((sd1) & 0xffffffe0) +#define SD_MAX_OFF(sd0) (((sd0) >> 10) & 0x3fff) +#define SD_ACC(sd0) (((sd0) >> 24) & 0xff) +#define SD_R_MASK 0x20 +#define SD_M_MASK 0x2 +#define SD_GOOD_MASK 0x1u +#define SDCE_TAG(sdcl) ((sdcl) & 0x3ff) + +#define SD_ADDR(va) (mmu_state.sec[SID(va)].addr + (SSL(va) * 8)) + + +/* Convert from sd to sd cache entry */ +#define SD_TO_SDCL(va,sd0) ((sd0 & 0xfffffc00)|SD_TAG(va)) +#define SD_TO_SDCH(sd0,sd1) (SD_SEG_ADDR(sd1)|(sd0 & 0x1e)|1) + +/* Note that this is a lossy transform. We will lose the state of the + I and R flags, as well as the software flags. We don't need them. + The V and P flags can be inferred as set. */ + +#define SDCE_TO_SD0(sdch,sdcl) ((sdcl & 0xfffffc00)|0x40|(sdch & 0x1e)|1) +#define SDCE_TO_SD1(sdch) (sdch & 0xffffffe0) + +/* Maximum size (in bytes) of a segment */ +#define MAX_OFFSET(sd0) ((SD_MAX_OFF(sd0) + 1) * 8) + +#define PD_PRESENT(pd) (pd & 1) +#define PD_MODIFIED(pd) ((pd >> 1) & 1) +#define PD_LAST(pd) ((pd >> 2) & 1) +#define PD_WFAULT(pd) ((pd >> 4) & 1) +#define PD_REF(pd) ((pd >> 5) & 1) +#define PD_ADDR(pd) (pd & 0xfffff800) /* Address portion of PD */ +#define PD_R_MASK 0x20 +#define PD_M_MASK 0x2 +#define PD_GOOD_MASK 0x1u +#define PDCLH_USED_MASK 0x40u +#define PDCXL_TAG(pdcxl) (pdcxl & 0xffff) + +#define PD_LOC(sd1,va) SD_SEG_ADDR(sd1) + (PSL(va) * 4) + +/* Page Descriptor Cache Entry + * + */ + +/* Convert from pd to pd cache entry. Alwasy sets "Good" bit. */ +#define SD_TO_PDCXL(va,sd0) ((sd0 & 0xff000000)|PD_TAG(va)) +#define PD_TO_PDCXH(pd,sd0) ((pd & 0xfffff836)|(sd0 & 0x8)|1) + +/* Always set 'present' to true on conversion */ +#define PDCXH_TO_PD(pdch) ((pdch & 0xfffff836)|1) +#define PDCXL_TO_ACC(pdcl) (((pdcl & 0xff000000) >> 24) & 0xff) + +/* Fault codes */ +#define MMU_FAULT(f) { \ + if (fc) { \ + mmu_state.fcode = ((((uint32)r_acc)<<7)| \ + (((uint32)(CPU_CM))<<5)|f); \ + mmu_state.faddr = va; \ + } \ + } + +typedef struct _mmu_sec { + uint32 addr; + uint32 len; +} mmu_sec; + +typedef struct _mmu_state { + t_bool enabled; /* Global enabled/disabled flag */ + + uint32 sdcl[MMU_SDCS]; /* SDC low bits (0-31) */ + uint32 sdch[MMU_SDCS]; /* SDC high bits (32-63) */ + + uint32 pdcll[MMU_PDCS]; /* PDC low bits (left) (0-31) */ + uint32 pdclh[MMU_PDCS]; /* PDC high bits (left) (32-63) */ + + uint32 pdcrl[MMU_PDCS]; /* PDC low bits (right) (0-31) */ + uint32 pdcrh[MMU_PDCS]; /* PDC high bits (right) (32-63) */ + + uint32 sra[MMU_SRS]; /* Section RAM A */ + uint32 srb[MMU_SRS]; /* Section RAM B */ + + mmu_sec sec[MMU_SRS]; /* Section descriptors decoded from + Section RAM A and B */ + + uint32 fcode; /* Fault Code Register */ + uint32 faddr; /* Fault Address Register */ + uint32 conf; /* Configuration Register */ + uint32 var; /* Virtual Address Register */ + +} MMU_STATE; + +t_stat mmu_init(DEVICE *dptr); +uint32 mmu_read(uint32 pa, size_t size); +void mmu_write(uint32 pa, uint32 val, size_t size); +CONST char *mmu_description(DEVICE *dptr); + +/* Virtual memory translation */ +uint32 mmu_xlate_addr(uint32 va, uint8 r_acc); +t_stat mmu_decode_vaddr(uint32 vaddr, uint8 r_acc, + t_bool fc, uint32 *pa); + +#define SHOULD_CACHE_PD(pd) \ + (fc && PD_PRESENT(pd)) + +#define SHOULD_CACHE_SD(sd) \ + (fc && SD_VALID(sd) && SD_PRESENT(sd)) + +#define SHOULD_UPDATE_SD_R_BIT(sd) \ + (MMU_CONF_R && !((sd) & SD_R_MASK)) + +#define SHOULD_UPDATE_SD_M_BIT(sd) \ + (MMU_CONF_M && r_acc == ACC_W && !((sd) & SD_M_MASK)) + +#define SHOULD_UPDATE_PD_R_BIT(pd) \ + (!((pd) & PD_R_MASK)) + +#define SHOULD_UPDATE_PD_M_BIT(pd) \ + (r_acc == ACC_W && !((pd) & PD_M_MASK)) + +t_stat mmu_decode_va(uint32 va, uint8 r_acc, t_bool fc, uint32 *pa); +void mmu_enable(); +void mmu_disable(); + +extern MMU_STATE mmu_state; + +#endif /* _3B2_REV2_MMU_H_ */ diff --git a/3B2/3b2_rev2_sys.c b/3B2/3b2_rev2_sys.c index 5a5d7428..c9e84b4e 100644 --- a/3B2/3b2_rev2_sys.c +++ b/3B2/3b2_rev2_sys.c @@ -1,82 +1,82 @@ -/* 3b2_rev2_sys.c: Version 2 (3B2/400) System Definition - - Copyright (c) 2017-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#include "3b2_defs.h" - -#include "3b2_cpu.h" -#include "3b2_csr.h" -#include "3b2_ctc.h" -#include "3b2_id.h" -#include "3b2_if.h" -#include "3b2_iu.h" -#include "3b2_ni.h" -#include "3b2_ports.h" -#include "3b2_mau.h" -#include "3b2_stddev.h" -#include "3b2_timer.h" - -char sim_name[] = "AT&T 3B2/400"; - -DEVICE *sim_devices[] = { - &cpu_dev, - &mmu_dev, - &mau_dev, - &timer_dev, - &tod_dev, - &nvram_dev, - &csr_dev, - &tti_dev, - &tto_dev, - &contty_dev, - &iu_timer_dev, - &dmac_dev, - &if_dev, - &id_dev, - &ports_dev, - &ctc_dev, - &ni_dev, - NULL -}; - -void full_reset() -{ - cpu_reset(&cpu_dev); - mau_reset(&mau_dev); - tti_reset(&tti_dev); - contty_reset(&contty_dev); - iu_timer_reset(&iu_timer_dev); - timer_reset(&timer_dev); - if_reset(&if_dev); - id_reset(&id_dev); - csr_reset(&csr_dev); - ports_reset(&ports_dev); - ctc_reset(&ctc_dev); - ni_reset(&ni_dev); -} +/* 3b2_rev2_sys.c: Version 2 (3B2/400) System Definition + + Copyright (c) 2017-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#include "3b2_defs.h" + +#include "3b2_cpu.h" +#include "3b2_csr.h" +#include "3b2_ctc.h" +#include "3b2_id.h" +#include "3b2_if.h" +#include "3b2_iu.h" +#include "3b2_ni.h" +#include "3b2_ports.h" +#include "3b2_mau.h" +#include "3b2_stddev.h" +#include "3b2_timer.h" + +char sim_name[] = "AT&T 3B2/400"; + +DEVICE *sim_devices[] = { + &cpu_dev, + &mmu_dev, + &mau_dev, + &timer_dev, + &tod_dev, + &nvram_dev, + &csr_dev, + &tti_dev, + &tto_dev, + &contty_dev, + &iu_timer_dev, + &dmac_dev, + &if_dev, + &id_dev, + &ports_dev, + &ctc_dev, + &ni_dev, + NULL +}; + +void full_reset() +{ + cpu_reset(&cpu_dev); + mau_reset(&mau_dev); + tti_reset(&tti_dev); + contty_reset(&contty_dev); + iu_timer_reset(&iu_timer_dev); + timer_reset(&timer_dev); + if_reset(&if_dev); + id_reset(&id_dev); + csr_reset(&csr_dev); + ports_reset(&ports_dev); + ctc_reset(&ctc_dev); + ni_reset(&ni_dev); +} diff --git a/3B2/3b2_rev3_csr.c b/3B2/3b2_rev3_csr.c index 06403b47..148e23f7 100644 --- a/3B2/3b2_rev3_csr.c +++ b/3B2/3b2_rev3_csr.c @@ -1,293 +1,293 @@ -/* 3b2_rev3_csr.c: CM518B System Board Control, Status & Error Register - - Copyright (c) 2020-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#include "3b2_cpu.h" -#include "3b2_csr.h" -#include "3b2_if.h" -#include "3b2_timer.h" -#include "3b2_sys.h" - -CSR_DATA csr_data; - -BITFIELD csr_bits[] = { - BIT(UTIM), - BIT(PWDN), - BIT(OI15), - BIT(IUINT), - BIT(IUDMA), - BIT(PIR9), - BIT(PIR8), - BIT(IUTIM), - - BIT(ISTY), - BIT(IUBUS), - BIT(IFLT), - BIT(ISBER), - BIT(IBUS), - BIT(IBUB), - BIT(FECC), - BIT(THERM), - - BIT(FLED), - BIT(PSPWR), - BIT(FLSPD), - BIT(FLSD1), - BIT(FLMOT), - BIT(FLDEN), - BIT(FLSZ), - BIT(SBER), - - BIT(MBER), - BIT(UBFL), - BIT(TIMO), - BIT(FLTFR), - BIT(DALGN), - BIT(STTIM), - BIT(ABRT), - BIT(RSTR), - - ENDBITS -}; - -UNIT csr_unit = { - UDATA(NULL, UNIT_FIX, CSRSIZE) -}; - -REG csr_reg[] = { - { HRDATADF(DATA, csr_data, 32, "CSR Data", csr_bits) }, - { NULL } -}; - -DEVICE csr_dev = { - "CSR", &csr_unit, csr_reg, NULL, - 1, 16, 8, 4, 16, 32, - &csr_ex, &csr_dep, &csr_reset, - NULL, NULL, NULL, NULL, - DEV_DEBUG, 0, sys_deb_tab -}; - -t_stat csr_ex(t_value *vptr, t_addr exta, UNIT *uptr, int32 sw) -{ - return SCPE_OK; -} - -t_stat csr_dep(t_value val, t_addr exta, UNIT *uptr, int32 sw) -{ - return SCPE_OK; -} - -t_stat csr_reset(DEVICE *dptr) -{ - CSRBIT(CSRFECC, TRUE); - CSRBIT(CSRTHERM, FALSE); - CSRBIT(CSRITIM, TRUE); - CSRBIT(CSRISTIM, TRUE); - CSRBIT(CSRIBUB, TRUE); - CSRBIT(CSRPWRSPDN, FALSE); - CSRBIT(CSRFLPMO, TRUE); - - return SCPE_OK; -} - -uint32 csr_read(uint32 pa, size_t size) -{ - uint32 reg = (pa - CSRBASE) & 0xff; - - switch (reg & 0xf0) { - case 0x00: - return csr_data & 0xff; - case 0x20: - return (csr_data >> 8) & 0xff; - case 0x40: - return (csr_data >> 16) & 0xff; - case 0x60: - return (csr_data >> 24) & 0xff; - default: - sim_debug(WRITE_MSG, &csr_dev, - "CSR READ. Warning, unexpected register = %02x)\n", - reg); - return 0; - } -} - -#define SET_INT(flag, val) { \ - if (val) { \ - CPU_SET_INT(flag); \ - } else { \ - CPU_CLR_INT(flag); \ - } \ - } - -void csr_write(uint32 pa, uint32 val, size_t size) -{ - uint32 reg = pa - CSRBASE; - - switch (reg) { - - case 0x00: - CSRBIT(CSRCLK, val); - SET_INT(INT_CLOCK, val); - break; - case 0x04: - CSRBIT(CSRPWRDN, val); - SET_INT(INT_PWRDWN, val); - break; - case 0x08: - CSRBIT(CSROPINT15, val); - SET_INT(INT_BUS_OP, val); - break; - case 0x0c: - CSRBIT(CSRUART, val); - SET_INT(INT_UART, val); - break; - case 0x10: - CSRBIT(CSRDMA, val); - SET_INT(INT_UART_DMA, val); - break; - case 0x14: - CSRBIT(CSRPIR9, val); - SET_INT(INT_PIR9, val); - break; - case 0x18: - CSRBIT(CSRPIR8, val); - SET_INT(INT_PIR8, val); - break; - case 0x1c: - CSRBIT(CSRITIM, val); - timer_gate(TIMER_INTERVAL, CSR(CSRITIM)); - break; - case 0x20: - CSRBIT(CSRISTIM, val); - timer_gate(TIMER_SANITY, CSR(CSRISTIM)); - break; - case 0x24: - CSRBIT(CSRITIMO, val); - timer_gate(TIMER_BUS, CSR(CSRITIMO)); - break; - case 0x28: - CSRBIT(CSRICPUFLT, val); - break; - case 0x2c: - CSRBIT(CSRISBERR, val); - break; - case 0x30: - CSRBIT(CSRIIOBUS, val); - break; - case 0x34: - CSRBIT(CSRIBUB, val); - break; - case 0x38: - CSRBIT(CSRFECC, val); - sim_debug(WRITE_MSG, &csr_dev, - "CSR WRITE. Force ECC Syndrome = %d\n", - val); - break; - case 0x3c: - CSRBIT(CSRTHERM, val); - cpu_nmi = val ? TRUE : FALSE; /* Immediate NMI */ - break; - case 0x40: - CSRBIT(CSRLED, val); - break; - case 0x44: - CSRBIT(CSRPWRSPDN, val); - if (!val) { - /* Stop the simulator - power down */ - stop_reason = STOP_POWER; - } - break; - case 0x48: - CSRBIT(CSRFLPFST, val); - break; - case 0x4c: /* Floppy Side 1: Set when Cleared */ - if_state.side = (val & 1) ? 0 : 1; - CSRBIT(CSRFLPS1, val & 1); - break; - case 0x50: - CSRBIT(CSRFLPMO, val); - break; - case 0x54: - CSRBIT(CSRFLPDEN, val); - break; - case 0x58: - CSRBIT(CSRFLPSZ, val); - break; - case 0x5c: - CSRBIT(CSRSBERR, val); - if (val) { - if (!(csr_data & CSRISBERR)) { - SET_INT(INT_SBERR, TRUE); - } - } else { - SET_INT(INT_SBERR, FALSE); - } - break; - case 0x60: - CSRBIT(CSRMBERR, val); - SET_INT(INT_MBERR, val); - break; - case 0x64: - CSRBIT(CSRUBUBF, val); - SET_INT(INT_BUS_RXF, val); - break; - case 0x68: - CSRBIT(CSRTIMO, val); - if (val) { - if (!(csr_data & CSRITIMO)) { - SET_INT(INT_BUS_TMO, TRUE); - } - } else { - SET_INT(INT_BUS_TMO, FALSE); - } - break; - case 0x6c: - CSRBIT(CSRFRF, val); - break; - case 0x70: - CSRBIT(CSRALGN, val); - break; - case 0x74: - CSRBIT(CSRSTIMO, val); - cpu_nmi = val ? TRUE : FALSE; /* Immediate NMI */ - break; - case 0x78: - CSRBIT(CSRABRT, val); - cpu_nmi = val ? TRUE : FALSE; /* Immediate NMI */ - break; - case 0x7c: - /* System reset request */ - full_reset(); - cpu_boot(0, &cpu_dev); - break; - default: - /* Do nothing */ - break; - } -} +/* 3b2_rev3_csr.c: CM518B System Board Control, Status & Error Register + + Copyright (c) 2020-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#include "3b2_cpu.h" +#include "3b2_csr.h" +#include "3b2_if.h" +#include "3b2_timer.h" +#include "3b2_sys.h" + +CSR_DATA csr_data; + +BITFIELD csr_bits[] = { + BIT(UTIM), + BIT(PWDN), + BIT(OI15), + BIT(IUINT), + BIT(IUDMA), + BIT(PIR9), + BIT(PIR8), + BIT(IUTIM), + + BIT(ISTY), + BIT(IUBUS), + BIT(IFLT), + BIT(ISBER), + BIT(IBUS), + BIT(IBUB), + BIT(FECC), + BIT(THERM), + + BIT(FLED), + BIT(PSPWR), + BIT(FLSPD), + BIT(FLSD1), + BIT(FLMOT), + BIT(FLDEN), + BIT(FLSZ), + BIT(SBER), + + BIT(MBER), + BIT(UBFL), + BIT(TIMO), + BIT(FLTFR), + BIT(DALGN), + BIT(STTIM), + BIT(ABRT), + BIT(RSTR), + + ENDBITS +}; + +UNIT csr_unit = { + UDATA(NULL, UNIT_FIX, CSRSIZE) +}; + +REG csr_reg[] = { + { HRDATADF(DATA, csr_data, 32, "CSR Data", csr_bits) }, + { NULL } +}; + +DEVICE csr_dev = { + "CSR", &csr_unit, csr_reg, NULL, + 1, 16, 8, 4, 16, 32, + &csr_ex, &csr_dep, &csr_reset, + NULL, NULL, NULL, NULL, + DEV_DEBUG, 0, sys_deb_tab +}; + +t_stat csr_ex(t_value *vptr, t_addr exta, UNIT *uptr, int32 sw) +{ + return SCPE_OK; +} + +t_stat csr_dep(t_value val, t_addr exta, UNIT *uptr, int32 sw) +{ + return SCPE_OK; +} + +t_stat csr_reset(DEVICE *dptr) +{ + CSRBIT(CSRFECC, TRUE); + CSRBIT(CSRTHERM, FALSE); + CSRBIT(CSRITIM, TRUE); + CSRBIT(CSRISTIM, TRUE); + CSRBIT(CSRIBUB, TRUE); + CSRBIT(CSRPWRSPDN, FALSE); + CSRBIT(CSRFLPMO, TRUE); + + return SCPE_OK; +} + +uint32 csr_read(uint32 pa, size_t size) +{ + uint32 reg = (pa - CSRBASE) & 0xff; + + switch (reg & 0xf0) { + case 0x00: + return csr_data & 0xff; + case 0x20: + return (csr_data >> 8) & 0xff; + case 0x40: + return (csr_data >> 16) & 0xff; + case 0x60: + return (csr_data >> 24) & 0xff; + default: + sim_debug(WRITE_MSG, &csr_dev, + "CSR READ. Warning, unexpected register = %02x)\n", + reg); + return 0; + } +} + +#define SET_INT(flag, val) { \ + if (val) { \ + CPU_SET_INT(flag); \ + } else { \ + CPU_CLR_INT(flag); \ + } \ + } + +void csr_write(uint32 pa, uint32 val, size_t size) +{ + uint32 reg = pa - CSRBASE; + + switch (reg) { + + case 0x00: + CSRBIT(CSRCLK, val); + SET_INT(INT_CLOCK, val); + break; + case 0x04: + CSRBIT(CSRPWRDN, val); + SET_INT(INT_PWRDWN, val); + break; + case 0x08: + CSRBIT(CSROPINT15, val); + SET_INT(INT_BUS_OP, val); + break; + case 0x0c: + CSRBIT(CSRUART, val); + SET_INT(INT_UART, val); + break; + case 0x10: + CSRBIT(CSRDMA, val); + SET_INT(INT_UART_DMA, val); + break; + case 0x14: + CSRBIT(CSRPIR9, val); + SET_INT(INT_PIR9, val); + break; + case 0x18: + CSRBIT(CSRPIR8, val); + SET_INT(INT_PIR8, val); + break; + case 0x1c: + CSRBIT(CSRITIM, val); + timer_gate(TIMER_INTERVAL, CSR(CSRITIM)); + break; + case 0x20: + CSRBIT(CSRISTIM, val); + timer_gate(TIMER_SANITY, CSR(CSRISTIM)); + break; + case 0x24: + CSRBIT(CSRITIMO, val); + timer_gate(TIMER_BUS, CSR(CSRITIMO)); + break; + case 0x28: + CSRBIT(CSRICPUFLT, val); + break; + case 0x2c: + CSRBIT(CSRISBERR, val); + break; + case 0x30: + CSRBIT(CSRIIOBUS, val); + break; + case 0x34: + CSRBIT(CSRIBUB, val); + break; + case 0x38: + CSRBIT(CSRFECC, val); + sim_debug(WRITE_MSG, &csr_dev, + "CSR WRITE. Force ECC Syndrome = %d\n", + val); + break; + case 0x3c: + CSRBIT(CSRTHERM, val); + cpu_nmi = val ? TRUE : FALSE; /* Immediate NMI */ + break; + case 0x40: + CSRBIT(CSRLED, val); + break; + case 0x44: + CSRBIT(CSRPWRSPDN, val); + if (!val) { + /* Stop the simulator - power down */ + stop_reason = STOP_POWER; + } + break; + case 0x48: + CSRBIT(CSRFLPFST, val); + break; + case 0x4c: /* Floppy Side 1: Set when Cleared */ + if_state.side = (val & 1) ? 0 : 1; + CSRBIT(CSRFLPS1, val & 1); + break; + case 0x50: + CSRBIT(CSRFLPMO, val); + break; + case 0x54: + CSRBIT(CSRFLPDEN, val); + break; + case 0x58: + CSRBIT(CSRFLPSZ, val); + break; + case 0x5c: + CSRBIT(CSRSBERR, val); + if (val) { + if (!(csr_data & CSRISBERR)) { + SET_INT(INT_SBERR, TRUE); + } + } else { + SET_INT(INT_SBERR, FALSE); + } + break; + case 0x60: + CSRBIT(CSRMBERR, val); + SET_INT(INT_MBERR, val); + break; + case 0x64: + CSRBIT(CSRUBUBF, val); + SET_INT(INT_BUS_RXF, val); + break; + case 0x68: + CSRBIT(CSRTIMO, val); + if (val) { + if (!(csr_data & CSRITIMO)) { + SET_INT(INT_BUS_TMO, TRUE); + } + } else { + SET_INT(INT_BUS_TMO, FALSE); + } + break; + case 0x6c: + CSRBIT(CSRFRF, val); + break; + case 0x70: + CSRBIT(CSRALGN, val); + break; + case 0x74: + CSRBIT(CSRSTIMO, val); + cpu_nmi = val ? TRUE : FALSE; /* Immediate NMI */ + break; + case 0x78: + CSRBIT(CSRABRT, val); + cpu_nmi = val ? TRUE : FALSE; /* Immediate NMI */ + break; + case 0x7c: + /* System reset request */ + full_reset(); + cpu_boot(0, &cpu_dev); + break; + default: + /* Do nothing */ + break; + } +} diff --git a/3B2/3b2_rev3_csr.h b/3B2/3b2_rev3_csr.h index 61dd7b7b..17078bed 100644 --- a/3B2/3b2_rev3_csr.h +++ b/3B2/3b2_rev3_csr.h @@ -1,45 +1,45 @@ -/* 3b2_rev3_csr.h: CM518B System Board Control, Status & Error Register - - Copyright (c) 2020-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#ifndef _3B2_400_CSR_H_ -#define _3B2_400_CSR_H_ - -#include "3b2_defs.h" - -typedef uint32 CSR_DATA; - -t_stat csr_svc(UNIT *uptr); -t_stat csr_ex(t_value *vptr, t_addr exta, UNIT *uptr, int32 sw); -t_stat csr_dep(t_value val, t_addr exta, UNIT *uptr, int32 sw); -t_stat csr_reset(DEVICE *dptr); -uint32 csr_read(uint32 pa, size_t size); -void csr_write(uint32 pa, uint32 val, size_t size); - -#endif +/* 3b2_rev3_csr.h: CM518B System Board Control, Status & Error Register + + Copyright (c) 2020-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#ifndef _3B2_400_CSR_H_ +#define _3B2_400_CSR_H_ + +#include "3b2_defs.h" + +typedef uint32 CSR_DATA; + +t_stat csr_svc(UNIT *uptr); +t_stat csr_ex(t_value *vptr, t_addr exta, UNIT *uptr, int32 sw); +t_stat csr_dep(t_value val, t_addr exta, UNIT *uptr, int32 sw); +t_stat csr_reset(DEVICE *dptr); +uint32 csr_read(uint32 pa, size_t size); +void csr_write(uint32 pa, uint32 val, size_t size); + +#endif diff --git a/3B2/3b2_rev3_defs.h b/3B2/3b2_rev3_defs.h index 9d55a270..31cd7006 100644 --- a/3B2/3b2_rev3_defs.h +++ b/3B2/3b2_rev3_defs.h @@ -1,141 +1,141 @@ -/* 3b2_rev3_defs.h: Veresion 3 (3B2/700) Common Definitions - - Copyright (c) 2021-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. - */ - -#ifndef _3B2_REV3_DEFS_H_ -#define _3B2_REV3_DEFS_H_ - -#define NUM_REGISTERS 32 - -#define DEFMEMSIZE MSIZ_16M -#define MAXMEMSIZE MSIZ_64M - -#define HWORD_OP_COUNT 12 -#define CPU_VERSION 0x1F /* Version encoded in WE32200 */ - -/* CSR Flags */ -#define CSRCLK 1u /* UNIX Interval Timer Timeout */ -#define CSRPWRDN (1u << 1) /* Power Down Request */ -#define CSROPINT15 (1u << 2) /* Oper. Interrupt Level 15 */ -#define CSRUART (1u << 3) /* DUART Interrupt */ -#define CSRDMA (1u << 4) /* DUART DMA Complete Interrupt */ -#define CSRPIR9 (1u << 5) /* Programmed Interrupt 9 */ -#define CSRPIR8 (1u << 6) /* Programmed Interrupt 8 */ -#define CSRITIM (1u << 7) /* Inhibit UNIX Interval Timer */ -#define CSRISTIM (1u << 8) /* Inhibit System Sanity Timer */ -#define CSRITIMO (1u << 9) /* Inhibit Bus Timer */ -#define CSRICPUFLT (1u << 10) /* Inhibit Faults to CPU */ -#define CSRISBERR (1u << 11) /* Inhibit Single Bit Error Rpt */ -#define CSRIIOBUS (1u << 12) /* Inhibit Integral 3B2 I/O Bus */ -#define CSRIBUB (1u << 13) /* Inhibit 4 BUB Slots */ -#define CSRFECC (1u << 14) /* Force ECC Syndrome */ -#define CSRTHERM (1u << 15) /* Thermal Shutdown Request */ -#define CSRLED (1u << 16) /* Failure LED */ -#define CSRPWRSPDN (1u << 17) /* Power Down -- Power Supply */ -#define CSRFLPFST (1u << 18) /* Floppy Speed Fast */ -#define CSRFLPS1 (1u << 19) /* Floppy Side 1 */ -#define CSRFLPMO (1u << 20) /* Floppy Motor On */ -#define CSRFLPDEN (1u << 21) /* Floppy Density */ -#define CSRFLPSZ (1u << 22) /* Floppy Size */ -#define CSRSBERR (1u << 23) /* Single Bit Error */ -#define CSRMBERR (1u << 24) /* Multiple Bit Error */ -#define CSRUBUBF (1u << 25) /* Ubus/BUB Received Fail */ -#define CSRTIMO (1u << 26) /* Bus Timer Timeout */ -#define CSRFRF (1u << 27) /* Fault Registers Frozen */ -#define CSRALGN (1u << 28) /* Data Alignment Error */ -#define CSRSTIMO (1u << 29) /* Sanity Timer Timeout */ -#define CSRABRT (1u << 30) /* Abort Switch Activated */ -#define CSRRRST (1u << 31) /* System Reset Request */ - -/* Interrupt Sources */ -#define INT_CLOCK 0x0001 /* UNIX Interval Timer Timeout - IPL 15 */ -#define INT_PWRDWN 0x0002 /* Power Down Request - IPL 15 */ -#define INT_BUS_OP 0x0004 /* UBUS or BUB Operational Interrupt - IPL 15 */ -#define INT_SBERR 0x0008 /* Single Bit Memory Error - IPL 15 */ -#define INT_MBERR 0x0010 /* Multiple Bit Memory Error - IPL 15 */ -#define INT_BUS_RXF 0x0020 /* UBUS, BUB, EIO Bus Received Fail - IPL 15 */ -#define INT_BUS_TMO 0x0040 /* UBUS Timer Timeout - IPL 15 */ -#define INT_UART_DMA 0x0080 /* UART DMA Complete - IPL 13 */ -#define INT_UART 0x0100 /* UART Interrupt - IPL 13 */ -#define INT_FLOPPY_DMA 0x0200 /* Floppy DMA Complete - IPL 11 */ -#define INT_FLOPPY 0x0400 /* Floppy Interrupt - IPL 11 */ -#define INT_PIR9 0x0800 /* PIR-9 (from CSER) - IPL 9 */ -#define INT_PIR8 0x1000 /* PIR-8 (from CSER) - IPL 8 */ - -#define INT_MAP_LEN 0x2000 - -#define IFCSRBASE 0x40000 -#define IFCSRSIZE 0x100 -#define TIMERBASE 0x41000 -#define TIMERSIZE 0x20 -#define NVRBASE 0x42000 -#define NVRSIZE 0x2000 -#define CSRBASE 0x44000 -#define CSRSIZE 0x100 -#define DMAIFBASE 0x45000 -#define DMAIFSIZE 0x5 -#define DMAIUABASE 0x46000 -#define DMAIUASIZE 0x5 -#define DMAIUBBASE 0x47000 -#define DMAIUBSIZE 0x5 -#define DMACBASE 0x48000 -#define DMACSIZE 0x11 -#define IFBASE 0x4a000 -#define IFSIZE 0x10 -#define TODBASE 0x4e000 -#define TODSIZE 0x40 -#define MMUBASE 0x4f000 -#define MMUSIZE 0x1000 -#define FLTLBASE 0x4c000 -#define FLTLSIZE 0x1000 -#define FLTHBASE 0x4d000 -#define FLTHSIZE 0x1000 - -#define VCACHE_BOTTOM 0x1c00000 -#define VCACHE_TOP 0x2000000 - -#define BUB_BOTTOM 0x6000000 -#define BUB_TOP 0x1a000000 - -#define IF_STATUS_REG 0 -#define IF_CMD_REG 0 -#define IF_TRACK_REG 1 -#define IF_SECTOR_REG 2 -#define IF_DATA_REG 3 - -#define DMA_IF_CHAN 1 -#define DMA_IUA_CHAN 2 -#define DMA_IUB_CHAN 3 - -#define DMA_IF 0x45 -#define DMA_IUA 0x46 -#define DMA_IUB 0x47 -#define DMA_C 0x48 - -#endif +/* 3b2_rev3_defs.h: Veresion 3 (3B2/700) Common Definitions + + Copyright (c) 2021-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. + */ + +#ifndef _3B2_REV3_DEFS_H_ +#define _3B2_REV3_DEFS_H_ + +#define NUM_REGISTERS 32 + +#define DEFMEMSIZE MSIZ_16M +#define MAXMEMSIZE MSIZ_64M + +#define HWORD_OP_COUNT 12 +#define CPU_VERSION 0x1F /* Version encoded in WE32200 */ + +/* CSR Flags */ +#define CSRCLK 1u /* UNIX Interval Timer Timeout */ +#define CSRPWRDN (1u << 1) /* Power Down Request */ +#define CSROPINT15 (1u << 2) /* Oper. Interrupt Level 15 */ +#define CSRUART (1u << 3) /* DUART Interrupt */ +#define CSRDMA (1u << 4) /* DUART DMA Complete Interrupt */ +#define CSRPIR9 (1u << 5) /* Programmed Interrupt 9 */ +#define CSRPIR8 (1u << 6) /* Programmed Interrupt 8 */ +#define CSRITIM (1u << 7) /* Inhibit UNIX Interval Timer */ +#define CSRISTIM (1u << 8) /* Inhibit System Sanity Timer */ +#define CSRITIMO (1u << 9) /* Inhibit Bus Timer */ +#define CSRICPUFLT (1u << 10) /* Inhibit Faults to CPU */ +#define CSRISBERR (1u << 11) /* Inhibit Single Bit Error Rpt */ +#define CSRIIOBUS (1u << 12) /* Inhibit Integral 3B2 I/O Bus */ +#define CSRIBUB (1u << 13) /* Inhibit 4 BUB Slots */ +#define CSRFECC (1u << 14) /* Force ECC Syndrome */ +#define CSRTHERM (1u << 15) /* Thermal Shutdown Request */ +#define CSRLED (1u << 16) /* Failure LED */ +#define CSRPWRSPDN (1u << 17) /* Power Down -- Power Supply */ +#define CSRFLPFST (1u << 18) /* Floppy Speed Fast */ +#define CSRFLPS1 (1u << 19) /* Floppy Side 1 */ +#define CSRFLPMO (1u << 20) /* Floppy Motor On */ +#define CSRFLPDEN (1u << 21) /* Floppy Density */ +#define CSRFLPSZ (1u << 22) /* Floppy Size */ +#define CSRSBERR (1u << 23) /* Single Bit Error */ +#define CSRMBERR (1u << 24) /* Multiple Bit Error */ +#define CSRUBUBF (1u << 25) /* Ubus/BUB Received Fail */ +#define CSRTIMO (1u << 26) /* Bus Timer Timeout */ +#define CSRFRF (1u << 27) /* Fault Registers Frozen */ +#define CSRALGN (1u << 28) /* Data Alignment Error */ +#define CSRSTIMO (1u << 29) /* Sanity Timer Timeout */ +#define CSRABRT (1u << 30) /* Abort Switch Activated */ +#define CSRRRST (1u << 31) /* System Reset Request */ + +/* Interrupt Sources */ +#define INT_CLOCK 0x0001 /* UNIX Interval Timer Timeout - IPL 15 */ +#define INT_PWRDWN 0x0002 /* Power Down Request - IPL 15 */ +#define INT_BUS_OP 0x0004 /* UBUS or BUB Operational Interrupt - IPL 15 */ +#define INT_SBERR 0x0008 /* Single Bit Memory Error - IPL 15 */ +#define INT_MBERR 0x0010 /* Multiple Bit Memory Error - IPL 15 */ +#define INT_BUS_RXF 0x0020 /* UBUS, BUB, EIO Bus Received Fail - IPL 15 */ +#define INT_BUS_TMO 0x0040 /* UBUS Timer Timeout - IPL 15 */ +#define INT_UART_DMA 0x0080 /* UART DMA Complete - IPL 13 */ +#define INT_UART 0x0100 /* UART Interrupt - IPL 13 */ +#define INT_FLOPPY_DMA 0x0200 /* Floppy DMA Complete - IPL 11 */ +#define INT_FLOPPY 0x0400 /* Floppy Interrupt - IPL 11 */ +#define INT_PIR9 0x0800 /* PIR-9 (from CSER) - IPL 9 */ +#define INT_PIR8 0x1000 /* PIR-8 (from CSER) - IPL 8 */ + +#define INT_MAP_LEN 0x2000 + +#define IFCSRBASE 0x40000 +#define IFCSRSIZE 0x100 +#define TIMERBASE 0x41000 +#define TIMERSIZE 0x20 +#define NVRBASE 0x42000 +#define NVRSIZE 0x2000 +#define CSRBASE 0x44000 +#define CSRSIZE 0x100 +#define DMAIFBASE 0x45000 +#define DMAIFSIZE 0x5 +#define DMAIUABASE 0x46000 +#define DMAIUASIZE 0x5 +#define DMAIUBBASE 0x47000 +#define DMAIUBSIZE 0x5 +#define DMACBASE 0x48000 +#define DMACSIZE 0x11 +#define IFBASE 0x4a000 +#define IFSIZE 0x10 +#define TODBASE 0x4e000 +#define TODSIZE 0x40 +#define MMUBASE 0x4f000 +#define MMUSIZE 0x1000 +#define FLTLBASE 0x4c000 +#define FLTLSIZE 0x1000 +#define FLTHBASE 0x4d000 +#define FLTHSIZE 0x1000 + +#define VCACHE_BOTTOM 0x1c00000 +#define VCACHE_TOP 0x2000000 + +#define BUB_BOTTOM 0x6000000 +#define BUB_TOP 0x1a000000 + +#define IF_STATUS_REG 0 +#define IF_CMD_REG 0 +#define IF_TRACK_REG 1 +#define IF_SECTOR_REG 2 +#define IF_DATA_REG 3 + +#define DMA_IF_CHAN 1 +#define DMA_IUA_CHAN 2 +#define DMA_IUB_CHAN 3 + +#define DMA_IF 0x45 +#define DMA_IUA 0x46 +#define DMA_IUB 0x47 +#define DMA_C 0x48 + +#endif diff --git a/3B2/3b2_rev3_mmu.c b/3B2/3b2_rev3_mmu.c index ceee7b4d..ccfc6eda 100644 --- a/3B2/3b2_rev3_mmu.c +++ b/3B2/3b2_rev3_mmu.c @@ -1,1178 +1,1178 @@ -/* 3b2_rev3_mmu.c: WE32201 MMU - - Copyright (c) 2020-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - - -#include "3b2_cpu.h" -#include "3b2_csr.h" -#include "3b2_mem.h" -#include "3b2_mmu.h" - -UNIT mmu_unit = { UDATA(NULL, 0, 0) }; - -MMU_STATE mmu_state; - -REG mmu_reg[] = { - { HRDATAD (ENABLE, mmu_state.enabled, 1, "Enabled?") }, - { HRDATAD (CONFIG, mmu_state.conf, 32, "Configuration") }, - { HRDATAD (VAR, mmu_state.var, 32, "Virtual Address") }, - { HRDATAD (FCODE, mmu_state.fcode, 32, "Fault Code") }, - { HRDATAD (FADDR, mmu_state.faddr, 32, "Fault Address") }, - { BRDATA (SDCL, mmu_state.sdcl, 16, 32, MMU_SDCS) }, - { BRDATA (SDCH, mmu_state.sdch, 16, 32, MMU_SDCS) }, - { BRDATA (PDCL, mmu_state.pdcl, 16, 32, MMU_PDCS) }, - { BRDATA (PDCH, mmu_state.pdch, 16, 32, MMU_PDCS) }, - { BRDATA (SRAMA, mmu_state.sra, 16, 32, MMU_SRS) }, - { BRDATA (SRAMB, mmu_state.srb, 16, 32, MMU_SRS) }, - { NULL } -}; - -#define MMU_EXEC_DBG 1 -#define MMU_TRACE_DBG 1 << 1 -#define MMU_CACHE_DBG 1 << 2 -#define MMU_FAULT_DBG 1 << 3 -#define MMU_READ_DBG 1 << 4 -#define MMU_WRITE_DBG 1 << 5 - -static DEBTAB mmu_debug[] = { - { "EXEC", MMU_EXEC_DBG, "Simple execution" }, - { "CACHE", MMU_CACHE_DBG, "Cache trace" }, - { "TRACE", MMU_TRACE_DBG, "Translation trace" }, - { "FAULT", MMU_FAULT_DBG, "Faults" }, - { "READ", MMU_READ_DBG, "Peripheral Read"}, - { "WRITE", MMU_WRITE_DBG, "Peripheral Write"}, - { NULL } -}; - -MTAB mmu_mod[] = { - { MTAB_XTD|MTAB_VDV|MTAB_NMO|MTAB_SHP, 0, "SDT", NULL, - NULL, &mmu_show_sdt, NULL, "Display SDT for section n [0-3]" }, - { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "SDC", NULL, - NULL, &mmu_show_sdc, NULL, "Display SD Cache" }, - { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "PDC", NULL, - NULL, &mmu_show_pdc, NULL, "Display PD Cache" }, - { 0 } -}; - -DEVICE mmu_dev = { - "MMU", /* name */ - &mmu_unit, /* units */ - mmu_reg, /* registers */ - mmu_mod, /* modifiers */ - 1, /* #units */ - 16, /* address radix */ - 8, /* address width */ - 4, /* address incr */ - 16, /* data radix */ - 32, /* data width */ - NULL, /* examine routine */ - NULL, /* deposit routine */ - &mmu_init, /* reset routine */ - NULL, /* boot routine */ - NULL, /* attach routine */ - NULL, /* detach routine */ - NULL, /* context */ - DEV_DEBUG, /* flags */ - 0, /* debug control flags */ - mmu_debug, /* debug flag names */ - NULL, /* memory size change */ - NULL, /* logical name */ - NULL, /* help routine */ - NULL, /* attach help routine */ - NULL, /* help context */ - &mmu_description /* device description */ -}; - -/* - * Each bitmask corresponds to the pattern of bits used for the tag in - * the first word of a segment descriptor in the cache. The outer - * index corresponds to mode (0=Single-Context Mode, 1=Multi-Context - * Mode), the inner index corresponds to page size (0=2kB, 1=4kB, - * 2=8kB, 3=undefined) - */ -uint32 pdc_tag_masks[2][4] = { - {0x43ffffe0, 0x43ffffc0, 0x43ffff80, 0}, - {0x7fffffe0, 0x7fffffc0, 0x7fffff80, 0}, -}; - -/* - * Bitmasks for generating page addresses for contiguous segments on - * cache miss. - */ -uint32 pd_addr_masks[4] = { - 0xfffff800, 0xfffff000, 0xffffe000, 0 -}; - -uint32 pd_psl_masks[4] = { - 0x1f800, 0x1f000, 0x1e000, 0 -}; - -/* Masks used when searching the PD cache for a matching tag. */ -#define PDC_TAG_MASK (pdc_tag_masks[MMU_CONF_MCE][MMU_CONF_PS]) - -/* Mask off the bottom 10 bits of virtual address when generating PD - * cache tags */ -#define VA_TO_TAG_MASK 0xfffff800 - -/* - * Macros used to generate PD cache tags from virtual-addresses - */ -#define PDC_MTAG(VA) ((((VA) & VA_TO_TAG_MASK) >> 6) | \ - (mmu_state.cidnr[SID(VA)] << 26) | \ - (1 << 30)) - -#define PDC_STAG(VA) ((((VA) & VA_TO_TAG_MASK) >> 6) | \ - (1 << 30)) - -#define PDC_TAG(VA) (MMU_CONF_MCE ? PDC_MTAG(VA) : PDC_STAG(VA)) - -/* - * Retrieve a Segment Descriptor from the SD cache. The Segment - * Descriptor Cache entry is returned in sd_lo and sd_hi, if found. - * - * If there is a cache hit, this function returns SCPE_OK. - * If there is a cache miss, this function returns SCPE_NXM. - */ -static t_stat get_sdce(uint32 va, uint32 *sd_hi, uint32 *sd_lo) -{ - uint32 hi, lo, va_tag, sdc_tag; - - hi = mmu_state.sdch[SDC_IDX(va)]; - lo = mmu_state.sdcl[SDC_IDX(va)]; - va_tag = (va >> 20) & 0xfff; - sdc_tag = lo & 0xfff; - - if ((hi & SDC_G_MASK) && (va_tag == sdc_tag)) { - *sd_hi = SDCE_TO_SDH(hi); - *sd_lo = SDCE_TO_SDL(hi,lo); - return SCPE_OK; - } - - return SCPE_NXM; -} - -/* - * Insert a Segment Descriptor into the SD cache. - */ -static void put_sdce(uint32 va, uint32 sd_hi, uint32 sd_lo) -{ - uint8 ci = SDC_IDX(va); - - mmu_state.sdch[ci] = SD_TO_SDCH(sd_hi, sd_lo); - mmu_state.sdcl[ci] = SD_TO_SDCL(sd_lo, va); - - sim_debug(MMU_CACHE_DBG, &mmu_dev, - "CACHED SD AT IDX %d. va=%08x sd_hi=%08x sd_lo=%08x sdc_hi=%08x sdc_lo=%08x\n", - ci, va, sd_hi, sd_lo, mmu_state.sdch[ci], mmu_state.sdcl[ci]); - -} - -/* - * Update the "Used" bit in the Page Descriptor cache for the given - * entry. - */ -static void set_u_bit(uint32 index) -{ - uint32 i; - - mmu_state.pdch[index] |= PDC_U_MASK; - - /* Check to see if all U bits have been set. If so, the cache will - * need to be flushed on the next put */ - for (i = 0; i < MMU_PDCS; i++) { - if ((mmu_state.pdch[i] & PDC_U_MASK) == 0) { - return; - } - } - - mmu_state.flush_u = TRUE; -} - -/* - * Retrieve a Page Descriptor Cache Entry from the Page Descriptor - * Cache. - */ -static t_stat get_pdce(uint32 va, uint32 *pd, uint8 *pd_acc, uint32 *pdc_idx) -{ - uint32 i, key_tag, target_tag; - - *pdc_idx = 0; - - /* This is a fully associative cache, so we must scan for an entry - with the correct tag. */ - key_tag = PDC_TAG(va) & PDC_TAG_MASK; - - for (i = 0; i < MMU_PDCS; i++) { - target_tag = mmu_state.pdch[i] & PDC_TAG_MASK; - if (target_tag == key_tag) { - /* Construct the PD from the cached version */ - *pd = PDCE_TO_PD(mmu_state.pdcl[i]); - *pd_acc = (mmu_state.pdcl[i] >> 24) & 0xff; - *pdc_idx = i; - sim_debug(MMU_TRACE_DBG, &mmu_dev, - "PDC HIT. va=%08x idx=%d tag=%03x pd=%08x pdcl=%08x pdch=%08x\n", - va, i, key_tag, *pd, - mmu_state.pdcl[i], mmu_state.pdch[i]); - set_u_bit(i); - return SCPE_OK; - } - } - - sim_debug(MMU_CACHE_DBG, &mmu_dev, - "PDC MISS. va=%08x tag=%03x\n", - va, key_tag); - - return SCPE_NXM; -} - -/* - * Cache a Page Descriptor in the specified slot. - */ -static void put_pdce_at(uint32 va, uint32 sd_lo, uint32 pd, uint32 slot) -{ - mmu_state.pdcl[slot] = PD_TO_PDCL(pd, sd_lo); - mmu_state.pdch[slot] = VA_TO_PDCH(va, sd_lo); - sim_debug(MMU_CACHE_DBG, &mmu_dev, - "Caching MMU PDC entry at index %d (pdc_hi=%08x pdc_lo=%08x va=%08x)\n", - slot, mmu_state.pdch[slot], mmu_state.pdcl[slot], va); - set_u_bit(slot); - mmu_state.last_cached = slot; -} - -/* - * Cache a Page Descriptor in the first available, least recently used - * slot. - */ -static uint32 put_pdce(uint32 va, uint32 sd_lo, uint32 pd) -{ - uint32 i; - - /* - * If all the U bits have been set, flush them all EXCEPT the most - * recently cached entry. - */ - if (mmu_state.flush_u) { - sim_debug(MMU_CACHE_DBG, &mmu_dev, - "Flushing PDC U bits on all-set condition.\n"); - mmu_state.flush_u = FALSE; - for (i = 0; i < MMU_PDCS; i++) { - if (i != mmu_state.last_cached) { - mmu_state.pdch[i] &= ~(PDC_U_MASK); - } - } - } - - /* TODO: This can be done in one pass! Clean it up */ - - /* Cache the Page Descriptor in the first slot with a cleared G - * bit */ - for (i = 0; i < MMU_PDCS; i++) { - if ((mmu_state.pdch[i] & PDC_G_MASK) == 0) { - put_pdce_at(va, sd_lo, pd, i); - return i; - } - } - - /* If ALL slots had their G bit set, find the first slot with a - cleared U bit */ - for (i = 0; i < MMU_PDCS; i++) { - if ((mmu_state.pdch[i] & PDC_U_MASK) == 0) { - put_pdce_at(va, sd_lo, pd, i); - return i; - } - } - - /* This should never happen, since if all U bits become set, they - * are automatically all cleared */ - stop_reason = STOP_MMU; - - return 0; -} - -/* - * Flush the cache for an individual virtual address. - */ -static void flush_pdc(uint32 va) -{ - uint32 i, j, key_tag, target_tag; - - /* Flush the PDC. This is a fully associative cache, so we must - * scan for an entry with the correct tag. */ - - key_tag = PDC_TAG(va) & PDC_TAG_MASK; - - for (i = 0; i < MMU_PDCS; i++) { - target_tag = mmu_state.pdch[i] & PDC_TAG_MASK; - if (target_tag == key_tag) { - sim_debug(MMU_CACHE_DBG, &mmu_dev, - "Flushing MMU PDC entry pdc_lo=%08x pdc_hi=%08x index %d (va=%08x)\n", - mmu_state.pdcl[i], - mmu_state.pdch[i], - i, - va); - if (mmu_state.pdch[i] & PDC_C_MASK) { - sim_debug(MMU_CACHE_DBG, &mmu_dev, - "Flushing MMU PDC entry: CONTIGUOUS\n"); - /* If this PD came from a contiguous SD, we need to - * flush ALL entries belonging to the same SD. All - * pages within the same segment have the same upper - * 11 bits. */ - for (j = 0; j < MMU_PDCS; j++) { - if ((mmu_state.pdch[j] & 0x3ffc000) == - (mmu_state.pdch[i] & 0x3ffc000)) { - mmu_state.pdch[j] &= ~(PDC_G_MASK|PDC_U_MASK); - } - } - } else { - /* Otherwise, just flush the one entry */ - mmu_state.pdch[i] &= ~(PDC_G_MASK|PDC_U_MASK); - } - return; - } - } - - sim_debug(MMU_CACHE_DBG, &mmu_dev, - "Flushing MMU PDC entry: NOT FOUND (va=%08x key_tag=%08x)\n", - va, key_tag); - -} - -/* - * Flush all entries in both SDC and PDC. - */ -static void flush_caches() -{ - uint32 i; - - sim_debug(MMU_CACHE_DBG, &mmu_dev, - "Flushing MMU PDC and SDC\n"); - - for (i = 0; i < MMU_SDCS; i++) { - mmu_state.sdch[i] &= ~SDC_G_MASK; - } - - for (i = 0; i < MMU_PDCS; i++) { - mmu_state.pdch[i] &= ~PDC_G_MASK; - mmu_state.pdch[i] &= ~PDC_U_MASK; - } -} - -/* - * Check permissions for a set of permission flags and an access type. - * - * Return SCPE_OK if permission is granted, SCPE_NXM if permission is - * not allowed. - */ -static t_stat mmu_check_perm(uint8 flags, uint8 r_acc) -{ - switch(MMU_PERM(flags)) { - case 0: /* No Access */ - return SCPE_NXM; - case 1: /* Exec Only */ - if (r_acc != ACC_IF && - r_acc != ACC_IFAD) { - return SCPE_NXM; - } - return SCPE_OK; - case 2: /* Read / Execute */ - if (r_acc != ACC_IF && - r_acc != ACC_IFAD && - r_acc != ACC_OF && - r_acc != ACC_AF && - r_acc != ACC_MT) { - return SCPE_NXM; - } - return SCPE_OK; - default: - return SCPE_OK; - } -} - -/* - * Initialize the MMU device - */ -t_stat mmu_init(DEVICE *dptr) -{ - flush_caches(); - return SCPE_OK; -} - -/* - * Memory-mapped (peripheral mode) read of the MMU device - */ -uint32 mmu_read(uint32 pa, size_t size) -{ - uint8 entity, index; - uint32 data = 0; - - /* Register entity */ - entity = ((uint8)(pa >> 8)) & 0xf; - - /* Index into entity */ - index = (uint8)((pa >> 2) & 0x1f); - - switch (entity) { - case MMU_SDCL: - data = mmu_state.sdcl[index]; - sim_debug(MMU_READ_DBG, &mmu_dev, - "MMU_SDCL[%d] = %08x\n", - index, data); - break; - case MMU_SDCH: - data = mmu_state.sdch[index]; - sim_debug(MMU_READ_DBG, &mmu_dev, - "MMU_SDCH[%d] = %08x\n", - index, data); - break; - case MMU_PDCL: - data = mmu_state.pdcl[index]; - sim_debug(MMU_READ_DBG, &mmu_dev, - "MMU_PDCL[%d] = %08x\n", - index, data); - break; - case MMU_PDCH: - data = mmu_state.pdch[index]; - sim_debug(MMU_READ_DBG, &mmu_dev, - "MMU_PDCH[%d] = %08x\n", - index, data); - break; - case MMU_SRAMA: - data = mmu_state.sra[index]; - sim_debug(MMU_READ_DBG, &mmu_dev, - "MMU_SRAMA[%d] = %08x\n", - index, data); - break; - case MMU_SRAMB: - data = mmu_state.srb[index]; - sim_debug(MMU_READ_DBG, &mmu_dev, - "MMU_SRAMB[%d] = %08x\n", - index, data); - break; - case MMU_FC: - data = mmu_state.fcode; - sim_debug(MMU_READ_DBG, &mmu_dev, - "MMU_FC = %08x\n", - data); - break; - case MMU_FA: - data = mmu_state.faddr; - sim_debug(MMU_READ_DBG, &mmu_dev, - "MMU_FA = %08x\n", - data); - break; - case MMU_CONF: - data = mmu_state.conf; - sim_debug(MMU_READ_DBG, &mmu_dev, - "MMU_CONF = %02x (M=%d R=%d $=%d PS=%d MCE=%d DCE=%d)\n", - data, - MMU_CONF_M, - MMU_CONF_R, - MMU_CONF_C, - MMU_CONF_PS, - MMU_CONF_MCE, - MMU_CONF_DCE); - break; - case MMU_VAR: - data = mmu_state.var; - sim_debug(MMU_READ_DBG, &mmu_dev, - "MMU_VAR = %08x\n", - data); - break; - case MMU_IDC: - /* TODO: Implement */ - data = 0; - sim_debug(MMU_READ_DBG, &mmu_dev, - "MMU_IDC\n"); - break; - case MMU_IDNR: - /* TODO: Implement */ - data = 0; - sim_debug(MMU_READ_DBG, &mmu_dev, - "MMU_IDNR\n"); - break; - case MMU_FIDNR: - /* TODO: Implement */ - data = 0; - sim_debug(MMU_READ_DBG, &mmu_dev, - "MMU_FIDNR\n"); - break; - case MMU_VR: - data = MMU_REV3_VER; - sim_debug(MMU_READ_DBG, &mmu_dev, - "MMU_VR = 0x23\n"); - break; - default: - sim_debug(MMU_READ_DBG, &mmu_dev, - "Invalid MMU register: pa=%08x\n", - pa); - CSRBIT(CSRTIMO, TRUE); - break; - } - - return data; -} - -void mmu_write(uint32 pa, uint32 val, size_t size) -{ - uint32 index, entity, i; - - /* Register entity */ - entity = ((uint8)(pa >> 8)) & 0xf; - - /* Index into entity */ - index = (uint8)((pa >> 2) & 0x1f); - - switch (entity) { - case MMU_SDCL: - sim_debug(MMU_WRITE_DBG, &mmu_dev, - "MMU_SDCL[%d] = %08x\n", - index, val); - mmu_state.sdcl[index] = val; - break; - case MMU_SDCH: - sim_debug(MMU_WRITE_DBG, &mmu_dev, - "MMU_SDCH[%d] = %08x\n", - index, val); - mmu_state.sdch[index] = val; - break; - case MMU_PDCL: - sim_debug(MMU_WRITE_DBG, &mmu_dev, - "MMU_PDCL[%d] = %08x\n", - index, val); - mmu_state.pdcl[index] = val; - break; - case MMU_PDCH: - sim_debug(MMU_WRITE_DBG, &mmu_dev, - "MMU_PDCH[%d] = %08x\n", - index, val); - mmu_state.pdch[index] = val; - break; - case MMU_FDCR: - sim_debug(MMU_WRITE_DBG, &mmu_dev, - "MMU_FDCR\n"); - /* Data cache is not implemented */ - break; - case MMU_SRAMA: - index = index & 3; - sim_debug(MMU_WRITE_DBG, &mmu_dev, - "MMU_SRAMA[%d] = %08x\n", - index, val); - mmu_state.sra[index] = val; - mmu_state.sec[index].addr = val & 0xfffffffc; - - /* Flush all SDC cache entries for this section */ - for (i = 0; i < MMU_SDCS; i++) { - if (((mmu_state.sdcl[i] >> 10) & 0x3) == index) { - sim_debug(MMU_CACHE_DBG, &mmu_dev, - "Flushing MMU SDC entry at index %d " - "(sdc_lo=%08x sdc_hi=%08x)\n", - i, mmu_state.sdcl[i], mmu_state.sdch[i]); - mmu_state.sdch[i] &= ~(SDC_G_MASK); - } - } - - /* Flush all PDC cache entries for this section */ - for (i = 0; i < MMU_PDCS; i++) { - if (((mmu_state.pdch[i] >> 24) & 0x3) == index) { - mmu_state.pdch[i] &= ~(PDC_G_MASK); - } - } - break; - case MMU_SRAMB: - index = index & 3; - mmu_state.srb[index] = val; - mmu_state.sec[index].len = (val >> 10) & 0x1fff; - /* We do not flush the cache on writing SRAMB */ - sim_debug(MMU_WRITE_DBG, &mmu_dev, - "MMU_SRAMB[%d] length=%04x (%d segments)\n", - index, - mmu_state.sec[index].len, - mmu_state.sec[index].len + 1); - break; - case MMU_FC: - /* Set a default value */ - mmu_state.fcode = (((CPU_CM) << 5) | (0xa << 7)); - sim_debug(MMU_WRITE_DBG, &mmu_dev, - "MMU_FC = %08x\n", - mmu_state.fcode); - break; - case MMU_FA: - mmu_state.faddr = val; - sim_debug(MMU_WRITE_DBG, &mmu_dev, - "MMU_FADDR = %08x\n", - val); - break; - case MMU_CONF: - mmu_state.conf = val & 0x7f; - sim_debug(MMU_WRITE_DBG, &mmu_dev, - "MMU_CONF = %02x (M=%d R=%d $=%d PS=%d MCE=%d DCE=%d)\n", - val, - MMU_CONF_M, - MMU_CONF_R, - MMU_CONF_C, - MMU_CONF_PS, - MMU_CONF_MCE, - MMU_CONF_DCE); - break; - case MMU_VAR: - mmu_state.var = val; - sim_debug(MMU_WRITE_DBG, &mmu_dev, - "MMU_VAR = %08x\n", val); - if ((mmu_state.sdcl[SDC_IDX(val)] & SDC_VADDR_MASK) == - ((val >> 20) & SDC_VADDR_MASK)) { - sim_debug(MMU_CACHE_DBG, &mmu_dev, - "Flushing MMU SDC entry at index %d " - "(sdc_lo=%08x sdc_hi=%08x)\n", - SDC_IDX(val), - mmu_state.sdcl[SDC_IDX(val)], - mmu_state.sdch[SDC_IDX(val)]); - mmu_state.sdch[SDC_IDX(val)] &= ~SDC_G_MASK; - } - flush_pdc(val); - break; - case MMU_IDC: - sim_debug(MMU_WRITE_DBG, &mmu_dev, - "MMU_IDC = %08x\n", - val); - break; - case MMU_IDNR: - sim_debug(MMU_WRITE_DBG, &mmu_dev, - "MMU_IDNR = %08x\n", - val); - break; - case MMU_FIDNR: - sim_debug(MMU_WRITE_DBG, &mmu_dev, - "MMU_FIDNR = %08x\n", - val); - break; - case MMU_VR: - sim_debug(MMU_WRITE_DBG, &mmu_dev, - "MMU_VR = %08x\n", - val); - break; - default: - sim_debug(MMU_WRITE_DBG, &mmu_dev, - "UNHANDLED WRITE (entity=0x%x, index=0x%x, val=%08x)\n", - entity, index, val); - break; - } -} - -/* - * Update history bits in cache and memory. - */ -static t_stat mmu_update_history(uint32 va, uint8 r_acc, uint32 pdc_idx, t_bool fc) -{ - uint32 sd_hi, sd_lo, pd; - uint32 pd_addr; - t_bool update_sdc = TRUE; - - if (get_sdce(va, &sd_hi, &sd_lo) != SCPE_OK) { - update_sdc = FALSE; - } - - sd_lo = pread_w(SD_ADDR(va), BUS_PER); - sd_hi = pread_w(SD_ADDR(va) + 4, BUS_PER); - - if (MMU_CONF_M && r_acc == ACC_W && (mmu_state.sdcl[SDC_IDX(va)] & SDC_M_MASK) == 0) { - if (update_sdc) { - mmu_state.sdcl[SDC_IDX(va)] |= SDC_M_MASK; - } - - if (mmu_check_perm(SD_ACC(sd_lo), r_acc) != SCPE_OK) { - sim_debug(MMU_FAULT_DBG, &mmu_dev, - "MMU R&M Update Fault (M)\n"); - MMU_FAULT(MMU_F_RM_UPD); - return SCPE_NXM; - } - - pwrite_w(SD_ADDR(va), sd_lo | SD_M_MASK, BUS_PER); - } - - if (MMU_CONF_R && (mmu_state.sdcl[SDC_IDX(va)] & SDC_R_MASK) == 0) { - if (update_sdc) { - mmu_state.sdcl[SDC_IDX(va)] |= SDC_R_MASK; - } - - if (mmu_check_perm(SD_ACC(sd_lo), r_acc) != SCPE_OK) { - sim_debug(MMU_FAULT_DBG, &mmu_dev, - "MMU R&M Update Fault (R)\n"); - MMU_FAULT(MMU_F_RM_UPD); - return SCPE_NXM; - } - - pwrite_w(SD_ADDR(va), sd_lo | SD_R_MASK, BUS_PER); - } - - if (!SD_CONTIG(sd_lo)) { - pd_addr = SD_SEG_ADDR(sd_hi) + (PSL(va) * 4); - - if (r_acc == ACC_W && (mmu_state.pdcl[pdc_idx] & PDC_M_MASK) == 0) { - mmu_state.pdcl[pdc_idx] |= PDC_M_MASK; - pd = pread_w(pd_addr, BUS_PER); - pwrite_w(pd_addr, pd | PD_M_MASK, BUS_PER); - } - - if ((mmu_state.pdcl[pdc_idx] & PDC_R_MASK) == 0) { - mmu_state.pdcl[pdc_idx] |= PDC_R_MASK; - pd = pread_w(pd_addr, BUS_PER); - pwrite_w(pd_addr, pd | PD_R_MASK, BUS_PER); - } - } - - return SCPE_OK; -} - -/* - * Handle a Page Descriptor cache miss. - * - * - va is the virtual address for the PD. - * - r_acc is the requested access type. - * - fc is the fault check flag. - * - * If there was a miss when reading the SDC, TRUE will be returned in - * "sd_miss". The page descriptor will be returned in "pd", and the - * segment descriptor will be returned in "sd_lo" and "sd_hi". - * - * Returns SCPE_OK on success, SCPE_NXM on failure. If SCPE_NXM is - * returned, a failure code and fault address will be set in the - * appropriate registers. - * - * As always, the flag 'fc' may be set to FALSE to avoid certain - * types of fault checking. - * - * For detailed documentation, See: - * - * "WE 32201 Memory Management Unit Information Manual", AT&T Select - * Code 307-706, February 1987; Figure 2-18, pages 2-24 through 2-25. - */ -t_stat mmu_pdc_miss(uint32 va, uint8 r_acc, t_bool fc, - uint32 *pd, uint32 *pdc_idx) -{ - uint32 sd_ptr, sd_hi, sd_lo, pd_addr; - uint32 indirect_count = 0; - t_bool sdc_miss = FALSE; - - *pdc_idx = 0; - - /* If this was an instruction fetch, the actual requested level - * here will become "Instruction Fetch After Discontinuity" - * due to the page miss. */ - r_acc = (r_acc == ACC_IF ? ACC_IFAD : r_acc); - - /* We immediately do SSL bounds checking. The 'fc' flag is not - * checked because SSL out of bounds is a fatal error. */ - if (SSL(va) > SRAMB_LEN(va)) { - sim_debug(MMU_FAULT_DBG, &mmu_dev, - "SDT Length Fault. sramb_len=%x ssl=%x va=%08x\n", - SRAMB_LEN(va), SSL(va), va); - MMU_FAULT(MMU_F_SDTLEN); - return SCPE_NXM; - } - - /* This loop handles segment descriptor indirection (if any) */ - sd_ptr = SD_ADDR(va); - while (1) { - /* Try to find the SD in the cache */ - if (get_sdce(va, &sd_hi, &sd_lo) != SCPE_OK) { - /* This was a miss, so we need to load the SD out of - * memory. */ - sdc_miss = TRUE; - - sd_lo = pread_w(sd_ptr, BUS_PER); /* Control Bits */ - sd_hi = pread_w(sd_ptr + 4, BUS_PER); /* Address Bits */ - sim_debug(MMU_CACHE_DBG, &mmu_dev, - "SDC miss. Read sd_ptr=%08x sd_lo=%08x sd_hi=%08x va=%08x\n", - sd_ptr, sd_lo, sd_hi, va); - } - - if (!SD_VALID(sd_lo)) { - sim_debug(MMU_FAULT_DBG, &mmu_dev, - "Invalid Segment Descriptor. va=%08x sd_hi=%08x sd_lo=%08x\n", - va, sd_hi, sd_lo); - MMU_FAULT(MMU_F_INV_SD); - return SCPE_NXM; - } - - if (SD_INDIRECT(sd_lo)) { - if (++indirect_count > MAX_INDIRECTS) { - sim_debug(MMU_FAULT_DBG, &mmu_dev, - "Max Indirects Fault. va=%08x sd_hi=%08x sd_lo=%08x\n", - va, sd_hi, sd_lo); - MMU_FAULT(MMU_F_INDIRECT); - return SCPE_NXM; - } - - /* Any permission failure at this point is actually an MMU_F_MISS_MEM */ - if (mmu_check_perm(SD_ACC(sd_lo), r_acc) != SCPE_OK) { - sim_debug(MMU_FAULT_DBG, &mmu_dev, - "MMU Miss Processing Memory Fault (SD Access) (ckm=%d pd_acc=%02x r_acc=%02x)\n", - CPU_CM, SD_ACC(sd_lo), r_acc); - MMU_FAULT(MMU_F_MISS_MEM); - return SCPE_NXM; - } - - /* sd_hi is a pointer to a new segment descriptor */ - sd_ptr = sd_hi; - - sd_lo = pread_w(sd_ptr, BUS_PER); - sd_hi = pread_w(sd_ptr + 4, BUS_PER); - } else { - /* If it's not an indirection, we're done. */ - break; - } - } - - /* Fault if the segment descriptor P bit isn't set */ - if (!SD_PRESENT(sd_lo)) { - /* If the C bit is set, this is a SEGMENT NOT PRESENT - fault; otherwise, it's a PDT NOT PRESENT fault. */ - if (SD_CONTIG(sd_lo)) { - sim_debug(MMU_FAULT_DBG, &mmu_dev, - "Segment Not Present. va=%08x\n", - va); - MMU_FAULT(MMU_F_SEG_NOT_PRES); - return SCPE_NXM; - } else { - sim_debug(MMU_FAULT_DBG, &mmu_dev, - "PDT Not Present. va=%08x\n", - va); - MMU_FAULT(MMU_F_PDT_NOT_PRES); - return SCPE_NXM; - } - } - - /* Check to see if the segment is too long. */ - if (SD_CONTIG(sd_lo)) { - if (PSL(va) > SD_MAX_OFF(sd_lo)) { - sim_debug(MMU_FAULT_DBG, &mmu_dev, - "Segment Offset Fault. va=%08x\n", - va); - MMU_FAULT(MMU_F_SEG_OFFSET); - return SCPE_NXM; - } - } else { - if ((va & 0x1ffff) > MAX_SEG_OFF(sd_lo)) { - sim_debug(MMU_FAULT_DBG, &mmu_dev, - "PDT Length Fault. va=%08x max_seg_off=0x%x\n", - va, MAX_SEG_OFF(sd_lo)); - MMU_FAULT(MMU_F_PDTLEN); - return SCPE_NXM; - } - } - - /* Either load or construct the PD */ - if (SD_CONTIG(sd_lo)) { - /* TODO: VERIFY */ - if (mmu_check_perm(SD_ACC(sd_lo), r_acc) != SCPE_OK) { - sim_debug(MMU_FAULT_DBG, &mmu_dev, - "[AFTER DISCONTINUITY] Access to Memory Denied (va=%08x ckm=%d pd_acc=%02x r_acc=%02x)\n", - va, CPU_CM, SD_ACC(sd_lo), r_acc); - MMU_FAULT(MMU_F_ACC); - return SCPE_NXM; - } - - /* We have to construct a PD for this SD. */ - *pd = (((sd_hi & pd_addr_masks[MMU_CONF_PS]) + PSL_C(va)) | - ((sd_lo & 0x800000) >> 18) | /* Copy R bit */ - ((sd_lo & 0x400000) >> 21) | /* Copy M bit */ - 1); /* P bit */ - - sim_debug(MMU_CACHE_DBG, &mmu_dev, - "Contiguous Segment. Constructing PD. PSIZE=%d va=%08x sd_hi=%08x sd_lo=%08x pd=%08x\n", - MMU_CONF_PS, va, sd_hi, sd_lo, *pd); - } else { - /* We can find the PD in main memory */ - pd_addr = SD_SEG_ADDR(sd_hi) + (PSL(va) * 4); - - *pd = pread_w(pd_addr, BUS_PER); - - sim_debug(MMU_CACHE_DBG, &mmu_dev, - "Paged Segment. Loaded PD. va=%08x sd_hi=%08x sd_lo=%08x pd_addr=%08x pd=%08x\n", - va, sd_hi, sd_lo, pd_addr, *pd); - } - - if (r_acc == ACC_W && (*pd & PD_W_MASK)) { - sim_debug(MMU_FAULT_DBG, &mmu_dev, - "Page Write Fault, pd=%08x va=%08x\n", - *pd, va); - MMU_FAULT(MMU_F_PW); - return SCPE_NXM; - } - - if ((*pd & PD_P_MASK) != PD_P_MASK) { - sim_debug(MMU_FAULT_DBG, &mmu_dev, - "Page Not Present Fault. pd=%08x va=%08x\n", - *pd, va); - MMU_FAULT(MMU_F_PAGE_NOT_PRES); - return SCPE_NXM; - } - - /* Finally, cache the PD */ - - if (sdc_miss) { - put_sdce(va, sd_hi, sd_lo); - } - - *pdc_idx = put_pdce(va, sd_lo, *pd); - - return SCPE_OK; -} - -/* - * Translate a virtual address into a physical address. - * - * Note that unlike 'mmu_xlate_addr', this function will _not_ abort - * on failure. The decoded physical address is returned in "pa". If - * the argument "fc" is FALSE, this function will bypass: - * - * - Access flag checks, - * - Cache insertion, - * - Setting MMU fault registers, - * - Modifying segment and page descriptor bits. - * - * In other words, setting "fc" to FALSE does the minimum work - * necessary to translate a virtual address without changing any MMU - * state. The primary use case for this flag is to provide simulator - * debugging access to memory translation while avoiding that access - * undermining the currently running operating system (if any). - * - * This function returns SCPE_OK if translation succeeded, and - * SCPE_NXM if translation failed. - * - */ -t_stat mmu_decode_va(uint32 va, uint8 r_acc, t_bool fc, uint32 *pa) -{ - uint32 pd, pdc_idx; - uint8 pd_acc; - t_stat succ; - - /* - * If the MMU is disabled, virtual == physical. - */ - if (!mmu_state.enabled) { - *pa = va; - return SCPE_OK; - } - - /* - * 1. Check PDC for an entry. - */ - if (get_pdce(va, &pd, &pd_acc, &pdc_idx) == SCPE_OK) { - if (mmu_check_perm(pd_acc, r_acc) != SCPE_OK) { - sim_debug(MMU_FAULT_DBG, &mmu_dev, - "Access to Memory Denied (va=%08x ckm=%d pd_acc=%02x r_acc=%02x)\n", - va, CPU_CM, pd_acc, r_acc); - MMU_FAULT(MMU_F_ACC); - return SCPE_NXM; - } - - if (r_acc == ACC_W && (pd & PD_W_MASK)) { - sim_debug(MMU_FAULT_DBG, &mmu_dev, - "Page Write Fault, pd=%08x va=%08x\n", - pd, va); - MMU_FAULT(MMU_F_PW); - return SCPE_NXM; - } - } else { - /* Do miss processing. This will cache the PD if necessary. */ - succ = mmu_pdc_miss(va, r_acc, fc, &pd, &pdc_idx); - if (succ != SCPE_OK) { - return succ; - } - } - - /* - * 2. Update history bits - */ - succ = mmu_update_history(va, r_acc, pdc_idx, fc); - if (succ != SCPE_OK) { - return succ; - } - - /* - * 3. Translation from Page Descriptor - */ - *pa = PD_ADDR(pd) + POT(va); - - sim_debug(MMU_TRACE_DBG, &mmu_dev, - "XLATE DONE. r_acc=%d va=%08x pa=%08x\n", - r_acc, va, *pa); - - return SCPE_OK; -} - -/* - * Translate a virtual address into a physical address. - * - * This function returns the translated virtual address, and aborts - * without returning if translation failed. - */ -uint32 mmu_xlate_addr(uint32 va, uint8 r_acc) -{ - uint32 pa; - t_stat succ; - - succ = mmu_decode_va(va, r_acc, TRUE, &pa); - - if (succ == SCPE_OK) { - mmu_state.var = va; - return pa; - } else { - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); - return 0; - } -} - -/* - * Enable the MMU and allow virtual address translation. - */ -void mmu_enable() -{ - mmu_state.enabled = TRUE; -} - -/* - * Disable the MMU. All memory access will be through physical addresses only. - */ -void mmu_disable() -{ - mmu_state.enabled = FALSE; -} - -CONST char *mmu_description(DEVICE *dptr) -{ - return "WE32201 MMU"; -} - -/* - * Display the segment descriptor cache - */ -t_stat mmu_show_sdc(FILE *st, UNIT *uptr, int32 val, CONST void *desc) -{ - uint32 sd_lo, sd_hi, base, pages, i; - - fprintf(st, "\nSegment Descriptor Cache\n\n"); - - fprintf(st, "start sdc (lo) sdc (hi) sd (lo) sd (hi) C/P seg start pages\n"); - fprintf(st, "-------- -------- -------- -------- -------- --- --------- -----\n"); - - for (i = 0; i < MMU_SDCS; i++) { - sd_lo = SDCE_TO_SDL(mmu_state.sdch[i], mmu_state.sdcl[i]); - sd_hi = SDCE_TO_SDH(mmu_state.sdch[i]); - base = ((mmu_state.sdcl[i] & 0xfff) << 20) | ((i & 7) << 17); - pages = ((sd_lo & SD_MAX_OFF_MASK) >> 18) + 1; - - fprintf(st, "%08x %08x %08x %08x %08x %s %08x %d\n", - base, - mmu_state.sdcl[i], - mmu_state.sdch[i], - sd_lo, sd_hi, - SD_CONTIG(sd_lo) ? "C" : "P", - sd_hi & SD_ADDR_MASK, - pages); - - } - - return SCPE_OK; -} - -t_stat mmu_show_pdc(FILE *st, UNIT *uptr, int32 val, CONST void *desc) -{ - uint32 i, pdc_hi, pdc_lo; - - fprintf(st, "\nPage Descriptor Cache\n\n"); - fprintf(st, "IDX pdc (hi) pdc (lo) U G C W vaddr pd addr\n"); - fprintf(st, "---- -------- -------- - - - - -------- --------\n"); - - for (i = 0; i < MMU_PDCS; i++) { - pdc_hi = mmu_state.pdch[i]; - pdc_lo = mmu_state.pdcl[i]; - - fprintf(st, "%02d %08x %08x %s %s %s %s %08x %08x\n", - i, - pdc_hi, pdc_lo, - pdc_hi & PDC_U_MASK ? "U" : " ", - pdc_hi & PDC_G_MASK ? "G" : " ", - pdc_hi & PDC_C_MASK ? "C" : "P", - pdc_lo & PDC_W_MASK ? "W" : " ", - (pdc_hi & PDC_TAG_MASK) << 6, - PDCE_TO_PD(pdc_lo)); - } - - return SCPE_OK; -} - -/* - * Display the segment table for a section. - */ -t_stat mmu_show_sdt(FILE *st, UNIT *uptr, int32 val, CONST void *desc) -{ - uint32 addr, len, sd_lo, sd_hi, base, pages, i; - uint8 sec; - char *cptr = (char *) desc; - t_stat result; - - if (cptr == NULL) { - fprintf(st, "Missing section number\n"); - return SCPE_ARG; - } - - sec = (uint8) get_uint(cptr, 10, 3, &result); - if (result != SCPE_OK) { - fprintf(st, "Please specify a section from 0-3\n"); - return SCPE_ARG; - } - - addr = mmu_state.sec[sec].addr; - len = mmu_state.sec[sec].len + 1; - - fprintf(st, "\nSection %d SDT\n\n", sec); - fprintf(st, "start end sd (lo) sd (hi) C/P seg start pages\n"); - fprintf(st, "-------- -------- -------- -------- --- --------- ------\n"); - - for (i = 0; i < len; i++) { - sd_lo = pread_w(addr + (i * 8), BUS_PER) & SD_RES_MASK; - sd_hi = pread_w(addr + (i * 8) + 4, BUS_PER); - base = (sec << 14 | i << 1) << 16; - pages = ((sd_lo & SD_MAX_OFF_MASK) >> 18) + 1; - - if (SD_VALID(sd_lo)) { - fprintf(st, "%08x-%08x %08x %08x %s %08x %d\n", - base, - base + (((sd_lo & SD_MAX_OFF_MASK) >> 15) * 2048) - 1, - sd_lo, sd_hi, - SD_CONTIG(sd_lo) ? "C" : "P", - sd_hi & SD_ADDR_MASK, - pages); - } - } - - return SCPE_OK; -} +/* 3b2_rev3_mmu.c: WE32201 MMU + + Copyright (c) 2020-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + + +#include "3b2_cpu.h" +#include "3b2_csr.h" +#include "3b2_mem.h" +#include "3b2_mmu.h" + +UNIT mmu_unit = { UDATA(NULL, 0, 0) }; + +MMU_STATE mmu_state; + +REG mmu_reg[] = { + { HRDATAD (ENABLE, mmu_state.enabled, 1, "Enabled?") }, + { HRDATAD (CONFIG, mmu_state.conf, 32, "Configuration") }, + { HRDATAD (VAR, mmu_state.var, 32, "Virtual Address") }, + { HRDATAD (FCODE, mmu_state.fcode, 32, "Fault Code") }, + { HRDATAD (FADDR, mmu_state.faddr, 32, "Fault Address") }, + { BRDATA (SDCL, mmu_state.sdcl, 16, 32, MMU_SDCS) }, + { BRDATA (SDCH, mmu_state.sdch, 16, 32, MMU_SDCS) }, + { BRDATA (PDCL, mmu_state.pdcl, 16, 32, MMU_PDCS) }, + { BRDATA (PDCH, mmu_state.pdch, 16, 32, MMU_PDCS) }, + { BRDATA (SRAMA, mmu_state.sra, 16, 32, MMU_SRS) }, + { BRDATA (SRAMB, mmu_state.srb, 16, 32, MMU_SRS) }, + { NULL } +}; + +#define MMU_EXEC_DBG 1 +#define MMU_TRACE_DBG 1 << 1 +#define MMU_CACHE_DBG 1 << 2 +#define MMU_FAULT_DBG 1 << 3 +#define MMU_READ_DBG 1 << 4 +#define MMU_WRITE_DBG 1 << 5 + +static DEBTAB mmu_debug[] = { + { "EXEC", MMU_EXEC_DBG, "Simple execution" }, + { "CACHE", MMU_CACHE_DBG, "Cache trace" }, + { "TRACE", MMU_TRACE_DBG, "Translation trace" }, + { "FAULT", MMU_FAULT_DBG, "Faults" }, + { "READ", MMU_READ_DBG, "Peripheral Read"}, + { "WRITE", MMU_WRITE_DBG, "Peripheral Write"}, + { NULL } +}; + +MTAB mmu_mod[] = { + { MTAB_XTD|MTAB_VDV|MTAB_NMO|MTAB_SHP, 0, "SDT", NULL, + NULL, &mmu_show_sdt, NULL, "Display SDT for section n [0-3]" }, + { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "SDC", NULL, + NULL, &mmu_show_sdc, NULL, "Display SD Cache" }, + { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "PDC", NULL, + NULL, &mmu_show_pdc, NULL, "Display PD Cache" }, + { 0 } +}; + +DEVICE mmu_dev = { + "MMU", /* name */ + &mmu_unit, /* units */ + mmu_reg, /* registers */ + mmu_mod, /* modifiers */ + 1, /* #units */ + 16, /* address radix */ + 8, /* address width */ + 4, /* address incr */ + 16, /* data radix */ + 32, /* data width */ + NULL, /* examine routine */ + NULL, /* deposit routine */ + &mmu_init, /* reset routine */ + NULL, /* boot routine */ + NULL, /* attach routine */ + NULL, /* detach routine */ + NULL, /* context */ + DEV_DEBUG, /* flags */ + 0, /* debug control flags */ + mmu_debug, /* debug flag names */ + NULL, /* memory size change */ + NULL, /* logical name */ + NULL, /* help routine */ + NULL, /* attach help routine */ + NULL, /* help context */ + &mmu_description /* device description */ +}; + +/* + * Each bitmask corresponds to the pattern of bits used for the tag in + * the first word of a segment descriptor in the cache. The outer + * index corresponds to mode (0=Single-Context Mode, 1=Multi-Context + * Mode), the inner index corresponds to page size (0=2kB, 1=4kB, + * 2=8kB, 3=undefined) + */ +uint32 pdc_tag_masks[2][4] = { + {0x43ffffe0, 0x43ffffc0, 0x43ffff80, 0}, + {0x7fffffe0, 0x7fffffc0, 0x7fffff80, 0}, +}; + +/* + * Bitmasks for generating page addresses for contiguous segments on + * cache miss. + */ +uint32 pd_addr_masks[4] = { + 0xfffff800, 0xfffff000, 0xffffe000, 0 +}; + +uint32 pd_psl_masks[4] = { + 0x1f800, 0x1f000, 0x1e000, 0 +}; + +/* Masks used when searching the PD cache for a matching tag. */ +#define PDC_TAG_MASK (pdc_tag_masks[MMU_CONF_MCE][MMU_CONF_PS]) + +/* Mask off the bottom 10 bits of virtual address when generating PD + * cache tags */ +#define VA_TO_TAG_MASK 0xfffff800 + +/* + * Macros used to generate PD cache tags from virtual-addresses + */ +#define PDC_MTAG(VA) ((((VA) & VA_TO_TAG_MASK) >> 6) | \ + (mmu_state.cidnr[SID(VA)] << 26) | \ + (1 << 30)) + +#define PDC_STAG(VA) ((((VA) & VA_TO_TAG_MASK) >> 6) | \ + (1 << 30)) + +#define PDC_TAG(VA) (MMU_CONF_MCE ? PDC_MTAG(VA) : PDC_STAG(VA)) + +/* + * Retrieve a Segment Descriptor from the SD cache. The Segment + * Descriptor Cache entry is returned in sd_lo and sd_hi, if found. + * + * If there is a cache hit, this function returns SCPE_OK. + * If there is a cache miss, this function returns SCPE_NXM. + */ +static t_stat get_sdce(uint32 va, uint32 *sd_hi, uint32 *sd_lo) +{ + uint32 hi, lo, va_tag, sdc_tag; + + hi = mmu_state.sdch[SDC_IDX(va)]; + lo = mmu_state.sdcl[SDC_IDX(va)]; + va_tag = (va >> 20) & 0xfff; + sdc_tag = lo & 0xfff; + + if ((hi & SDC_G_MASK) && (va_tag == sdc_tag)) { + *sd_hi = SDCE_TO_SDH(hi); + *sd_lo = SDCE_TO_SDL(hi,lo); + return SCPE_OK; + } + + return SCPE_NXM; +} + +/* + * Insert a Segment Descriptor into the SD cache. + */ +static void put_sdce(uint32 va, uint32 sd_hi, uint32 sd_lo) +{ + uint8 ci = SDC_IDX(va); + + mmu_state.sdch[ci] = SD_TO_SDCH(sd_hi, sd_lo); + mmu_state.sdcl[ci] = SD_TO_SDCL(sd_lo, va); + + sim_debug(MMU_CACHE_DBG, &mmu_dev, + "CACHED SD AT IDX %d. va=%08x sd_hi=%08x sd_lo=%08x sdc_hi=%08x sdc_lo=%08x\n", + ci, va, sd_hi, sd_lo, mmu_state.sdch[ci], mmu_state.sdcl[ci]); + +} + +/* + * Update the "Used" bit in the Page Descriptor cache for the given + * entry. + */ +static void set_u_bit(uint32 index) +{ + uint32 i; + + mmu_state.pdch[index] |= PDC_U_MASK; + + /* Check to see if all U bits have been set. If so, the cache will + * need to be flushed on the next put */ + for (i = 0; i < MMU_PDCS; i++) { + if ((mmu_state.pdch[i] & PDC_U_MASK) == 0) { + return; + } + } + + mmu_state.flush_u = TRUE; +} + +/* + * Retrieve a Page Descriptor Cache Entry from the Page Descriptor + * Cache. + */ +static t_stat get_pdce(uint32 va, uint32 *pd, uint8 *pd_acc, uint32 *pdc_idx) +{ + uint32 i, key_tag, target_tag; + + *pdc_idx = 0; + + /* This is a fully associative cache, so we must scan for an entry + with the correct tag. */ + key_tag = PDC_TAG(va) & PDC_TAG_MASK; + + for (i = 0; i < MMU_PDCS; i++) { + target_tag = mmu_state.pdch[i] & PDC_TAG_MASK; + if (target_tag == key_tag) { + /* Construct the PD from the cached version */ + *pd = PDCE_TO_PD(mmu_state.pdcl[i]); + *pd_acc = (mmu_state.pdcl[i] >> 24) & 0xff; + *pdc_idx = i; + sim_debug(MMU_TRACE_DBG, &mmu_dev, + "PDC HIT. va=%08x idx=%d tag=%03x pd=%08x pdcl=%08x pdch=%08x\n", + va, i, key_tag, *pd, + mmu_state.pdcl[i], mmu_state.pdch[i]); + set_u_bit(i); + return SCPE_OK; + } + } + + sim_debug(MMU_CACHE_DBG, &mmu_dev, + "PDC MISS. va=%08x tag=%03x\n", + va, key_tag); + + return SCPE_NXM; +} + +/* + * Cache a Page Descriptor in the specified slot. + */ +static void put_pdce_at(uint32 va, uint32 sd_lo, uint32 pd, uint32 slot) +{ + mmu_state.pdcl[slot] = PD_TO_PDCL(pd, sd_lo); + mmu_state.pdch[slot] = VA_TO_PDCH(va, sd_lo); + sim_debug(MMU_CACHE_DBG, &mmu_dev, + "Caching MMU PDC entry at index %d (pdc_hi=%08x pdc_lo=%08x va=%08x)\n", + slot, mmu_state.pdch[slot], mmu_state.pdcl[slot], va); + set_u_bit(slot); + mmu_state.last_cached = slot; +} + +/* + * Cache a Page Descriptor in the first available, least recently used + * slot. + */ +static uint32 put_pdce(uint32 va, uint32 sd_lo, uint32 pd) +{ + uint32 i; + + /* + * If all the U bits have been set, flush them all EXCEPT the most + * recently cached entry. + */ + if (mmu_state.flush_u) { + sim_debug(MMU_CACHE_DBG, &mmu_dev, + "Flushing PDC U bits on all-set condition.\n"); + mmu_state.flush_u = FALSE; + for (i = 0; i < MMU_PDCS; i++) { + if (i != mmu_state.last_cached) { + mmu_state.pdch[i] &= ~(PDC_U_MASK); + } + } + } + + /* TODO: This can be done in one pass! Clean it up */ + + /* Cache the Page Descriptor in the first slot with a cleared G + * bit */ + for (i = 0; i < MMU_PDCS; i++) { + if ((mmu_state.pdch[i] & PDC_G_MASK) == 0) { + put_pdce_at(va, sd_lo, pd, i); + return i; + } + } + + /* If ALL slots had their G bit set, find the first slot with a + cleared U bit */ + for (i = 0; i < MMU_PDCS; i++) { + if ((mmu_state.pdch[i] & PDC_U_MASK) == 0) { + put_pdce_at(va, sd_lo, pd, i); + return i; + } + } + + /* This should never happen, since if all U bits become set, they + * are automatically all cleared */ + stop_reason = STOP_MMU; + + return 0; +} + +/* + * Flush the cache for an individual virtual address. + */ +static void flush_pdc(uint32 va) +{ + uint32 i, j, key_tag, target_tag; + + /* Flush the PDC. This is a fully associative cache, so we must + * scan for an entry with the correct tag. */ + + key_tag = PDC_TAG(va) & PDC_TAG_MASK; + + for (i = 0; i < MMU_PDCS; i++) { + target_tag = mmu_state.pdch[i] & PDC_TAG_MASK; + if (target_tag == key_tag) { + sim_debug(MMU_CACHE_DBG, &mmu_dev, + "Flushing MMU PDC entry pdc_lo=%08x pdc_hi=%08x index %d (va=%08x)\n", + mmu_state.pdcl[i], + mmu_state.pdch[i], + i, + va); + if (mmu_state.pdch[i] & PDC_C_MASK) { + sim_debug(MMU_CACHE_DBG, &mmu_dev, + "Flushing MMU PDC entry: CONTIGUOUS\n"); + /* If this PD came from a contiguous SD, we need to + * flush ALL entries belonging to the same SD. All + * pages within the same segment have the same upper + * 11 bits. */ + for (j = 0; j < MMU_PDCS; j++) { + if ((mmu_state.pdch[j] & 0x3ffc000) == + (mmu_state.pdch[i] & 0x3ffc000)) { + mmu_state.pdch[j] &= ~(PDC_G_MASK|PDC_U_MASK); + } + } + } else { + /* Otherwise, just flush the one entry */ + mmu_state.pdch[i] &= ~(PDC_G_MASK|PDC_U_MASK); + } + return; + } + } + + sim_debug(MMU_CACHE_DBG, &mmu_dev, + "Flushing MMU PDC entry: NOT FOUND (va=%08x key_tag=%08x)\n", + va, key_tag); + +} + +/* + * Flush all entries in both SDC and PDC. + */ +static void flush_caches() +{ + uint32 i; + + sim_debug(MMU_CACHE_DBG, &mmu_dev, + "Flushing MMU PDC and SDC\n"); + + for (i = 0; i < MMU_SDCS; i++) { + mmu_state.sdch[i] &= ~SDC_G_MASK; + } + + for (i = 0; i < MMU_PDCS; i++) { + mmu_state.pdch[i] &= ~PDC_G_MASK; + mmu_state.pdch[i] &= ~PDC_U_MASK; + } +} + +/* + * Check permissions for a set of permission flags and an access type. + * + * Return SCPE_OK if permission is granted, SCPE_NXM if permission is + * not allowed. + */ +static t_stat mmu_check_perm(uint8 flags, uint8 r_acc) +{ + switch(MMU_PERM(flags)) { + case 0: /* No Access */ + return SCPE_NXM; + case 1: /* Exec Only */ + if (r_acc != ACC_IF && + r_acc != ACC_IFAD) { + return SCPE_NXM; + } + return SCPE_OK; + case 2: /* Read / Execute */ + if (r_acc != ACC_IF && + r_acc != ACC_IFAD && + r_acc != ACC_OF && + r_acc != ACC_AF && + r_acc != ACC_MT) { + return SCPE_NXM; + } + return SCPE_OK; + default: + return SCPE_OK; + } +} + +/* + * Initialize the MMU device + */ +t_stat mmu_init(DEVICE *dptr) +{ + flush_caches(); + return SCPE_OK; +} + +/* + * Memory-mapped (peripheral mode) read of the MMU device + */ +uint32 mmu_read(uint32 pa, size_t size) +{ + uint8 entity, index; + uint32 data = 0; + + /* Register entity */ + entity = ((uint8)(pa >> 8)) & 0xf; + + /* Index into entity */ + index = (uint8)((pa >> 2) & 0x1f); + + switch (entity) { + case MMU_SDCL: + data = mmu_state.sdcl[index]; + sim_debug(MMU_READ_DBG, &mmu_dev, + "MMU_SDCL[%d] = %08x\n", + index, data); + break; + case MMU_SDCH: + data = mmu_state.sdch[index]; + sim_debug(MMU_READ_DBG, &mmu_dev, + "MMU_SDCH[%d] = %08x\n", + index, data); + break; + case MMU_PDCL: + data = mmu_state.pdcl[index]; + sim_debug(MMU_READ_DBG, &mmu_dev, + "MMU_PDCL[%d] = %08x\n", + index, data); + break; + case MMU_PDCH: + data = mmu_state.pdch[index]; + sim_debug(MMU_READ_DBG, &mmu_dev, + "MMU_PDCH[%d] = %08x\n", + index, data); + break; + case MMU_SRAMA: + data = mmu_state.sra[index]; + sim_debug(MMU_READ_DBG, &mmu_dev, + "MMU_SRAMA[%d] = %08x\n", + index, data); + break; + case MMU_SRAMB: + data = mmu_state.srb[index]; + sim_debug(MMU_READ_DBG, &mmu_dev, + "MMU_SRAMB[%d] = %08x\n", + index, data); + break; + case MMU_FC: + data = mmu_state.fcode; + sim_debug(MMU_READ_DBG, &mmu_dev, + "MMU_FC = %08x\n", + data); + break; + case MMU_FA: + data = mmu_state.faddr; + sim_debug(MMU_READ_DBG, &mmu_dev, + "MMU_FA = %08x\n", + data); + break; + case MMU_CONF: + data = mmu_state.conf; + sim_debug(MMU_READ_DBG, &mmu_dev, + "MMU_CONF = %02x (M=%d R=%d $=%d PS=%d MCE=%d DCE=%d)\n", + data, + MMU_CONF_M, + MMU_CONF_R, + MMU_CONF_C, + MMU_CONF_PS, + MMU_CONF_MCE, + MMU_CONF_DCE); + break; + case MMU_VAR: + data = mmu_state.var; + sim_debug(MMU_READ_DBG, &mmu_dev, + "MMU_VAR = %08x\n", + data); + break; + case MMU_IDC: + /* TODO: Implement */ + data = 0; + sim_debug(MMU_READ_DBG, &mmu_dev, + "MMU_IDC\n"); + break; + case MMU_IDNR: + /* TODO: Implement */ + data = 0; + sim_debug(MMU_READ_DBG, &mmu_dev, + "MMU_IDNR\n"); + break; + case MMU_FIDNR: + /* TODO: Implement */ + data = 0; + sim_debug(MMU_READ_DBG, &mmu_dev, + "MMU_FIDNR\n"); + break; + case MMU_VR: + data = MMU_REV3_VER; + sim_debug(MMU_READ_DBG, &mmu_dev, + "MMU_VR = 0x23\n"); + break; + default: + sim_debug(MMU_READ_DBG, &mmu_dev, + "Invalid MMU register: pa=%08x\n", + pa); + CSRBIT(CSRTIMO, TRUE); + break; + } + + return data; +} + +void mmu_write(uint32 pa, uint32 val, size_t size) +{ + uint32 index, entity, i; + + /* Register entity */ + entity = ((uint8)(pa >> 8)) & 0xf; + + /* Index into entity */ + index = (uint8)((pa >> 2) & 0x1f); + + switch (entity) { + case MMU_SDCL: + sim_debug(MMU_WRITE_DBG, &mmu_dev, + "MMU_SDCL[%d] = %08x\n", + index, val); + mmu_state.sdcl[index] = val; + break; + case MMU_SDCH: + sim_debug(MMU_WRITE_DBG, &mmu_dev, + "MMU_SDCH[%d] = %08x\n", + index, val); + mmu_state.sdch[index] = val; + break; + case MMU_PDCL: + sim_debug(MMU_WRITE_DBG, &mmu_dev, + "MMU_PDCL[%d] = %08x\n", + index, val); + mmu_state.pdcl[index] = val; + break; + case MMU_PDCH: + sim_debug(MMU_WRITE_DBG, &mmu_dev, + "MMU_PDCH[%d] = %08x\n", + index, val); + mmu_state.pdch[index] = val; + break; + case MMU_FDCR: + sim_debug(MMU_WRITE_DBG, &mmu_dev, + "MMU_FDCR\n"); + /* Data cache is not implemented */ + break; + case MMU_SRAMA: + index = index & 3; + sim_debug(MMU_WRITE_DBG, &mmu_dev, + "MMU_SRAMA[%d] = %08x\n", + index, val); + mmu_state.sra[index] = val; + mmu_state.sec[index].addr = val & 0xfffffffc; + + /* Flush all SDC cache entries for this section */ + for (i = 0; i < MMU_SDCS; i++) { + if (((mmu_state.sdcl[i] >> 10) & 0x3) == index) { + sim_debug(MMU_CACHE_DBG, &mmu_dev, + "Flushing MMU SDC entry at index %d " + "(sdc_lo=%08x sdc_hi=%08x)\n", + i, mmu_state.sdcl[i], mmu_state.sdch[i]); + mmu_state.sdch[i] &= ~(SDC_G_MASK); + } + } + + /* Flush all PDC cache entries for this section */ + for (i = 0; i < MMU_PDCS; i++) { + if (((mmu_state.pdch[i] >> 24) & 0x3) == index) { + mmu_state.pdch[i] &= ~(PDC_G_MASK); + } + } + break; + case MMU_SRAMB: + index = index & 3; + mmu_state.srb[index] = val; + mmu_state.sec[index].len = (val >> 10) & 0x1fff; + /* We do not flush the cache on writing SRAMB */ + sim_debug(MMU_WRITE_DBG, &mmu_dev, + "MMU_SRAMB[%d] length=%04x (%d segments)\n", + index, + mmu_state.sec[index].len, + mmu_state.sec[index].len + 1); + break; + case MMU_FC: + /* Set a default value */ + mmu_state.fcode = (((CPU_CM) << 5) | (0xa << 7)); + sim_debug(MMU_WRITE_DBG, &mmu_dev, + "MMU_FC = %08x\n", + mmu_state.fcode); + break; + case MMU_FA: + mmu_state.faddr = val; + sim_debug(MMU_WRITE_DBG, &mmu_dev, + "MMU_FADDR = %08x\n", + val); + break; + case MMU_CONF: + mmu_state.conf = val & 0x7f; + sim_debug(MMU_WRITE_DBG, &mmu_dev, + "MMU_CONF = %02x (M=%d R=%d $=%d PS=%d MCE=%d DCE=%d)\n", + val, + MMU_CONF_M, + MMU_CONF_R, + MMU_CONF_C, + MMU_CONF_PS, + MMU_CONF_MCE, + MMU_CONF_DCE); + break; + case MMU_VAR: + mmu_state.var = val; + sim_debug(MMU_WRITE_DBG, &mmu_dev, + "MMU_VAR = %08x\n", val); + if ((mmu_state.sdcl[SDC_IDX(val)] & SDC_VADDR_MASK) == + ((val >> 20) & SDC_VADDR_MASK)) { + sim_debug(MMU_CACHE_DBG, &mmu_dev, + "Flushing MMU SDC entry at index %d " + "(sdc_lo=%08x sdc_hi=%08x)\n", + SDC_IDX(val), + mmu_state.sdcl[SDC_IDX(val)], + mmu_state.sdch[SDC_IDX(val)]); + mmu_state.sdch[SDC_IDX(val)] &= ~SDC_G_MASK; + } + flush_pdc(val); + break; + case MMU_IDC: + sim_debug(MMU_WRITE_DBG, &mmu_dev, + "MMU_IDC = %08x\n", + val); + break; + case MMU_IDNR: + sim_debug(MMU_WRITE_DBG, &mmu_dev, + "MMU_IDNR = %08x\n", + val); + break; + case MMU_FIDNR: + sim_debug(MMU_WRITE_DBG, &mmu_dev, + "MMU_FIDNR = %08x\n", + val); + break; + case MMU_VR: + sim_debug(MMU_WRITE_DBG, &mmu_dev, + "MMU_VR = %08x\n", + val); + break; + default: + sim_debug(MMU_WRITE_DBG, &mmu_dev, + "UNHANDLED WRITE (entity=0x%x, index=0x%x, val=%08x)\n", + entity, index, val); + break; + } +} + +/* + * Update history bits in cache and memory. + */ +static t_stat mmu_update_history(uint32 va, uint8 r_acc, uint32 pdc_idx, t_bool fc) +{ + uint32 sd_hi, sd_lo, pd; + uint32 pd_addr; + t_bool update_sdc = TRUE; + + if (get_sdce(va, &sd_hi, &sd_lo) != SCPE_OK) { + update_sdc = FALSE; + } + + sd_lo = pread_w(SD_ADDR(va), BUS_PER); + sd_hi = pread_w(SD_ADDR(va) + 4, BUS_PER); + + if (MMU_CONF_M && r_acc == ACC_W && (mmu_state.sdcl[SDC_IDX(va)] & SDC_M_MASK) == 0) { + if (update_sdc) { + mmu_state.sdcl[SDC_IDX(va)] |= SDC_M_MASK; + } + + if (mmu_check_perm(SD_ACC(sd_lo), r_acc) != SCPE_OK) { + sim_debug(MMU_FAULT_DBG, &mmu_dev, + "MMU R&M Update Fault (M)\n"); + MMU_FAULT(MMU_F_RM_UPD); + return SCPE_NXM; + } + + pwrite_w(SD_ADDR(va), sd_lo | SD_M_MASK, BUS_PER); + } + + if (MMU_CONF_R && (mmu_state.sdcl[SDC_IDX(va)] & SDC_R_MASK) == 0) { + if (update_sdc) { + mmu_state.sdcl[SDC_IDX(va)] |= SDC_R_MASK; + } + + if (mmu_check_perm(SD_ACC(sd_lo), r_acc) != SCPE_OK) { + sim_debug(MMU_FAULT_DBG, &mmu_dev, + "MMU R&M Update Fault (R)\n"); + MMU_FAULT(MMU_F_RM_UPD); + return SCPE_NXM; + } + + pwrite_w(SD_ADDR(va), sd_lo | SD_R_MASK, BUS_PER); + } + + if (!SD_CONTIG(sd_lo)) { + pd_addr = SD_SEG_ADDR(sd_hi) + (PSL(va) * 4); + + if (r_acc == ACC_W && (mmu_state.pdcl[pdc_idx] & PDC_M_MASK) == 0) { + mmu_state.pdcl[pdc_idx] |= PDC_M_MASK; + pd = pread_w(pd_addr, BUS_PER); + pwrite_w(pd_addr, pd | PD_M_MASK, BUS_PER); + } + + if ((mmu_state.pdcl[pdc_idx] & PDC_R_MASK) == 0) { + mmu_state.pdcl[pdc_idx] |= PDC_R_MASK; + pd = pread_w(pd_addr, BUS_PER); + pwrite_w(pd_addr, pd | PD_R_MASK, BUS_PER); + } + } + + return SCPE_OK; +} + +/* + * Handle a Page Descriptor cache miss. + * + * - va is the virtual address for the PD. + * - r_acc is the requested access type. + * - fc is the fault check flag. + * + * If there was a miss when reading the SDC, TRUE will be returned in + * "sd_miss". The page descriptor will be returned in "pd", and the + * segment descriptor will be returned in "sd_lo" and "sd_hi". + * + * Returns SCPE_OK on success, SCPE_NXM on failure. If SCPE_NXM is + * returned, a failure code and fault address will be set in the + * appropriate registers. + * + * As always, the flag 'fc' may be set to FALSE to avoid certain + * types of fault checking. + * + * For detailed documentation, See: + * + * "WE 32201 Memory Management Unit Information Manual", AT&T Select + * Code 307-706, February 1987; Figure 2-18, pages 2-24 through 2-25. + */ +t_stat mmu_pdc_miss(uint32 va, uint8 r_acc, t_bool fc, + uint32 *pd, uint32 *pdc_idx) +{ + uint32 sd_ptr, sd_hi, sd_lo, pd_addr; + uint32 indirect_count = 0; + t_bool sdc_miss = FALSE; + + *pdc_idx = 0; + + /* If this was an instruction fetch, the actual requested level + * here will become "Instruction Fetch After Discontinuity" + * due to the page miss. */ + r_acc = (r_acc == ACC_IF ? ACC_IFAD : r_acc); + + /* We immediately do SSL bounds checking. The 'fc' flag is not + * checked because SSL out of bounds is a fatal error. */ + if (SSL(va) > SRAMB_LEN(va)) { + sim_debug(MMU_FAULT_DBG, &mmu_dev, + "SDT Length Fault. sramb_len=%x ssl=%x va=%08x\n", + SRAMB_LEN(va), SSL(va), va); + MMU_FAULT(MMU_F_SDTLEN); + return SCPE_NXM; + } + + /* This loop handles segment descriptor indirection (if any) */ + sd_ptr = SD_ADDR(va); + while (1) { + /* Try to find the SD in the cache */ + if (get_sdce(va, &sd_hi, &sd_lo) != SCPE_OK) { + /* This was a miss, so we need to load the SD out of + * memory. */ + sdc_miss = TRUE; + + sd_lo = pread_w(sd_ptr, BUS_PER); /* Control Bits */ + sd_hi = pread_w(sd_ptr + 4, BUS_PER); /* Address Bits */ + sim_debug(MMU_CACHE_DBG, &mmu_dev, + "SDC miss. Read sd_ptr=%08x sd_lo=%08x sd_hi=%08x va=%08x\n", + sd_ptr, sd_lo, sd_hi, va); + } + + if (!SD_VALID(sd_lo)) { + sim_debug(MMU_FAULT_DBG, &mmu_dev, + "Invalid Segment Descriptor. va=%08x sd_hi=%08x sd_lo=%08x\n", + va, sd_hi, sd_lo); + MMU_FAULT(MMU_F_INV_SD); + return SCPE_NXM; + } + + if (SD_INDIRECT(sd_lo)) { + if (++indirect_count > MAX_INDIRECTS) { + sim_debug(MMU_FAULT_DBG, &mmu_dev, + "Max Indirects Fault. va=%08x sd_hi=%08x sd_lo=%08x\n", + va, sd_hi, sd_lo); + MMU_FAULT(MMU_F_INDIRECT); + return SCPE_NXM; + } + + /* Any permission failure at this point is actually an MMU_F_MISS_MEM */ + if (mmu_check_perm(SD_ACC(sd_lo), r_acc) != SCPE_OK) { + sim_debug(MMU_FAULT_DBG, &mmu_dev, + "MMU Miss Processing Memory Fault (SD Access) (ckm=%d pd_acc=%02x r_acc=%02x)\n", + CPU_CM, SD_ACC(sd_lo), r_acc); + MMU_FAULT(MMU_F_MISS_MEM); + return SCPE_NXM; + } + + /* sd_hi is a pointer to a new segment descriptor */ + sd_ptr = sd_hi; + + sd_lo = pread_w(sd_ptr, BUS_PER); + sd_hi = pread_w(sd_ptr + 4, BUS_PER); + } else { + /* If it's not an indirection, we're done. */ + break; + } + } + + /* Fault if the segment descriptor P bit isn't set */ + if (!SD_PRESENT(sd_lo)) { + /* If the C bit is set, this is a SEGMENT NOT PRESENT + fault; otherwise, it's a PDT NOT PRESENT fault. */ + if (SD_CONTIG(sd_lo)) { + sim_debug(MMU_FAULT_DBG, &mmu_dev, + "Segment Not Present. va=%08x\n", + va); + MMU_FAULT(MMU_F_SEG_NOT_PRES); + return SCPE_NXM; + } else { + sim_debug(MMU_FAULT_DBG, &mmu_dev, + "PDT Not Present. va=%08x\n", + va); + MMU_FAULT(MMU_F_PDT_NOT_PRES); + return SCPE_NXM; + } + } + + /* Check to see if the segment is too long. */ + if (SD_CONTIG(sd_lo)) { + if (PSL(va) > SD_MAX_OFF(sd_lo)) { + sim_debug(MMU_FAULT_DBG, &mmu_dev, + "Segment Offset Fault. va=%08x\n", + va); + MMU_FAULT(MMU_F_SEG_OFFSET); + return SCPE_NXM; + } + } else { + if ((va & 0x1ffff) > MAX_SEG_OFF(sd_lo)) { + sim_debug(MMU_FAULT_DBG, &mmu_dev, + "PDT Length Fault. va=%08x max_seg_off=0x%x\n", + va, MAX_SEG_OFF(sd_lo)); + MMU_FAULT(MMU_F_PDTLEN); + return SCPE_NXM; + } + } + + /* Either load or construct the PD */ + if (SD_CONTIG(sd_lo)) { + /* TODO: VERIFY */ + if (mmu_check_perm(SD_ACC(sd_lo), r_acc) != SCPE_OK) { + sim_debug(MMU_FAULT_DBG, &mmu_dev, + "[AFTER DISCONTINUITY] Access to Memory Denied (va=%08x ckm=%d pd_acc=%02x r_acc=%02x)\n", + va, CPU_CM, SD_ACC(sd_lo), r_acc); + MMU_FAULT(MMU_F_ACC); + return SCPE_NXM; + } + + /* We have to construct a PD for this SD. */ + *pd = (((sd_hi & pd_addr_masks[MMU_CONF_PS]) + PSL_C(va)) | + ((sd_lo & 0x800000) >> 18) | /* Copy R bit */ + ((sd_lo & 0x400000) >> 21) | /* Copy M bit */ + 1); /* P bit */ + + sim_debug(MMU_CACHE_DBG, &mmu_dev, + "Contiguous Segment. Constructing PD. PSIZE=%d va=%08x sd_hi=%08x sd_lo=%08x pd=%08x\n", + MMU_CONF_PS, va, sd_hi, sd_lo, *pd); + } else { + /* We can find the PD in main memory */ + pd_addr = SD_SEG_ADDR(sd_hi) + (PSL(va) * 4); + + *pd = pread_w(pd_addr, BUS_PER); + + sim_debug(MMU_CACHE_DBG, &mmu_dev, + "Paged Segment. Loaded PD. va=%08x sd_hi=%08x sd_lo=%08x pd_addr=%08x pd=%08x\n", + va, sd_hi, sd_lo, pd_addr, *pd); + } + + if (r_acc == ACC_W && (*pd & PD_W_MASK)) { + sim_debug(MMU_FAULT_DBG, &mmu_dev, + "Page Write Fault, pd=%08x va=%08x\n", + *pd, va); + MMU_FAULT(MMU_F_PW); + return SCPE_NXM; + } + + if ((*pd & PD_P_MASK) != PD_P_MASK) { + sim_debug(MMU_FAULT_DBG, &mmu_dev, + "Page Not Present Fault. pd=%08x va=%08x\n", + *pd, va); + MMU_FAULT(MMU_F_PAGE_NOT_PRES); + return SCPE_NXM; + } + + /* Finally, cache the PD */ + + if (sdc_miss) { + put_sdce(va, sd_hi, sd_lo); + } + + *pdc_idx = put_pdce(va, sd_lo, *pd); + + return SCPE_OK; +} + +/* + * Translate a virtual address into a physical address. + * + * Note that unlike 'mmu_xlate_addr', this function will _not_ abort + * on failure. The decoded physical address is returned in "pa". If + * the argument "fc" is FALSE, this function will bypass: + * + * - Access flag checks, + * - Cache insertion, + * - Setting MMU fault registers, + * - Modifying segment and page descriptor bits. + * + * In other words, setting "fc" to FALSE does the minimum work + * necessary to translate a virtual address without changing any MMU + * state. The primary use case for this flag is to provide simulator + * debugging access to memory translation while avoiding that access + * undermining the currently running operating system (if any). + * + * This function returns SCPE_OK if translation succeeded, and + * SCPE_NXM if translation failed. + * + */ +t_stat mmu_decode_va(uint32 va, uint8 r_acc, t_bool fc, uint32 *pa) +{ + uint32 pd, pdc_idx; + uint8 pd_acc; + t_stat succ; + + /* + * If the MMU is disabled, virtual == physical. + */ + if (!mmu_state.enabled) { + *pa = va; + return SCPE_OK; + } + + /* + * 1. Check PDC for an entry. + */ + if (get_pdce(va, &pd, &pd_acc, &pdc_idx) == SCPE_OK) { + if (mmu_check_perm(pd_acc, r_acc) != SCPE_OK) { + sim_debug(MMU_FAULT_DBG, &mmu_dev, + "Access to Memory Denied (va=%08x ckm=%d pd_acc=%02x r_acc=%02x)\n", + va, CPU_CM, pd_acc, r_acc); + MMU_FAULT(MMU_F_ACC); + return SCPE_NXM; + } + + if (r_acc == ACC_W && (pd & PD_W_MASK)) { + sim_debug(MMU_FAULT_DBG, &mmu_dev, + "Page Write Fault, pd=%08x va=%08x\n", + pd, va); + MMU_FAULT(MMU_F_PW); + return SCPE_NXM; + } + } else { + /* Do miss processing. This will cache the PD if necessary. */ + succ = mmu_pdc_miss(va, r_acc, fc, &pd, &pdc_idx); + if (succ != SCPE_OK) { + return succ; + } + } + + /* + * 2. Update history bits + */ + succ = mmu_update_history(va, r_acc, pdc_idx, fc); + if (succ != SCPE_OK) { + return succ; + } + + /* + * 3. Translation from Page Descriptor + */ + *pa = PD_ADDR(pd) + POT(va); + + sim_debug(MMU_TRACE_DBG, &mmu_dev, + "XLATE DONE. r_acc=%d va=%08x pa=%08x\n", + r_acc, va, *pa); + + return SCPE_OK; +} + +/* + * Translate a virtual address into a physical address. + * + * This function returns the translated virtual address, and aborts + * without returning if translation failed. + */ +uint32 mmu_xlate_addr(uint32 va, uint8 r_acc) +{ + uint32 pa; + t_stat succ; + + succ = mmu_decode_va(va, r_acc, TRUE, &pa); + + if (succ == SCPE_OK) { + mmu_state.var = va; + return pa; + } else { + cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); + return 0; + } +} + +/* + * Enable the MMU and allow virtual address translation. + */ +void mmu_enable() +{ + mmu_state.enabled = TRUE; +} + +/* + * Disable the MMU. All memory access will be through physical addresses only. + */ +void mmu_disable() +{ + mmu_state.enabled = FALSE; +} + +CONST char *mmu_description(DEVICE *dptr) +{ + return "WE32201 MMU"; +} + +/* + * Display the segment descriptor cache + */ +t_stat mmu_show_sdc(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + uint32 sd_lo, sd_hi, base, pages, i; + + fprintf(st, "\nSegment Descriptor Cache\n\n"); + + fprintf(st, "start sdc (lo) sdc (hi) sd (lo) sd (hi) C/P seg start pages\n"); + fprintf(st, "-------- -------- -------- -------- -------- --- --------- -----\n"); + + for (i = 0; i < MMU_SDCS; i++) { + sd_lo = SDCE_TO_SDL(mmu_state.sdch[i], mmu_state.sdcl[i]); + sd_hi = SDCE_TO_SDH(mmu_state.sdch[i]); + base = ((mmu_state.sdcl[i] & 0xfff) << 20) | ((i & 7) << 17); + pages = ((sd_lo & SD_MAX_OFF_MASK) >> 18) + 1; + + fprintf(st, "%08x %08x %08x %08x %08x %s %08x %d\n", + base, + mmu_state.sdcl[i], + mmu_state.sdch[i], + sd_lo, sd_hi, + SD_CONTIG(sd_lo) ? "C" : "P", + sd_hi & SD_ADDR_MASK, + pages); + + } + + return SCPE_OK; +} + +t_stat mmu_show_pdc(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + uint32 i, pdc_hi, pdc_lo; + + fprintf(st, "\nPage Descriptor Cache\n\n"); + fprintf(st, "IDX pdc (hi) pdc (lo) U G C W vaddr pd addr\n"); + fprintf(st, "---- -------- -------- - - - - -------- --------\n"); + + for (i = 0; i < MMU_PDCS; i++) { + pdc_hi = mmu_state.pdch[i]; + pdc_lo = mmu_state.pdcl[i]; + + fprintf(st, "%02d %08x %08x %s %s %s %s %08x %08x\n", + i, + pdc_hi, pdc_lo, + pdc_hi & PDC_U_MASK ? "U" : " ", + pdc_hi & PDC_G_MASK ? "G" : " ", + pdc_hi & PDC_C_MASK ? "C" : "P", + pdc_lo & PDC_W_MASK ? "W" : " ", + (pdc_hi & PDC_TAG_MASK) << 6, + PDCE_TO_PD(pdc_lo)); + } + + return SCPE_OK; +} + +/* + * Display the segment table for a section. + */ +t_stat mmu_show_sdt(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + uint32 addr, len, sd_lo, sd_hi, base, pages, i; + uint8 sec; + char *cptr = (char *) desc; + t_stat result; + + if (cptr == NULL) { + fprintf(st, "Missing section number\n"); + return SCPE_ARG; + } + + sec = (uint8) get_uint(cptr, 10, 3, &result); + if (result != SCPE_OK) { + fprintf(st, "Please specify a section from 0-3\n"); + return SCPE_ARG; + } + + addr = mmu_state.sec[sec].addr; + len = mmu_state.sec[sec].len + 1; + + fprintf(st, "\nSection %d SDT\n\n", sec); + fprintf(st, "start end sd (lo) sd (hi) C/P seg start pages\n"); + fprintf(st, "-------- -------- -------- -------- --- --------- ------\n"); + + for (i = 0; i < len; i++) { + sd_lo = pread_w(addr + (i * 8), BUS_PER) & SD_RES_MASK; + sd_hi = pread_w(addr + (i * 8) + 4, BUS_PER); + base = (sec << 14 | i << 1) << 16; + pages = ((sd_lo & SD_MAX_OFF_MASK) >> 18) + 1; + + if (SD_VALID(sd_lo)) { + fprintf(st, "%08x-%08x %08x %08x %s %08x %d\n", + base, + base + (((sd_lo & SD_MAX_OFF_MASK) >> 15) * 2048) - 1, + sd_lo, sd_hi, + SD_CONTIG(sd_lo) ? "C" : "P", + sd_hi & SD_ADDR_MASK, + pages); + } + } + + return SCPE_OK; +} diff --git a/3B2/3b2_rev3_mmu.h b/3B2/3b2_rev3_mmu.h index d78edb4c..e538d785 100644 --- a/3B2/3b2_rev3_mmu.h +++ b/3B2/3b2_rev3_mmu.h @@ -1,358 +1,358 @@ -/* 3b2_rev3_mmu.h: WE32201 MMU - - Copyright (c) 2020-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#ifndef _3B2_REV3_MMU_H_ -#define _3B2_REV3_MMU_H_ - -#include "3b2_defs.h" - -#define MMU_SRS 4 /* Section RAM array size (words) */ -#define MMU_SDCS 8 /* SD Cache H/L array size */ -#define MMU_PDCS 64 /* PD Cache H/L array size */ -#define MMU_IDNCS 16 /* ID Number Cache array size */ - -/* Register address offsets */ -#define MMU_SDCL 0 /* SDC - Low Bits */ -#define MMU_SDCH 1 /* SDC - High Bits */ -#define MMU_PDCL 2 /* PDC - Low Bits */ -#define MMU_PDCH 3 /* PDC - High Bits */ -#define MMU_FDCR 4 /* Flush Data Cache Register */ -#define MMU_SRAMA 6 /* Section RAM A */ -#define MMU_SRAMB 7 /* Section RAM B */ -#define MMU_FC 8 /* Fault Code */ -#define MMU_FA 9 /* Fault Address */ -#define MMU_CONF 10 /* Configuration */ -#define MMU_VAR 11 /* Virtual Address Register */ -#define MMU_IDC 12 /* ID Number Cache */ -#define MMU_IDNR 13 /* ID Number Register */ -#define MMU_FIDNR 14 /* Flush ID Number Register */ -#define MMU_VR 15 /* Version Register */ - -#define MMU_REV3_VER 0x23 /* Version byte returned by WE32201 MMU */ - -#define MMU_CONF_M (mmu_state.conf & 0x1) -#define MMU_CONF_R ((mmu_state.conf & 0x2) >> 1) -#define MMU_CONF_C ((mmu_state.conf & 0x4) >> 1) -#define MMU_CONF_PS ((mmu_state.conf & 0x18) >> 3) -#define MMU_CONF_MCE ((mmu_state.conf & 0x20) >> 5) -#define MMU_CONF_DCE ((mmu_state.conf & 0x40) >> 6) - -/* Shift and mask the flag bits for the current CPU mode */ -#define MMU_PERM(f) ((f >> ((3 - (CPU_CM)) * 2)) & 3) - -/* Codes set in the MMU Fault register */ -#define MMU_F_MISS_MEM 1 -#define MMU_F_RM_UPD 2 -#define MMU_F_SDTLEN 3 -#define MMU_F_PW 4 -#define MMU_F_PDTLEN 5 -#define MMU_F_INV_SD 6 -#define MMU_F_SEG_NOT_PRES 7 -#define MMU_F_PDT_NOT_PRES 9 -#define MMU_F_PAGE_NOT_PRES 10 -#define MMU_F_INDIRECT 11 -#define MMU_F_ACC 13 -#define MMU_F_SEG_OFFSET 14 - -/* Access Request types */ -#define ACC_MT 0 /* Move Translated */ -#define ACC_SPW 1 /* Support processor write */ -#define ACC_SPF 3 /* Support processor fetch */ -#define ACC_IR 7 /* Interlocked read */ -#define ACC_AF 8 /* Address fetch */ -#define ACC_OF 9 /* Operand fetch */ -#define ACC_W 10 /* Write */ -#define ACC_IFAD 12 /* Instruction fetch after discontinuity */ -#define ACC_IF 13 /* Instruction fetch */ - -/* Pluck out Virtual Address fields */ -#define SID(va) (((va) >> 30) & 3) -#define SSL(va) (((va) >> 17) & 0x1fff) -#define SOT(va) (va & 0x1ffff) - -/* PSL will be either: - * - Bits 11-16 (2K pages: MMU_CONF_PS = 0) - * - Bits 12-16 (4K pages: MMU_CONF_PS = 1) - * - Bits 13-16 (8K pages: MMU_CONF_PS = 2) - */ -#define PSL(va) (((va) >> (11 + MMU_CONF_PS)) & (0x3f >> MMU_CONF_PS)) -#define PSL_C(va) (((va) & pd_psl_masks[MMU_CONF_PS])) - -/* POT will be either: - * - Bits 0-10 (2K pages: MMU_CONF_PS = 0) - * - Bits 0-11 (4K pages: MMU_CONF_PS = 1) - * - Bits 0-12 (8K pages: MMU_CONF_PS = 2) - */ -#define POT(va) ((va) & (0x1fff >> (2 - (MMU_CONF_PS)))) - -/* Get the maximum length of an SSL from SRAMB */ -#define SRAMB_LEN(va) (mmu_state.sec[SID(va)].len) - -/* Pluck out Segment Descriptor fields */ -#define SD_PRESENT(sd_lo) ((sd_lo) & 1) -#define SD_MODIFIED(sd_lo) (((sd_lo) >> 1) & 1) -#define SD_CONTIG(sd_lo) (((sd_lo) >> 2) & 1) -#define SD_VALID(sd_lo) (((sd_lo) >> 6) & 1) -#define SD_INDIRECT(sd_lo) (((sd_lo) >> 7) & 1) -#define SD_MAX_OFF(sd_lo) (((sd_lo) >> 18) & 0x3f) -#define SD_ACC(sd_lo) (((sd_lo) >> 24) & 0xff) - -#define SD_SEG_ADDR(sd_hi) ((sd_hi) & 0xfffffff8u) - -#define SD_P_MASK 0x1 -#define SD_M_MASK 0x2 -#define SD_C_MASK 0x4 -#define SD_CACHE_MASK 0x8 -#define SD_R_MASK 0x20 -#define SD_V_MASK 0x40 -#define SD_MAX_OFF_MASK 0xfc0000 -#define SD_ACC_MASK 0xff000000u -#define SD_ADDR_MASK 0xfffffff8u -#define SD_VADDR_MASK 0xfff00000u -#define SD_RES_MASK 0xfffc00efu - -#define SDC_VADDR_MASK 0xfff -#define SDC_ACC_MASK 0xff000000u -#define SDC_MAX_OFF_MASK 0x001f8000 -#define SDC_G_MASK 0x1 -#define SDC_C_MASK 0x2 -#define SDC_CACHE_MASK 0x4 -#define SDC_M_MASK 0x400000 -#define SDC_R_MASK 0x800000 - -#define PD_P_MASK 0x1 -#define PD_M_MASK 0x2 -#define PD_W_MASK 0x10 -#define PD_R_MASK 0x20 -#define PD_PADDR_MASK 0xfffff800u - -#define PDC_PADDR_MASK 0x1fffff -#define PDC_C_MASK 0x2 -#define PDC_W_MASK 0x200000 -#define PDC_M_MASK 0x400000 -#define PDC_R_MASK 0x800000 -#define PDC_G_MASK 0x40000000 -#define PDC_U_MASK 0x80000000u - -#define MAX_INDIRECTS 3 - -#define PD_ADDR(pd) (pd & (pd_addr_masks[MMU_CONF_PS])) -#define SD_ADDR(va) (mmu_state.sec[SID(va)].addr + (SSL(va) * 8)) - -#define SDC_IDX(va) ((uint8)((va) >> 17) & 7) - -/* Convert from sd to sd cache entry */ -#define SD_TO_SDCH(hi,lo) (((hi) & SD_ADDR_MASK) | \ - ((lo) & SD_C_MASK) >> 1 | \ - ((lo) & SD_CACHE_MASK) >> 1 | \ - (SDC_G_MASK)) -#define SD_TO_SDCL(lo,va) (((lo) & SD_ACC_MASK) | \ - ((lo) & SD_MAX_OFF_MASK) >> 3 | \ - ((lo) & SD_R_MASK) << 18 | \ - ((lo) & SD_M_MASK) << 21 | \ - ((va) & SD_VADDR_MASK) >> 20) - -/* Convert from sd cache entry to sd */ -#define SDCE_TO_SDH(hi) ((hi) & SD_ADDR_MASK) -#define SDCE_TO_SDL(hi,lo) (((lo) & SDC_ACC_MASK) | \ - ((lo) & SDC_MAX_OFF_MASK) << 3 | \ - ((lo) & SDC_R_MASK) >> 18 | \ - ((lo) & SDC_M_MASK) >> 21 | \ - ((hi) & SDC_C_MASK) << 1 | \ - ((hi) & SDC_CACHE_MASK) << 1 | \ - SD_V_MASK | SD_P_MASK) - -/* Convert from pd cache entry to pd */ -#define PDCE_TO_PD(pdcl) ((((pdcl) & PDC_PADDR_MASK) << 11) | \ - (((pdcl) & PDC_W_MASK) >> 17) | \ - (((pdcl) & PDC_M_MASK) >> 21) | \ - (((pdcl) & PDC_R_MASK) >> 18) | \ - PD_P_MASK) - -/* Convert from pd to pd cache entry (low word) */ -#define PD_TO_PDCL(pd, sd_lo) ((((pd) & PD_PADDR_MASK) >> 11) | \ - (((pd) & PD_W_MASK) << 17) | \ - (((pd) & PD_M_MASK) << 21) | \ - (((pd) & PD_R_MASK) << 18) | \ - ((sd_lo) & SD_ACC_MASK)) - -/* Convert from va to pd cache entry (high word / tag) */ -#define VA_TO_PDCH(va, sd_lo) ((1 << 30) | \ - (mmu_state.cidnr[SID(va)] << 26) | \ - ((va & 0xfffff800u) >> 6) | \ - ((sd_lo & SD_CACHE_MASK) >> 1) | \ - ((sd_lo & SD_C_MASK) >> 1)) - -/* Maximum offset (in bytes) of a paged segment */ -#define MAX_SEG_OFF(w) (((SD_MAX_OFF(w) + 1) * ((MMU_CONF_PS + 1) * 2048)) - 1) - -#define IDNC_TAG(val) ((val) & 0xfffffff8) -#define IDNC_U(val) ((val) & 0x1) - -/* Fault codes */ -#define MMU_FAULT(f) { \ - if (fc) { \ - mmu_state.fcode = ((((uint32)r_acc)<<7) | \ - (((uint32)(CPU_CM))<<5) | \ - (f & 0x1f)); \ - mmu_state.faddr = va; \ - } \ - } - -typedef struct { - uint32 addr; - uint32 len; -} mmu_sec; - - -/* - * Segment Descriptor Cache Entry Format - * ===================================== - * - * The Segment Descriptor Cache is a directly mapped cache, indexed by - * bits 19, 18, and 17 of the virtual address. Some notes: - * - * - "Acc", "R", "M", "Max Offset", "Address", "$", and "C" are all - * copied from the SD in main memory. - * - "VAddr" holds bits 20-31 of the virtual address - * - "Address" holds a pointer (word-aligned, so the top 30 bits) to - * a page descriptor table in paged mode, or a segment in - * contiguous segment mode. - * - "Max Offset" holds the number of pages minus one in the - * segment. Depending on current page size, various bits of this - * field will be ignored: - * o Bits 20-15 are used for 2K pages - * o Bits 20-16 are used for 4K pages - * o Bits 20-17 are used for 8K pages - * - * Low Word (bits 0-31) - * -------------------- - * - * 31 24 23 22 21 20 15 14 12 11 0 - * +-------+---+---+---+------------+------+--------------------------+ - * | Acc | R | M | - | Max Offset | - | VAddr | - * +-------+---+---+---+------------+------+--------------------------+ - * - * High Word (bits 32-63) - * ---------------------- - * - * 31 3 2 1 0 - * +------------------------------------------------------+---+---+---+ - * | Address | $ | C | G | - * +------------------------------------------------------+---+---+---+ - * - * - * Page Descriptor Cache Entry Format - * ================================== - * - * The Page Descriptor Cache is a fully associative cache, with a - * tag constructed from the "G" and "IDN" bits, and bits 31-11 of - * the virtual address. - * - * Depending on the current page size and access mode, various bits of - * "VAddr" are ignored. - * - * o Multi-context mode, all ops except single-entry flush: - * VAddr bits 29-11 are used. - * o Multi-context mode, single-entry flush: - * VAddr bits 31-11 are used. - * o Single-context mode, all ops: - * Vaddr bits 31-11 are used. - * o In ALL CASES: - * + 2KB Page Size: Bits 11-12 are used. - * + 4KB Page Size: Bit 11 ignored, 12 used. - * + 8KB Page Size: Bits 11-12 ignored. - * - * Low Word (bits 0-31) - * -------------------- - * - * 31 24 23 22 21 20 0 - * +-------+---+---+---+----------------------------------------------+ - * | Acc | R | M | W | Physical Address | - * +-------+---+---+---+----------------------------------------------+ - * - * - * High Word (bits 32-63) - * ---------------------- - * - * 31 30 29 26 25 5 4 3 2 1 0 - * +---+---+---------+----------------------------+-------+---+---+---+ - * | U | G | IDN | (31) VAddr (11)| - | $ | C | - | - * +---+---+---------+----------------------------+-------+---+---+---+ - * - */ - -typedef struct _mmu_state { - t_bool enabled; /* Global enabled/disabled flag */ - - t_bool flush_u; /* If true, flush all but last cached entry */ - uint32 last_cached; /* The index of the last cached PDC entry */ - - uint32 sdcl[MMU_SDCS]; /* SDC low bits (0-31) */ - uint32 sdch[MMU_SDCS]; /* SDC high bits (32-63) */ - - uint32 pdcl[MMU_PDCS]; /* PDC low bits (0-31) */ - uint32 pdch[MMU_PDCS]; /* PDC high bits (32-63) */ - - uint32 sra[4]; /* Section RAM A */ - uint32 srb[4]; /* Section RAM B */ - - uint32 cidnr[4]; /* Current ID Number Registers */ - uint32 idnc[16]; /* ID Number Cache */ - - mmu_sec sec[4]; /* Section descriptors decoded from - Section RAM A and B */ - - uint32 fcode; /* Fault Code Register */ - uint32 faddr; /* Fault Address Register */ - uint32 conf; /* Configuration Register */ - uint32 var; /* Virtual Address Register */ - -} MMU_STATE; - -t_stat mmu_init(DEVICE *dptr); -uint32 mmu_read(uint32 pa, size_t size); -void mmu_write(uint32 pa, uint32 val, size_t size); -CONST char *mmu_description(DEVICE *dptr); - -/* Virtual memory translation */ -uint32 mmu_xlate_addr(uint32 va, uint8 r_acc); -t_stat mmu_decode_vaddr(uint32 vaddr, uint8 r_acc, - t_bool fc, uint32 *pa); - -t_stat mmu_decode_va(uint32 va, uint8 r_acc, t_bool fc, uint32 *pa); -void mmu_enable(); -void mmu_disable(); - -t_stat mmu_show_sdt(FILE *st, UNIT *uptr, int32 val, CONST void *desc); -t_stat mmu_show_sdc(FILE *st, UNIT *uptr, int32 val, CONST void *desc); -t_stat mmu_show_pdc(FILE *st, UNIT *uptr, int32 val, CONST void *desc); - -#endif /* _3B2_REV3_MMU_H_ */ +/* 3b2_rev3_mmu.h: WE32201 MMU + + Copyright (c) 2020-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#ifndef _3B2_REV3_MMU_H_ +#define _3B2_REV3_MMU_H_ + +#include "3b2_defs.h" + +#define MMU_SRS 4 /* Section RAM array size (words) */ +#define MMU_SDCS 8 /* SD Cache H/L array size */ +#define MMU_PDCS 64 /* PD Cache H/L array size */ +#define MMU_IDNCS 16 /* ID Number Cache array size */ + +/* Register address offsets */ +#define MMU_SDCL 0 /* SDC - Low Bits */ +#define MMU_SDCH 1 /* SDC - High Bits */ +#define MMU_PDCL 2 /* PDC - Low Bits */ +#define MMU_PDCH 3 /* PDC - High Bits */ +#define MMU_FDCR 4 /* Flush Data Cache Register */ +#define MMU_SRAMA 6 /* Section RAM A */ +#define MMU_SRAMB 7 /* Section RAM B */ +#define MMU_FC 8 /* Fault Code */ +#define MMU_FA 9 /* Fault Address */ +#define MMU_CONF 10 /* Configuration */ +#define MMU_VAR 11 /* Virtual Address Register */ +#define MMU_IDC 12 /* ID Number Cache */ +#define MMU_IDNR 13 /* ID Number Register */ +#define MMU_FIDNR 14 /* Flush ID Number Register */ +#define MMU_VR 15 /* Version Register */ + +#define MMU_REV3_VER 0x23 /* Version byte returned by WE32201 MMU */ + +#define MMU_CONF_M (mmu_state.conf & 0x1) +#define MMU_CONF_R ((mmu_state.conf & 0x2) >> 1) +#define MMU_CONF_C ((mmu_state.conf & 0x4) >> 1) +#define MMU_CONF_PS ((mmu_state.conf & 0x18) >> 3) +#define MMU_CONF_MCE ((mmu_state.conf & 0x20) >> 5) +#define MMU_CONF_DCE ((mmu_state.conf & 0x40) >> 6) + +/* Shift and mask the flag bits for the current CPU mode */ +#define MMU_PERM(f) ((f >> ((3 - (CPU_CM)) * 2)) & 3) + +/* Codes set in the MMU Fault register */ +#define MMU_F_MISS_MEM 1 +#define MMU_F_RM_UPD 2 +#define MMU_F_SDTLEN 3 +#define MMU_F_PW 4 +#define MMU_F_PDTLEN 5 +#define MMU_F_INV_SD 6 +#define MMU_F_SEG_NOT_PRES 7 +#define MMU_F_PDT_NOT_PRES 9 +#define MMU_F_PAGE_NOT_PRES 10 +#define MMU_F_INDIRECT 11 +#define MMU_F_ACC 13 +#define MMU_F_SEG_OFFSET 14 + +/* Access Request types */ +#define ACC_MT 0 /* Move Translated */ +#define ACC_SPW 1 /* Support processor write */ +#define ACC_SPF 3 /* Support processor fetch */ +#define ACC_IR 7 /* Interlocked read */ +#define ACC_AF 8 /* Address fetch */ +#define ACC_OF 9 /* Operand fetch */ +#define ACC_W 10 /* Write */ +#define ACC_IFAD 12 /* Instruction fetch after discontinuity */ +#define ACC_IF 13 /* Instruction fetch */ + +/* Pluck out Virtual Address fields */ +#define SID(va) (((va) >> 30) & 3) +#define SSL(va) (((va) >> 17) & 0x1fff) +#define SOT(va) (va & 0x1ffff) + +/* PSL will be either: + * - Bits 11-16 (2K pages: MMU_CONF_PS = 0) + * - Bits 12-16 (4K pages: MMU_CONF_PS = 1) + * - Bits 13-16 (8K pages: MMU_CONF_PS = 2) + */ +#define PSL(va) (((va) >> (11 + MMU_CONF_PS)) & (0x3f >> MMU_CONF_PS)) +#define PSL_C(va) (((va) & pd_psl_masks[MMU_CONF_PS])) + +/* POT will be either: + * - Bits 0-10 (2K pages: MMU_CONF_PS = 0) + * - Bits 0-11 (4K pages: MMU_CONF_PS = 1) + * - Bits 0-12 (8K pages: MMU_CONF_PS = 2) + */ +#define POT(va) ((va) & (0x1fff >> (2 - (MMU_CONF_PS)))) + +/* Get the maximum length of an SSL from SRAMB */ +#define SRAMB_LEN(va) (mmu_state.sec[SID(va)].len) + +/* Pluck out Segment Descriptor fields */ +#define SD_PRESENT(sd_lo) ((sd_lo) & 1) +#define SD_MODIFIED(sd_lo) (((sd_lo) >> 1) & 1) +#define SD_CONTIG(sd_lo) (((sd_lo) >> 2) & 1) +#define SD_VALID(sd_lo) (((sd_lo) >> 6) & 1) +#define SD_INDIRECT(sd_lo) (((sd_lo) >> 7) & 1) +#define SD_MAX_OFF(sd_lo) (((sd_lo) >> 18) & 0x3f) +#define SD_ACC(sd_lo) (((sd_lo) >> 24) & 0xff) + +#define SD_SEG_ADDR(sd_hi) ((sd_hi) & 0xfffffff8u) + +#define SD_P_MASK 0x1 +#define SD_M_MASK 0x2 +#define SD_C_MASK 0x4 +#define SD_CACHE_MASK 0x8 +#define SD_R_MASK 0x20 +#define SD_V_MASK 0x40 +#define SD_MAX_OFF_MASK 0xfc0000 +#define SD_ACC_MASK 0xff000000u +#define SD_ADDR_MASK 0xfffffff8u +#define SD_VADDR_MASK 0xfff00000u +#define SD_RES_MASK 0xfffc00efu + +#define SDC_VADDR_MASK 0xfff +#define SDC_ACC_MASK 0xff000000u +#define SDC_MAX_OFF_MASK 0x001f8000 +#define SDC_G_MASK 0x1 +#define SDC_C_MASK 0x2 +#define SDC_CACHE_MASK 0x4 +#define SDC_M_MASK 0x400000 +#define SDC_R_MASK 0x800000 + +#define PD_P_MASK 0x1 +#define PD_M_MASK 0x2 +#define PD_W_MASK 0x10 +#define PD_R_MASK 0x20 +#define PD_PADDR_MASK 0xfffff800u + +#define PDC_PADDR_MASK 0x1fffff +#define PDC_C_MASK 0x2 +#define PDC_W_MASK 0x200000 +#define PDC_M_MASK 0x400000 +#define PDC_R_MASK 0x800000 +#define PDC_G_MASK 0x40000000 +#define PDC_U_MASK 0x80000000u + +#define MAX_INDIRECTS 3 + +#define PD_ADDR(pd) (pd & (pd_addr_masks[MMU_CONF_PS])) +#define SD_ADDR(va) (mmu_state.sec[SID(va)].addr + (SSL(va) * 8)) + +#define SDC_IDX(va) ((uint8)((va) >> 17) & 7) + +/* Convert from sd to sd cache entry */ +#define SD_TO_SDCH(hi,lo) (((hi) & SD_ADDR_MASK) | \ + ((lo) & SD_C_MASK) >> 1 | \ + ((lo) & SD_CACHE_MASK) >> 1 | \ + (SDC_G_MASK)) +#define SD_TO_SDCL(lo,va) (((lo) & SD_ACC_MASK) | \ + ((lo) & SD_MAX_OFF_MASK) >> 3 | \ + ((lo) & SD_R_MASK) << 18 | \ + ((lo) & SD_M_MASK) << 21 | \ + ((va) & SD_VADDR_MASK) >> 20) + +/* Convert from sd cache entry to sd */ +#define SDCE_TO_SDH(hi) ((hi) & SD_ADDR_MASK) +#define SDCE_TO_SDL(hi,lo) (((lo) & SDC_ACC_MASK) | \ + ((lo) & SDC_MAX_OFF_MASK) << 3 | \ + ((lo) & SDC_R_MASK) >> 18 | \ + ((lo) & SDC_M_MASK) >> 21 | \ + ((hi) & SDC_C_MASK) << 1 | \ + ((hi) & SDC_CACHE_MASK) << 1 | \ + SD_V_MASK | SD_P_MASK) + +/* Convert from pd cache entry to pd */ +#define PDCE_TO_PD(pdcl) ((((pdcl) & PDC_PADDR_MASK) << 11) | \ + (((pdcl) & PDC_W_MASK) >> 17) | \ + (((pdcl) & PDC_M_MASK) >> 21) | \ + (((pdcl) & PDC_R_MASK) >> 18) | \ + PD_P_MASK) + +/* Convert from pd to pd cache entry (low word) */ +#define PD_TO_PDCL(pd, sd_lo) ((((pd) & PD_PADDR_MASK) >> 11) | \ + (((pd) & PD_W_MASK) << 17) | \ + (((pd) & PD_M_MASK) << 21) | \ + (((pd) & PD_R_MASK) << 18) | \ + ((sd_lo) & SD_ACC_MASK)) + +/* Convert from va to pd cache entry (high word / tag) */ +#define VA_TO_PDCH(va, sd_lo) ((1 << 30) | \ + (mmu_state.cidnr[SID(va)] << 26) | \ + ((va & 0xfffff800u) >> 6) | \ + ((sd_lo & SD_CACHE_MASK) >> 1) | \ + ((sd_lo & SD_C_MASK) >> 1)) + +/* Maximum offset (in bytes) of a paged segment */ +#define MAX_SEG_OFF(w) (((SD_MAX_OFF(w) + 1) * ((MMU_CONF_PS + 1) * 2048)) - 1) + +#define IDNC_TAG(val) ((val) & 0xfffffff8) +#define IDNC_U(val) ((val) & 0x1) + +/* Fault codes */ +#define MMU_FAULT(f) { \ + if (fc) { \ + mmu_state.fcode = ((((uint32)r_acc)<<7) | \ + (((uint32)(CPU_CM))<<5) | \ + (f & 0x1f)); \ + mmu_state.faddr = va; \ + } \ + } + +typedef struct { + uint32 addr; + uint32 len; +} mmu_sec; + + +/* + * Segment Descriptor Cache Entry Format + * ===================================== + * + * The Segment Descriptor Cache is a directly mapped cache, indexed by + * bits 19, 18, and 17 of the virtual address. Some notes: + * + * - "Acc", "R", "M", "Max Offset", "Address", "$", and "C" are all + * copied from the SD in main memory. + * - "VAddr" holds bits 20-31 of the virtual address + * - "Address" holds a pointer (word-aligned, so the top 30 bits) to + * a page descriptor table in paged mode, or a segment in + * contiguous segment mode. + * - "Max Offset" holds the number of pages minus one in the + * segment. Depending on current page size, various bits of this + * field will be ignored: + * o Bits 20-15 are used for 2K pages + * o Bits 20-16 are used for 4K pages + * o Bits 20-17 are used for 8K pages + * + * Low Word (bits 0-31) + * -------------------- + * + * 31 24 23 22 21 20 15 14 12 11 0 + * +-------+---+---+---+------------+------+--------------------------+ + * | Acc | R | M | - | Max Offset | - | VAddr | + * +-------+---+---+---+------------+------+--------------------------+ + * + * High Word (bits 32-63) + * ---------------------- + * + * 31 3 2 1 0 + * +------------------------------------------------------+---+---+---+ + * | Address | $ | C | G | + * +------------------------------------------------------+---+---+---+ + * + * + * Page Descriptor Cache Entry Format + * ================================== + * + * The Page Descriptor Cache is a fully associative cache, with a + * tag constructed from the "G" and "IDN" bits, and bits 31-11 of + * the virtual address. + * + * Depending on the current page size and access mode, various bits of + * "VAddr" are ignored. + * + * o Multi-context mode, all ops except single-entry flush: + * VAddr bits 29-11 are used. + * o Multi-context mode, single-entry flush: + * VAddr bits 31-11 are used. + * o Single-context mode, all ops: + * Vaddr bits 31-11 are used. + * o In ALL CASES: + * + 2KB Page Size: Bits 11-12 are used. + * + 4KB Page Size: Bit 11 ignored, 12 used. + * + 8KB Page Size: Bits 11-12 ignored. + * + * Low Word (bits 0-31) + * -------------------- + * + * 31 24 23 22 21 20 0 + * +-------+---+---+---+----------------------------------------------+ + * | Acc | R | M | W | Physical Address | + * +-------+---+---+---+----------------------------------------------+ + * + * + * High Word (bits 32-63) + * ---------------------- + * + * 31 30 29 26 25 5 4 3 2 1 0 + * +---+---+---------+----------------------------+-------+---+---+---+ + * | U | G | IDN | (31) VAddr (11)| - | $ | C | - | + * +---+---+---------+----------------------------+-------+---+---+---+ + * + */ + +typedef struct _mmu_state { + t_bool enabled; /* Global enabled/disabled flag */ + + t_bool flush_u; /* If true, flush all but last cached entry */ + uint32 last_cached; /* The index of the last cached PDC entry */ + + uint32 sdcl[MMU_SDCS]; /* SDC low bits (0-31) */ + uint32 sdch[MMU_SDCS]; /* SDC high bits (32-63) */ + + uint32 pdcl[MMU_PDCS]; /* PDC low bits (0-31) */ + uint32 pdch[MMU_PDCS]; /* PDC high bits (32-63) */ + + uint32 sra[4]; /* Section RAM A */ + uint32 srb[4]; /* Section RAM B */ + + uint32 cidnr[4]; /* Current ID Number Registers */ + uint32 idnc[16]; /* ID Number Cache */ + + mmu_sec sec[4]; /* Section descriptors decoded from + Section RAM A and B */ + + uint32 fcode; /* Fault Code Register */ + uint32 faddr; /* Fault Address Register */ + uint32 conf; /* Configuration Register */ + uint32 var; /* Virtual Address Register */ + +} MMU_STATE; + +t_stat mmu_init(DEVICE *dptr); +uint32 mmu_read(uint32 pa, size_t size); +void mmu_write(uint32 pa, uint32 val, size_t size); +CONST char *mmu_description(DEVICE *dptr); + +/* Virtual memory translation */ +uint32 mmu_xlate_addr(uint32 va, uint8 r_acc); +t_stat mmu_decode_vaddr(uint32 vaddr, uint8 r_acc, + t_bool fc, uint32 *pa); + +t_stat mmu_decode_va(uint32 va, uint8 r_acc, t_bool fc, uint32 *pa); +void mmu_enable(); +void mmu_disable(); + +t_stat mmu_show_sdt(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +t_stat mmu_show_sdc(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +t_stat mmu_show_pdc(FILE *st, UNIT *uptr, int32 val, CONST void *desc); + +#endif /* _3B2_REV3_MMU_H_ */ diff --git a/3B2/3b2_rev3_sys.c b/3B2/3b2_rev3_sys.c index 3b088025..ce1645d2 100644 --- a/3B2/3b2_rev3_sys.c +++ b/3B2/3b2_rev3_sys.c @@ -1,80 +1,80 @@ -/* 3b2_rev3_sys.c: Version 3 (3B2/700) System Definition - - Copyright (c) 2020-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#include "3b2_defs.h" - -#include "3b2_cpu.h" -#include "3b2_csr.h" -#include "3b2_if.h" -#include "3b2_iu.h" -#include "3b2_mau.h" -#include "3b2_ni.h" -#include "3b2_ports.h" -#include "3b2_scsi.h" -#include "3b2_stddev.h" -#include "3b2_timer.h" - -char sim_name[] = "AT&T 3B2/700"; - -DEVICE *sim_devices[] = { - &cpu_dev, - &csr_dev, - &flt_dev, - &mmu_dev, - &mau_dev, - &timer_dev, - &tod_dev, - &nvram_dev, - &tti_dev, - &tto_dev, - &contty_dev, - &iu_timer_dev, - &dmac_dev, - &if_dev, - &ha_dev, - &ports_dev, - &ni_dev, - NULL -}; - -void full_reset() -{ - cpu_reset(&cpu_dev); - mau_reset(&mau_dev); - tti_reset(&tti_dev); - contty_reset(&contty_dev); - iu_timer_reset(&iu_timer_dev); - timer_reset(&timer_dev); - if_reset(&if_dev); - ha_reset(&ha_dev); - csr_reset(&csr_dev); - ports_reset(&ports_dev); - ni_reset(&ni_dev); -} +/* 3b2_rev3_sys.c: Version 3 (3B2/700) System Definition + + Copyright (c) 2020-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#include "3b2_defs.h" + +#include "3b2_cpu.h" +#include "3b2_csr.h" +#include "3b2_if.h" +#include "3b2_iu.h" +#include "3b2_mau.h" +#include "3b2_ni.h" +#include "3b2_ports.h" +#include "3b2_scsi.h" +#include "3b2_stddev.h" +#include "3b2_timer.h" + +char sim_name[] = "AT&T 3B2/700"; + +DEVICE *sim_devices[] = { + &cpu_dev, + &csr_dev, + &flt_dev, + &mmu_dev, + &mau_dev, + &timer_dev, + &tod_dev, + &nvram_dev, + &tti_dev, + &tto_dev, + &contty_dev, + &iu_timer_dev, + &dmac_dev, + &if_dev, + &ha_dev, + &ports_dev, + &ni_dev, + NULL +}; + +void full_reset() +{ + cpu_reset(&cpu_dev); + mau_reset(&mau_dev); + tti_reset(&tti_dev); + contty_reset(&contty_dev); + iu_timer_reset(&iu_timer_dev); + timer_reset(&timer_dev); + if_reset(&if_dev); + ha_reset(&ha_dev); + csr_reset(&csr_dev); + ports_reset(&ports_dev); + ni_reset(&ni_dev); +} diff --git a/3B2/3b2_scsi.c b/3B2/3b2_scsi.c index 99eee173..22d4ac23 100644 --- a/3B2/3b2_scsi.c +++ b/3B2/3b2_scsi.c @@ -1,1452 +1,1452 @@ -/* 3b2_scsi.c: CM195W SCSI Controller CIO Card - - Copyright (c) 2020-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#include "3b2_scsi.h" - -#include "sim_scsi.h" -#include "sim_tape.h" - -#include "3b2_cpu.h" -#include "3b2_io.h" -#include "3b2_mem.h" - -#define DIAG_CRC_1 0x271b114c -#define PUMP_CRC 0x201b3617 - -#define HA_SCSI_ID 0 -#define HA_MAXFR (1u << 16) - -static void ha_cmd(uint8 op, uint8 subdev, uint32 addr, - int32 len, t_bool express); -static void ha_build_req(uint8 tc, uint8 subdev, t_bool express); -static void ha_ctrl(uint8 tc); - - -HA_STATE ha_state; -SCSI_BUS ha_bus; -uint8 *ha_buf; -int8 ha_subdev_tab[8]; /* Map of subdevice to SCSI target */ -uint8 ha_subdev_cnt; -uint32 ha_crc = 0; -uint32 cq_offset = 0; -t_bool ha_conf = FALSE; - -static struct scsi_dev_t ha_tab[] = { - HA_DISK(SD155), - HA_DISK(SD300), - HA_DISK(SD327), - HA_DISK(SD630), - HA_TAPE(ST120) -}; - -#define SCSI_U_FLAGS (UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_DIS+ \ - UNIT_ROABLE+(SD155_DTYPE << UNIT_V_DTYPE)) - -UNIT ha_unit[] = { - { UDATA (&ha_svc, UNIT_DIS, 0) }, /* SCSI ID 0 - the host adapter */ - { UDATA (&ha_svc, SCSI_U_FLAGS, HA_SIZE(SD155)) }, /* SCSI ID 1 */ - { UDATA (&ha_svc, SCSI_U_FLAGS, HA_SIZE(SD155)) }, /* SCSI ID 2 */ - { UDATA (&ha_svc, SCSI_U_FLAGS, HA_SIZE(SD155)) }, /* SCSI ID 3 */ - { UDATA (&ha_svc, SCSI_U_FLAGS, HA_SIZE(SD155)) }, /* SCSI ID 4 */ - { UDATA (&ha_svc, SCSI_U_FLAGS, HA_SIZE(SD155)) }, /* SCSI ID 5 */ - { UDATA (&ha_svc, SCSI_U_FLAGS, HA_SIZE(SD155)) }, /* SCSI ID 6 */ - { UDATA (&ha_svc, SCSI_U_FLAGS, HA_SIZE(SD155)) }, /* SCSI ID 7 */ -}; - -static UNIT *cio_unit = &ha_unit[0]; - -MTAB ha_mod[] = { - { SCSI_WLK, 0, NULL, "WRITEENABLED", - &scsi_set_wlk, NULL, NULL, "Write enable disk drive" }, - { SCSI_WLK, SCSI_WLK, NULL, "LOCKED", - &scsi_set_wlk, NULL, NULL, "Write lock disk drive" }, - { MTAB_XTD|MTAB_VUN, 0, "WRITE", NULL, - NULL, &scsi_show_wlk, NULL, "Display drive writelock status" }, - { MTAB_XTD|MTAB_VUN, SD155_DTYPE, NULL, "SD155", - &ha_set_type, NULL, NULL, "Set 155MB Disk Type" }, - { MTAB_XTD|MTAB_VUN, SD300_DTYPE, NULL, "SD300", - &ha_set_type, NULL, NULL, "Set 300MB Disk Type" }, - { MTAB_XTD|MTAB_VUN, SD327_DTYPE, NULL, "SD327", - &ha_set_type, NULL, NULL, "Set 327MB Disk Type" }, - { MTAB_XTD|MTAB_VUN, SD630_DTYPE, NULL, "SD630", - &ha_set_type, NULL, NULL, "Set 630MB Disk Type" }, - { MTAB_XTD|MTAB_VUN, ST120_DTYPE, NULL, "ST120", - &ha_set_type, NULL, NULL, "Set Wangtek 120MB Tape Type" }, - { MTAB_XTD|MTAB_VUN, 0, "TYPE", NULL, - NULL, &ha_show_type, NULL, "Display device type" }, - { MTAB_XTD|MTAB_VUN, 0, "FORMAT", "FORMAT", - &scsi_set_fmt, &scsi_show_fmt, NULL, "Set/Display unit format" }, - { 0 } -}; - -#define HA_TRACE 1 - -static DEBTAB ha_debug[] = { - { "TRACE", HA_TRACE, "Call Trace" }, - { "SCMD", SCSI_DBG_CMD, "SCSI commands"}, - { "SBUS", SCSI_DBG_BUS, "SCSI bus activity" }, - { "SMSG", SCSI_DBG_MSG, "SCSI messages" }, - { "SDSK", SCSI_DBG_DSK, "SCSI disk activity" }, - { "STAP", MTSE_DBG_API, "SCSI tape activity" }, - { NULL } -}; - -DEVICE ha_dev = { - "SCSI", /* name */ - ha_unit, /* units */ - NULL, /* registers */ - ha_mod, /* modifiers */ - 8, /* #units */ - 16, /* address radix */ - 32, /* address width */ - 1, /* address incr. */ - 16, /* data radix */ - 8, /* data width */ - NULL, /* examine routine */ - NULL, /* deposit routine */ - &ha_reset, /* reset routine */ - NULL, /* boot routine */ - &ha_attach, /* attach routine */ - &ha_detach, /* detach routine */ - NULL, /* context */ - DEV_DEBUG|DEV_DISK|DEV_SECTORS, /* flags */ - 0, /* debug control flags */ - ha_debug, /* debug flag names */ - NULL, /* memory size change */ - NULL, /* logical name */ - NULL, /* help routine */ - NULL, - NULL, - NULL, -}; - -void ha_cio_reset(uint8 slot) -{ - sim_debug(HA_TRACE, &ha_dev, - "Handling CIO reset\n"); - ha_state.pump_state = PUMP_NONE; - ha_crc = 0; -} - -t_stat ha_reset(DEVICE *dptr) -{ - uint8 slot; - uint32 t, dtyp; - UNIT *uptr; - t_stat r; - - ha_state.pump_state = PUMP_NONE; - - if (ha_buf == NULL) { - ha_buf = (uint8 *)calloc(HA_MAXFR, sizeof(uint8)); - } - - r = scsi_init(&ha_bus, HA_MAXFR); - - if (r != SCPE_OK) { - return r; - } - - ha_bus.dptr = dptr; - - scsi_reset(&ha_bus); - - for (t = 0; t < 8; t++) { - uptr = dptr->units + t; - if (t == HA_SCSI_ID) { - uptr->flags = UNIT_DIS; - } - scsi_add_unit(&ha_bus, t, uptr); - dtyp = GET_DTYPE(uptr->flags); - scsi_set_unit(&ha_bus, uptr, &ha_tab[dtyp]); - scsi_reset_unit(uptr); - } - - if (dptr->flags & DEV_DIS) { - cio_remove_all(HA_ID); - ha_conf = FALSE; - return SCPE_OK; - } - - if (!ha_conf) { - r = cio_install(HA_ID, "SCSI", HA_IPL, - &ha_express, &ha_full, &ha_sysgen, &ha_cio_reset, - &slot); - if (r != SCPE_OK) { - return r; - } - ha_state.slot = slot; - ha_conf = TRUE; - } - - return SCPE_OK; -} - -static void ha_calc_subdevs() -{ - uint32 tc; - UNIT *uptr; - - ha_subdev_cnt = 0; - - memset(ha_subdev_tab, -1, 8); - - for (tc = 0; tc < 8; tc++) { - uptr = &ha_unit[tc]; - if (uptr->flags & UNIT_ATT) { - ha_subdev_tab[ha_subdev_cnt++] = tc; - } - } -} - -t_stat ha_attach(UNIT *uptr, CONST char *cptr) -{ - t_stat r; - r = scsi_attach(uptr, cptr); - ha_calc_subdevs(); - return r; -} - -t_stat ha_detach(UNIT *uptr) -{ - t_stat r; - r = scsi_detach(uptr); - ha_calc_subdevs(); - return r; -} - -t_stat ha_svc(UNIT *uptr) -{ - cio_entry cqe; - uint8 capp_data[CAPP_LEN] = {0}; - int8 i, tc = -1; - uint8 svc_req = 0; - ha_req *req = NULL; - ha_resp *rep = NULL; - - sim_debug(HA_TRACE, &ha_dev, - "[ha_svc] SERVICE ROUTINE\n"); - - /* Determine how many targets need servicing */ - for (i = 0; i < 8; i++) { - if (ha_state.ts[i].pending) { - if (req == NULL && rep == NULL) { - tc = i; - sim_debug(HA_TRACE, &ha_dev, - "[ha_svc] Found a job for target %d\n", - tc); - req = &ha_state.ts[i].req; - rep = &ha_state.ts[i].rep; - ha_state.ts[i].pending = FALSE; - } - svc_req++; - } - } - - if (tc == -1) { - return SCPE_OK; - } - - switch(rep->type) { - case HA_JOB_QUICK: - ha_fcm_express(tc); - - sim_debug(HA_TRACE, &ha_dev, - "[ha_svc] FAST MODE CQ: target=%d status=%02x op=%02x subdev=%02x ssb=%02x\n", - tc, rep->status, rep->op, rep->subdev, rep->ssb); - break; - case HA_JOB_EXPRESS: - case HA_JOB_FULL: - cqe.byte_count = rep->len; - cqe.opcode = rep->status; /* Yes, status, not opcode! */ - cqe.subdevice = rep->subdev; - cqe.address = rep->addr; - - sim_debug(HA_TRACE, &ha_dev, - "[ha_svc] CQE: target=%d, byte_count=%04x, opcode=%02x, subdevice=%02x, addr=%08x\n", - tc, cqe.byte_count, cqe.opcode, cqe.subdevice, cqe.address); - - if (rep->type == HA_JOB_EXPRESS) { - sim_debug(HA_TRACE, &ha_dev, - "[ha_svc] EXPRESS MODE CQ: target=%d, byte_count=%02x " - "op=%02x subdev=%02x address=%08x\n", - tc, cqe.byte_count, cqe.opcode, cqe.subdevice, cqe.address); - - cio_cexpress(ha_state.slot, SCQCESIZE, &cqe, capp_data); - } else { - sim_debug(HA_TRACE, &ha_dev, - "[ha_svc] FULL MODE CQ: target=%d, status=%02x op=%02x subdev=%02x ssb=%02x\n", - tc, rep->status, rep->op, rep->subdev, rep->ssb); - - cio_cqueue(ha_state.slot, 0, SCQCESIZE, &cqe, capp_data); - } - break; - } - - sim_debug(HA_TRACE, &ha_dev, - "[ha_svc] IRQ for board %d (VEC=%d). PSW_CUR_IPL=%d\n", - ha_state.slot, cio[ha_state.slot].ivec, PSW_CUR_IPL); - - CIO_SET_INT(ha_state.slot); - - /* There's more work to do after this job is done */ - if (svc_req > 1) { - sim_debug(HA_TRACE, &ha_dev, - "[ha_svc] Scheduling job to handle another %d open requests\n", - svc_req - 1); - sim_activate_abs(uptr, 1000); - } - - return SCPE_OK; -} - -void ha_sysgen(uint8 slot) -{ - uint32 sysgen_p; - uint32 alert_buf_p; - - cq_offset = 0; - - sim_debug(HA_TRACE, &ha_dev, "[ha_sysgen] Handling Sysgen.\n"); - sim_debug(HA_TRACE, &ha_dev, "[ha_sysgen] rqp=%08x\n", cio[slot].rqp); - sim_debug(HA_TRACE, &ha_dev, "[ha_sysgen] cqp=%08x\n", cio[slot].cqp); - sim_debug(HA_TRACE, &ha_dev, "[ha_sysgen] rqs=%d\n", cio[slot].rqs); - sim_debug(HA_TRACE, &ha_dev, "[ha_sysgen] cqs=%d\n", cio[slot].cqs); - sim_debug(HA_TRACE, &ha_dev, "[ha_sysgen] ivec=%d\n", cio[slot].ivec); - sim_debug(HA_TRACE, &ha_dev, "[ha_sysgen] no_rque=%d\n", cio[slot].no_rque); - - sysgen_p = pread_w(SYSGEN_PTR, BUS_PER); - alert_buf_p = pread_w(sysgen_p + 12, BUS_PER); - sim_debug(HA_TRACE, &ha_dev, "[ha_sysgen] alert_bfr=%08x\n", alert_buf_p); - - ha_state.frq = (cio[slot].no_rque == 0); - - ha_state.ts[HA_SCSI_ID].rep.type = ha_state.frq ? HA_JOB_QUICK : HA_JOB_EXPRESS; - ha_state.ts[HA_SCSI_ID].rep.addr = 0; - ha_state.ts[HA_SCSI_ID].rep.len = 0; - ha_state.ts[HA_SCSI_ID].rep.status = 3; - ha_state.ts[HA_SCSI_ID].rep.op = 0; - ha_state.ts[HA_SCSI_ID].pending = TRUE; - - if (ha_crc == PUMP_CRC) { - sim_debug(HA_TRACE, &ha_dev, - "[ha_sysgen] PUMP: NEW STATE = PUMP_SYSGEN\n"); - ha_state.pump_state = PUMP_SYSGEN; - } else { - sim_debug(HA_TRACE, &ha_dev, - "[ha_sysgen] PUMP: NEW STATE = PUMP_NONE\n"); - ha_state.pump_state = PUMP_NONE; - } - - sim_activate_abs(cio_unit, 1000); -} - -void ha_fast_queue_check() -{ - uint8 busy, op, subdev; - uint32 addr, len, rqp; - - rqp = cio[ha_state.slot].rqp; - - busy = pread_b(rqp, BUS_PER); - op = pread_b(rqp + 1, BUS_PER); - subdev = pread_b(rqp + 2, BUS_PER); - /* 4-byte timeout value at rqp + 4 not used */ - addr = pread_w(rqp + 8, BUS_PER); - len = pread_w(rqp + 12, BUS_PER); - - if (busy == 0xff || ha_state.pump_state != PUMP_COMPLETE) { - sim_debug(HA_TRACE, &ha_dev, - "[ha_fast_queue_check] Job pending (opcode=0x%02x subdev=%02x)\n", - op, subdev); - pwrite_b(rqp, 0, BUS_PER); /* Job has been taken */ - ha_cmd(op, subdev, addr, len, FALSE); - } -} - -void ha_express(uint8 slot) -{ - cio_entry rqe; - uint8 rapp_data[RAPP_LEN] = {0}; - - if (ha_state.frq) { - ha_fast_queue_check(); - } else { - cio_rexpress(slot, SCQRESIZE, &rqe, rapp_data); - - sim_debug(HA_TRACE, &ha_dev, - "[ha_express] Handling Express Request. subdev=%02x\n", - rqe.subdevice); - - ha_cmd(rqe.opcode, rqe.subdevice, rqe.address, rqe.byte_count, TRUE); - } -} - -void ha_full(uint8 slot) -{ - sim_debug(HA_TRACE, &ha_dev, - "[ha_full] Handling Full Request (INT3)\n"); - - if (ha_state.pump_state == PUMP_SYSGEN) { - sim_debug(HA_TRACE, &ha_dev, - "[ha_full] PUMP: NEW STATE = PUMP_COMPLETE\n"); - - ha_state.pump_state = PUMP_COMPLETE; - } - - if (ha_state.frq) { - ha_fast_queue_check(); - } else { - sim_debug(HA_TRACE, &ha_dev, - "[ha_full] NON_FRQ NOT HANDLED\n"); - } -} - -static void ha_boot_disk(UNIT *uptr, uint8 tc) -{ - t_seccnt sectsread; - t_stat r; - uint8 buf[HA_BLKSZ]; - uint32 i, boot_loc; - - /* Read in the Physical Descriptor (PD) block (block 0) */ - r = sim_disk_rdsect(uptr, 0, buf, §sread, 1); - - if (r != SCPE_OK) { - sim_debug(HA_TRACE, &ha_dev, - "[ha_boot_disk] Could not read LBA 0\n"); - HA_STAT(tc, HA_CKCON, CIO_SUCCESS); - return; - } - - /* Store the Physical Descriptor (PD) block at well-known - address 0x2004400 */ - sim_debug(HA_TRACE, &ha_dev, - "[ha_boot_disk] Storing PD block at 0x%08x.\n", - HA_PDINFO_ADDR); - for (i = 0; i < HA_BLKSZ; i++) { - pwrite_b(HA_PDINFO_ADDR + i, buf[i], BUS_PER); - } - - - /* The PD block points to the logical start of disk */ - boot_loc = ATOW(buf, HA_PDLS_OFF); - - sim_debug(HA_TRACE, &ha_dev, - "[ha_boot_disk] Logical Start is at 0x%x\n", - boot_loc); - - r = sim_disk_rdsect(uptr, boot_loc, buf, §sread, 1); - - sim_debug(HA_TRACE, &ha_dev, - "[ha_boot_disk] Storing boot block %d at 0x%08x.\n", - boot_loc, HA_BOOT_ADDR); - - for (i = 0; i < HA_BLKSZ; i++) { - pwrite_b(HA_BOOT_ADDR + i, buf[i], BUS_PER); - } - - sim_debug(HA_TRACE, &ha_dev, - "[ha_boot_disk] Done storing boot block at 0x%08x\n", - HA_BOOT_ADDR); - - HA_STAT(tc, HA_GOOD, CIO_SUCCESS); - - ha_state.ts[tc].rep.addr = HA_BOOT_ADDR; - ha_state.ts[tc].rep.len = HA_BLKSZ; -} - -static void ha_boot_tape(UNIT *uptr, uint8 tc) -{ - t_seccnt sectsread; - t_stat r; - uint8 buf[HA_BLKSZ]; - uint32 i; - - if (!(uptr->flags & UNIT_ATT)) { - sim_debug(HA_TRACE, &ha_dev, - "[ha_boot_tape] Target not attached\n"); - HA_STAT(tc, HA_CKCON, CIO_SUCCESS); - return; - } - - r = sim_tape_rewind(uptr); - - if (r != SCPE_OK) { - sim_debug(HA_TRACE, &ha_dev, - "[ha_boot_tape] Could not rewind tape\n"); - HA_STAT(tc, HA_CKCON, CIO_SUCCESS); - return; - } - - r = sim_tape_rdrecf(uptr, buf, §sread, HA_BLKSZ); /* Read block 0 */ - - if (r != SCPE_OK) { - sim_debug(HA_TRACE, &ha_dev, - "[ha_boot_tape] Could not read PD block.\n"); - HA_STAT(tc, HA_CKCON, CIO_SUCCESS); - return; - } - - for (i = 0; i < HA_BLKSZ; i++) { - pwrite_b(HA_BOOT_ADDR + i, buf[i], BUS_PER); - } - - sim_debug(HA_TRACE, &ha_dev, - "[ha_boot_tape] Transfered 512 bytes to 0x%08x\n", - HA_BOOT_ADDR); - - r = sim_tape_sprecf(uptr, §sread); /* Skip block 1 */ - - HA_STAT(tc, HA_GOOD, CIO_SUCCESS); - - ha_state.ts[tc].rep.addr = HA_BOOT_ADDR; - ha_state.ts[tc].rep.len = HA_BLKSZ; -} - -static void ha_read_block_tape(UNIT *uptr, uint32 addr, uint8 tc) -{ - t_seccnt sectsread; - t_stat r; - uint8 buf[HA_BLKSZ]; - uint32 i; - - if (!(uptr->flags & UNIT_ATT)) { - sim_debug(HA_TRACE, &ha_dev, - "[ha_read_block_tape] Target not attached\n"); - HA_STAT(tc, HA_CKCON, CIO_SUCCESS); - return; - } - - r = sim_tape_rdrecf(uptr, buf, §sread, HA_BLKSZ); - - if (r != SCPE_OK) { - sim_debug(HA_TRACE, &ha_dev, - "[ha_read_block_tape] Could not read next block.\n"); - HA_STAT(tc, HA_CKCON, CIO_SUCCESS); - return; - } - - for (i = 0; i < HA_BLKSZ; i++) { - pwrite_b(addr + i, buf[i], BUS_PER); - } - - sim_debug(HA_TRACE, &ha_dev, - "[ha_read_block_tape] Transfered 512 bytes to 0x%08x\n", - addr); - - HA_STAT(tc, HA_GOOD, CIO_SUCCESS); - - ha_state.ts[tc].rep.addr = addr; - ha_state.ts[tc].rep.len = HA_BLKSZ; -} - -static void ha_read_block_disk(UNIT *uptr, uint32 addr, uint8 tc, uint32 lba) -{ - t_seccnt sectsread; - t_stat r; - uint8 buf[HA_BLKSZ]; - uint32 i; - - r = sim_disk_rdsect(uptr, lba, buf, §sread, 1); - - if (r != SCPE_OK) { - sim_debug(HA_TRACE, &ha_dev, - "[ha_read_block_disk] Could not read block %d\n", - lba); - HA_STAT(tc, HA_CKCON, CIO_SUCCESS); - return; - } - - for (i = 0; i < HA_BLKSZ; i++) { - pwrite_b(addr + i, buf[i], BUS_PER); - } - - sim_debug(HA_TRACE, &ha_dev, - "[ha_read_block_disk] Transferred 512 bytes to 0x%08x\n", - addr); - - HA_STAT(tc, HA_GOOD, CIO_SUCCESS); - - ha_state.ts[tc].rep.addr = addr; - ha_state.ts[tc].rep.len = HA_BLKSZ; -} - -static void ha_write_block_disk(UNIT *uptr, uint32 addr, uint8 tc, uint32 lba) -{ - t_seccnt sectswritten; - t_stat r; - uint8 buf[HA_BLKSZ]; - uint32 i; - - for (i = 0 ; i < HA_BLKSZ; i++) { - buf[i] = pread_b(addr + i, BUS_PER); - } - - r = sim_disk_wrsect(uptr, lba, buf, §swritten, 1); - - if (r != SCPE_OK) { - sim_debug(HA_TRACE, &ha_dev, - "[ha_write_block_disk] Could not write block %d\n", - lba); - HA_STAT(tc, HA_CKCON, CIO_SUCCESS); - return; - } - - HA_STAT(tc, HA_GOOD, CIO_SUCCESS); - - ha_state.ts[tc].rep.addr = addr; - ha_state.ts[tc].rep.len = HA_BLKSZ; -} - -static void ha_build_req(uint8 tc, uint8 subdev, t_bool express) -{ - uint32 i, rqp, ptr, dma_lst, daddr_ptr; - uint32 len, addr; - cio_entry rqe; - uint8 rapp_data[RAPP_LEN] = {0}; - - /* - * There are two possible ways to get the SCSI command we've - * been asked to perform. - * - * 1. If this is a "fast mode" operation, then the SCSI command - * is embedded in the Fast Request Queue entry. - * - * 2. If this is a regular queue operation, then the SCSI command - * is embedded in a struct pointed to by the "address" field - * of the queue entry. - */ - - for (i = 0; i < HA_MAX_CMD; i++) { - ha_state.ts[tc].req.cmd[i] = 0; - } - - if (ha_state.frq) { - rqp = cio[ha_state.slot].rqp; - - subdev = pread_b(rqp + 2, BUS_PER); - - ha_state.ts[tc].req.tc = FC_TC(subdev); - ha_state.ts[tc].req.lu = FC_LU(subdev); - ha_state.ts[tc].req.timeout = pread_w(rqp + 4, BUS_PER); - ha_state.ts[tc].req.cmd_len = pread_h(rqp + 18, BUS_PER); - for (i = 0; i < HA_MAX_CMD; i++) { - ha_state.ts[tc].req.cmd[i] = pread_b(rqp + 20 + i, BUS_PER); - } - ha_state.ts[tc].req.op = ha_state.ts[tc].req.cmd[0]; - - /* Possible list of DMA scatter/gather addresses */ - dma_lst = pread_h(rqp + 16, BUS_PER) / 8; - - if (dma_lst) { - t_bool link; - - /* There's a list of address / lengths. Each entry is 8 - * bytes long. */ - ptr = pread_w(rqp + 8, BUS_PER); - link = FALSE; - - sim_debug(HA_TRACE, &ha_dev, - "[build_req] Building a list of scatter/gather addresses.\n"); - - daddr_ptr = 0; - - for (i = 0; (i < dma_lst) || link; i++) { - addr = pread_w(ptr, BUS_PER); - len = pread_w(ptr + 4, BUS_PER); - - if (len == 0) { - sim_debug(HA_TRACE, &ha_dev, - "[build_req] Found length of 0, bailing early.\n"); - break; /* Done early */ - } - - if (len > 0x1000) { - /* There's a new pointer in town */ - ptr = pread_w(ptr, BUS_PER); - sim_debug(HA_TRACE, &ha_dev, - "[build_req] New ptr=%08x\n", - ptr); - link = TRUE; - continue; - } - - sim_debug(HA_TRACE, &ha_dev, - "[build_req] daddr[%d]: addr=%08x, len=%d (%x)\n", - daddr_ptr, addr, len, len); - - ha_state.ts[tc].req.daddr[daddr_ptr].addr = addr; - ha_state.ts[tc].req.daddr[daddr_ptr].len = len; - - daddr_ptr++; - ptr += 8; - } - - ha_state.ts[tc].req.dlen = i; - } else { - /* There's only one embedded address / length */ - ha_state.ts[tc].req.daddr[0].addr = pread_w(rqp + 8, BUS_PER); - ha_state.ts[tc].req.daddr[0].len = pread_w(rqp + 12, BUS_PER); - ha_state.ts[tc].req.dlen = 1; - } - - } else { - if (express) { - cio_rexpress(ha_state.slot, SCQRESIZE, &rqe, rapp_data); - } else { - /* TODO: Find correct queue number! */ - cio_rqueue(ha_state.slot, 0, SCQRESIZE, &rqe, rapp_data); - } - - ptr = rqe.address; - - ha_state.ts[tc].req.tc = FC_TC(rqe.subdevice); - ha_state.ts[tc].req.lu = FC_LU(rqe.subdevice); - ha_state.ts[tc].req.cmd_len = pread_w(ptr + 4, BUS_PER); - ha_state.ts[tc].req.timeout = pread_w(ptr + 8, BUS_PER); - ha_state.ts[tc].req.daddr[0].addr = pread_w(ptr + 12, BUS_PER); - ha_state.ts[tc].req.daddr[0].len = rqe.byte_count; - ha_state.ts[tc].req.dlen = 1; - - sim_debug(HA_TRACE, &ha_dev, - "[build_req] [non-fast] Building a list of 1 scatter/gather addresses.\n"); - - ptr = pread_w(ptr, BUS_PER); - - for (i = 0; (i < ha_state.ts[tc].req.cmd_len) && (i < HA_MAX_CMD); i++) { - ha_state.ts[tc].req.cmd[i] = pread_b(ptr + i, BUS_PER); - } - - ha_state.ts[tc].req.op = ha_state.ts[tc].req.cmd[0]; - } -} - -static SIM_INLINE void ha_cmd_prep(uint8 tc, uint8 op, uint8 subdev, t_bool express) -{ - ha_state.ts[tc].pending = TRUE; - ha_state.ts[tc].rep.op = op; - ha_state.ts[tc].rep.subdev = subdev; - ha_state.ts[tc].rep.status = CIO_FAILURE; - ha_state.ts[tc].rep.ssb = 0; - ha_state.ts[tc].rep.len = 0; - ha_state.ts[tc].rep.addr = 0; - - if (ha_state.pump_state == PUMP_COMPLETE) { - ha_state.ts[tc].rep.op |= 0x80; - } - - if (ha_state.frq) { - ha_state.ts[tc].rep.type = HA_JOB_QUICK; - } else if (express) { - ha_state.ts[tc].rep.type = HA_JOB_EXPRESS; - } else { - ha_state.ts[tc].rep.type = HA_JOB_FULL; - } -} - -static void ha_cmd(uint8 op, uint8 subdev, uint32 addr, int32 len, t_bool express) -{ - int32 i, block; - UNIT *uptr; - SCSI_DEV *dev; - uint8 dsd_tc, tc; - - sim_debug(HA_TRACE, &ha_dev, - "[ha_cmd] --------------------------[START]---------------------------------\n"); - sim_debug(HA_TRACE, &ha_dev, - "[ha_cmd] op=%02x (%d), subdev=%02x, addr=%08x, len=%d\n", - op, op, subdev, addr, len); - - switch (op) { - case CIO_DLM: - tc = HA_SCSI_ID; - ha_cmd_prep(tc, op, subdev, express); - - for (i = 0; i < len; i++) { - ha_crc = cio_crc32_shift(ha_crc, pread_b(addr + i, BUS_PER)); - } - - sim_debug(HA_TRACE, &ha_dev, - "[ha_cmd] SCSI Download Memory: bytecnt=%04x " - "addr=%08x return_addr=%08x subdev=%02x (CRC=%08x)\n", - len, addr, addr, subdev, ha_crc); - ha_state.ts[tc].rep.status = CIO_SUCCESS; - sim_activate_abs(cio_unit, 1000); - break; - case CIO_FCF: - tc = HA_SCSI_ID; - ha_cmd_prep(tc, op, subdev, express); - - sim_debug(HA_TRACE, &ha_dev, - "[ha_cmd] SCSI Force Function Call. (CRC=%08x)\n", - ha_crc); - if (ha_crc == DIAG_CRC_1) { - pwrite_h(0x200f000, 0x1, BUS_PER); /* Test success */ - pwrite_h(0x200f002, 0x0, BUS_PER); /* Test Number */ - pwrite_h(0x200f004, 0x0, BUS_PER); /* Actual */ - pwrite_h(0x200f006, 0x0, BUS_PER); /* Expected */ - pwrite_b(0x200f008, 0x1, BUS_PER); /* Success flag again */ - } - - cio[ha_state.slot].sysgen_s = 0; - ha_state.ts[tc].rep.status = CIO_SUCCESS; - sim_activate_abs(cio_unit, 1000); - break; - case CIO_DSD: - tc = HA_SCSI_ID; - ha_cmd_prep(tc, op, subdev, express); - - sim_debug(HA_TRACE, &ha_dev, - "[ha_cmd] SCSI DSD - %d CONFIGURED DEVICES (writing to addr %08x).\n", - ha_subdev_cnt, addr); - - pwrite_h(addr, ha_subdev_cnt, BUS_PER); - - for (i = 0; i < ha_subdev_cnt; i++) { - addr += 2; - - dsd_tc = ha_subdev_tab[i]; - - if (dsd_tc < 0) { - pwrite_h(addr, 0, BUS_PER); - continue; - } - - uptr = &ha_unit[dsd_tc]; - - dev = (SCSI_DEV *)uptr->up7; - - sim_debug(HA_TRACE, &ha_dev, - "[ha_cmd] [DSD] Probing subdev %d, target %d, devtype %d\n", - i, dsd_tc, dev->devtype); - - switch(dev->devtype) { - case SCSI_DISK: - sim_debug(HA_TRACE, &ha_dev, - "[ha_cmd] [DSD] Subdev %d is DISK (writing to addr %08x)\n", - i, addr); - pwrite_h(addr, HA_DSD_DISK, BUS_PER); - break; - case SCSI_TAPE: - sim_debug(HA_TRACE, &ha_dev, - "[ha_cmd] [DSD] Subdev %d is TAPE (writing to addr %08x)\n", - i, addr); - pwrite_h(addr, HA_DSD_TAPE, BUS_PER); - break; - default: - sim_debug(HA_TRACE, &ha_dev, - "[ha_cmd] [DSD] Warning: No device type for subdev %d (Writing to addr %08x)\n", - i, addr); - pwrite_h(addr, 0, BUS_PER); - break; - } - } - - ha_state.ts[tc].rep.status = CIO_SUCCESS; - - sim_activate_abs(cio_unit, 1000); - - break; - case HA_BOOT: - tc = ha_subdev_tab[subdev & 7]; - ha_cmd_prep(tc, op, subdev, express); - - - sim_debug(HA_TRACE, &ha_dev, - "[ha_cmd] TARGET %d BOOTING.\n", - tc); - - if (tc < 0) { - ha_state.ts[tc].rep.status = CIO_TIMEOUT; - sim_activate_abs(cio_unit, 1000); - return; - } - - uptr = &ha_unit[tc]; - - if (!(uptr->flags & UNIT_ATT)) { - sim_debug(HA_TRACE, &ha_dev, - "[ha_cmd] TARGET %d NOT ATTACHED.\n", - tc); - ha_state.ts[tc].rep.status = CIO_TIMEOUT; - sim_activate_abs(cio_unit, 1000); - return; - } - - dev = (SCSI_DEV *)uptr->up7; - - switch(dev->devtype) { - case SCSI_DISK: - ha_boot_disk(uptr, tc); - break; - case SCSI_TAPE: - ha_boot_tape(uptr, tc); - break; - default: - sim_debug(HA_TRACE, &ha_dev, - "[HA_BOOT] Cannot boot target %d (not disk or tape).\n", - tc); - break; - } - - ha_state.ts[tc].rep.status = CIO_SUCCESS; - sim_activate_abs(cio_unit, 1000); - break; - case HA_READ_BLK: - tc = ha_subdev_tab[subdev & 7]; - ha_cmd_prep(tc, op, subdev, express); - - sim_debug(HA_TRACE, &ha_dev, - "[ha_cmd] SUBDEV %d TARGET %d READ BLOCK (BLOCK 0x%08x TO ADDR 0x%08x)\n", - subdev, tc, pread_w(addr, BUS_PER), pread_w(addr + 4, BUS_PER)); - - sim_debug(HA_TRACE, &ha_dev, - "[ha_read_blk] addr = %08x\n", - addr); - sim_debug(HA_TRACE, &ha_dev, - "[ha_read_blk] %08x = %08x\n", - addr, pread_w(addr, BUS_PER)); - sim_debug(HA_TRACE, &ha_dev, - "[ha_read_blk] %08x = %08x\n", - addr + 4, pread_w(addr + 4, BUS_PER)); - sim_debug(HA_TRACE, &ha_dev, - "[ha_read_blk] %08x = %08x\n", - addr + 8, pread_w(addr + 8, BUS_PER)); - sim_debug(HA_TRACE, &ha_dev, - "[ha_read_blk] %08x = %08x\n", - addr + 12, pread_w(addr + 12, BUS_PER)); - sim_debug(HA_TRACE, &ha_dev, - "[ha_read_blik] %08x = %08x\n", - addr + 16, pread_w(addr + 16, BUS_PER)); - - - if (tc < 0) { - ha_state.ts[tc].rep.status = CIO_TIMEOUT; - sim_activate_abs(cio_unit, 1000); - return; - } - - uptr = &ha_unit[tc]; - - if (!(uptr->flags & UNIT_ATT)) { - ha_state.ts[tc].rep.status = CIO_TIMEOUT; - sim_activate_abs(cio_unit, 1000); - return; - } - - dev = (SCSI_DEV *)uptr->up7; - block = pread_w(addr, BUS_PER); /* Logical block we've been asked to read */ - addr = pread_w(addr + 4, BUS_PER); /* Dereference the pointer to the destination */ - - switch(dev->devtype) { - case SCSI_TAPE: - ha_read_block_tape(uptr, addr, tc); - break; - case SCSI_DISK: - ha_read_block_disk(uptr, addr, tc, block); - break; - default: - sim_debug(HA_TRACE, &ha_dev, - "[HA_READ_BLOCK] Cannot read block %d on target %d (not disk or tape)\n", - block, tc); - break; - } - - ha_state.ts[tc].rep.status = CIO_SUCCESS; - sim_activate_abs(cio_unit, 1000); - - break; - case HA_WRITE_BLK: - tc = ha_subdev_tab[subdev & 7]; - ha_cmd_prep(tc, op, subdev, express); - - sim_debug(HA_TRACE, &ha_dev, - "[ha_cmd] SUBDEV %d TARGET %d WRITE BLOCK (BLOCK 0x%08x FROM ADDR 0x%08x)\n", - subdev, tc, pread_w(addr, BUS_PER), pread_w(addr + 4, BUS_PER)); - - sim_debug(HA_TRACE, &ha_dev, - "[ha_write_blk] addr = %08x\n", - addr); - sim_debug(HA_TRACE, &ha_dev, - "[ha_write_blk] %08x = %08x\n", - addr, pread_w(addr, BUS_PER)); - sim_debug(HA_TRACE, &ha_dev, - "[ha_write_blk] %08x = %08x\n", - addr + 4, pread_w(addr + 4, BUS_PER)); - - if (tc < 0) { - ha_state.ts[tc].rep.status = CIO_TIMEOUT; - sim_activate_abs(cio_unit, 1000); - return; - } - - uptr = &ha_unit[tc]; - - if (!(uptr->flags & UNIT_ATT)) { - ha_state.ts[tc].rep.status = CIO_TIMEOUT; - sim_activate_abs(cio_unit, 1000); - return; - } - - dev = (SCSI_DEV *)uptr->up7; - block = pread_w(addr, BUS_PER); /* Logical block we've been asked to write */ - addr = pread_w(addr + 4, BUS_PER); /* Dereference the pointer to the source */ - - switch(dev->devtype) { - case SCSI_DISK: - ha_write_block_disk(uptr, addr, tc, block); - break; - default: - sim_debug(HA_TRACE, &ha_dev, - "[ha_write_blk] Cannot write block %d on target %d (not disk)\n", - block, tc); - break; - } - - ha_state.ts[tc].rep.status = CIO_SUCCESS; - sim_activate_abs(cio_unit, 1000); - break; - case HA_CNTRL: - tc = FC_TC(subdev); - ha_cmd_prep(tc, op, subdev, express); - - sim_debug(HA_TRACE, &ha_dev, - "[ha_cmd] SCSI CONTROL (subdev=%02x addr=%08x)\n", - subdev, addr); - - ha_build_req(tc, subdev, express); - ha_ctrl(tc); - sim_activate_abs(cio_unit, 1000); - break; - case HA_VERS: - /* - * Get Host Adapter Version - */ - tc = HA_SCSI_ID; - ha_cmd_prep(tc, op, subdev, express); - - sim_debug(HA_TRACE, &ha_dev, - "[ha_cmd] SCSI GET VERSION (addr=%08x len=%08x)\n", - addr, len); - - pwrite_w(addr, HA_VERSION, BUS_PER); - ha_state.ts[tc].rep.status = CIO_SUCCESS; - sim_activate_abs(cio_unit, 1000); - - break; - case HA_DL_EEDT: - /* - * This is a request to download the Extended Equipped Device - * Table from the host adapter to the 3B2 main memory. - */ - tc = HA_SCSI_ID; - ha_cmd_prep(tc, op, subdev, express); - - sim_debug(HA_TRACE, &ha_dev, - "[ha_cmd] SCSI DOWNLOAD EDT (%d bytes to address %08x)\n", - len, addr); - - for (i = 0; i < len; i++) { - pwrite_b(addr + i, ha_state.edt[i], BUS_PER); - } - - ha_state.ts[tc].rep.status = CIO_SUCCESS; - - sim_activate_abs(cio_unit, 1000); - - break; - case HA_UL_EEDT: - /* - * This is a request to upload the Extended Equipped Device - * Table from the 3B2 main memory to the host adapter - */ - - tc = HA_SCSI_ID; - ha_cmd_prep(tc, op, subdev, express); - - sim_debug(HA_TRACE, &ha_dev, - "[ha_cmd] SCSI UPLOAD EDT (%d bytes from address %08x)\n", - len, addr); - - for (i = 0; i < len; i++) { - ha_state.edt[i] = pread_b(addr + i, BUS_PER); - } - - ha_state.ts[tc].rep.status = CIO_SUCCESS; - - sim_activate_abs(cio_unit, 1000); - - break; - case HA_EDSD: - /* - * This command is used to determine which TCs are attached to - * the SCSI bus, and what LUNs they support. - */ - tc = HA_SCSI_ID; - ha_cmd_prep(tc, op, subdev, express); - - sim_debug(HA_TRACE, &ha_dev, - "[ha_cmd] SCSI EXTENDED DSD.\n"); - - ha_state.ts[tc].rep.status = CIO_SUCCESS; - ha_state.ts[tc].rep.addr = addr; - ha_state.ts[tc].rep.len = 9; - - /* - * Loop over each SCSI ID and configure LUNs. - */ - for (i = 0; i < 8; i++) { - uptr = &ha_unit[i]; - /* - * TODO: The byte being written here is a bit mask of - * equipped luns. e.g., - * - * - 0x01 means LUN 0 is equipped, - * - 0x80 means LUN 7 is equipped, - * - 0x33 means LUNs 0, 1, 4, and 5 are equipped. - * - * For now, we only support one LUN per target, and it's - * always LUN 0. - */ - pwrite_b(addr + i, (uptr->flags & UNIT_ATT) ? 1 : 0, BUS_PER); - } - - pwrite_b(addr + 8, HA_SCSI_ID, BUS_PER); /* ID of the card */ - - sim_activate_abs(cio_unit, 1000); - - break; - case HA_RESET: - tc = HA_SCSI_ID; - ha_cmd_prep(tc, op, subdev, express); - - scsi_reset(&ha_bus); - - sim_debug(HA_TRACE, &ha_dev, - "[ha_cmd] SCSI RESET.\n"); - - ha_state.ts[tc].rep.status = CIO_SUCCESS; - ha_state.ts[tc].rep.addr = addr; - ha_state.ts[tc].rep.len = 0; - - sim_activate_abs(cio_unit, 1000); - break; - default: - tc = HA_SCSI_ID; - ha_cmd_prep(tc, op, subdev, express); - - sim_debug(HA_TRACE, &ha_dev, - "*** SCSI WARNING: UNHANDLED OPCODE 0x%02x\n", - op); - - ha_state.ts[tc].rep.status = CIO_FAILURE; - - sim_activate_abs(cio_unit, 1000); - } - - sim_debug(HA_TRACE, &ha_dev, - "[ha_cmd] ---------------------------[END]----------------------------------\n"); - -} - -/* - * Handle a raw SCSI control message. - */ -void ha_ctrl(uint8 tc) -{ - volatile t_bool txn_done; - uint32 i, j; - uint32 plen, ha_ptr; - uint32 in_len, out_len; - uint8 lu, status; - uint8 msgi_buf[64]; - uint32 msgi_len; - uint32 to_read; - - sim_debug(HA_TRACE, &ha_dev, - "[ha_ctrl] [HA_REQ] TC=%d LU=%d TIMEOUT=%d DLEN=%d\n", - ha_state.ts[tc].req.tc, - ha_state.ts[tc].req.lu, - ha_state.ts[tc].req.timeout, - ha_state.ts[tc].req.dlen); - - sim_debug(HA_TRACE, &ha_dev, - "[ha_ctrl] [HA_REQ] CMD_LEN=%d CMD=<%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x>\n", - ha_state.ts[tc].req.cmd_len, - ha_state.ts[tc].req.cmd[0], ha_state.ts[tc].req.cmd[1], - ha_state.ts[tc].req.cmd[2], ha_state.ts[tc].req.cmd[3], - ha_state.ts[tc].req.cmd[4], ha_state.ts[tc].req.cmd[5], - ha_state.ts[tc].req.cmd[6], ha_state.ts[tc].req.cmd[7], - ha_state.ts[tc].req.cmd[8], ha_state.ts[tc].req.cmd[9]); - - in_len = out_len = 0; - - /* - * These ops need special handling. - */ - switch(ha_state.ts[tc].req.op) { - case HA_TESTRDY: - /* Fail early if LU is set */ - if (ha_state.ts[tc].req.lu) { - HA_STAT(tc, HA_CKCON, CIO_TIMEOUT); - return; - } - break; - case HA_FORMAT: /* Not yet handled by sim_scsi library */ - case HA_VERIFY: /* Not yet handled by sim_scsi library */ - /* Just mimic success */ - HA_STAT(tc, HA_GOOD, CIO_SUCCESS); - return; - } - - /* Get the bus's attention */ - if (!scsi_arbitrate(&ha_bus, HA_SCSI_ID)) { - HA_STAT(tc, HA_CKCON, CIO_TIMEOUT); - return; - } - - scsi_set_atn(&ha_bus); - - if (!scsi_select(&ha_bus, ha_state.ts[tc].req.tc)) { - HA_STAT(tc, HA_CKCON, CIO_TIMEOUT); - scsi_release(&ha_bus); - return; - } - - /* Select the correct LU */ - lu = 0x80 | ha_state.ts[tc].req.lu; - scsi_write(&ha_bus, &lu, 1); - - txn_done = FALSE; - - /* TODO: Fix this. Work around a bug in command length. The host - * occasionally sends a command length of 8 for 6-byte SCSI - * commands. The sim_scsi library knows to only consume 6 bytes, - * which leaves the buffer in a bad state. */ - if (ha_state.ts[tc].req.cmd_len == 8) { - ha_state.ts[tc].req.cmd_len = 6; - } - - while (!txn_done) { - switch(ha_bus.phase) { - case SCSI_CMD: - plen = scsi_write(&ha_bus, ha_state.ts[tc].req.cmd, ha_state.ts[tc].req.cmd_len); - if (plen < ha_state.ts[tc].req.cmd_len) { - HA_STAT(tc, HA_CKCON, CIO_SUCCESS); - scsi_release(&ha_bus); - return; - } - break; - case SCSI_DATI: - /* This is a read */ - in_len = scsi_read(&ha_bus, ha_buf, HA_MAXFR); - - sim_debug(HA_TRACE, &ha_dev, - "[ha_ctrl] SCSI_DATI: Consumed %d (0x%X) bytes to ha_buf in SCSI read.\n", - in_len, in_len); - - /* We need special handling based on the op code */ - switch(ha_state.ts[tc].req.op) { - case HA_READ: - case HA_READEXT: - ha_ptr = 0; - - for (i = 0; i < ha_state.ts[tc].req.dlen; i++) { - /* - * Consume the lesser of: - * - The total bytes we consumed, or: - * - The length of the current block - */ - to_read = MIN(ha_state.ts[tc].req.daddr[i].len, in_len); - - sim_debug(HA_TRACE, &ha_dev, - "[(%02x) TC%d,LU%d] DATI: Processing %d bytes to address %08x...\n", - ha_state.ts[tc].req.op, - ha_state.ts[tc].req.tc, - ha_state.ts[tc].req.lu, - to_read, - ha_state.ts[tc].req.daddr[i].addr); - - for (j = 0; j < to_read; j++) { - pwrite_b(ha_state.ts[tc].req.daddr[i].addr + j, ha_buf[ha_ptr++], BUS_PER); - } - - if (in_len >= to_read) { - in_len -= to_read; - } else { - /* Nothing left to write */ - break; - } - } - - break; - default: - sim_debug(HA_TRACE, &ha_dev, - "[(%02x) TC%d,LU%d] DATI: Processing %d bytes to address %08x...\n", - ha_state.ts[tc].req.op, ha_state.ts[tc].req.tc, - ha_state.ts[tc].req.lu, in_len, - ha_state.ts[tc].req.daddr[0].addr); - for (i = 0; i < in_len; i++) { - sim_debug(HA_TRACE, &ha_dev, "[%04x] [DATI] 0x%02x\n", i, ha_buf[i]); - pwrite_b(ha_state.ts[tc].req.daddr[0].addr + i, ha_buf[i], BUS_PER); - } - - break; - } - - break; - case SCSI_DATO: - /* This is a write */ - ha_ptr = 0; - out_len = 0; - ha_state.ts[tc].rep.len = ha_state.ts[tc].req.dlen; - - for (i = 0; i < ha_state.ts[tc].req.dlen; i++) { - sim_debug(HA_TRACE, &ha_dev, - "[ha_ctrl] [%d] DATO: Writing %d bytes to ha_buf.\n", - i, ha_state.ts[tc].req.daddr[i].len); - - for (j = 0; j < ha_state.ts[tc].req.daddr[i].len; j++) { - ha_buf[ha_ptr++] = pread_b(ha_state.ts[tc].req.daddr[i].addr + j, BUS_PER); - if (ha_state.ts[tc].req.op == 0x15) { - sim_debug(HA_TRACE, &ha_dev, - "[ha_ctrl] [%d]\t\t%02x\n", - j, ha_buf[ha_ptr - 1]); - } - } - - out_len += ha_state.ts[tc].req.daddr[i].len; - } - - if (ha_state.ts[tc].req.op == HA_WRITE || ha_state.ts[tc].req.op == HA_WRTEXT) { - /* If total len is not on a block boundary, we have to - bump it up in order to write the whole block. */ - if (out_len % HA_BLKSZ) { - out_len = out_len + (HA_BLKSZ - (out_len % HA_BLKSZ)); - } - } - - scsi_write(&ha_bus, ha_buf, out_len); - - sim_debug(HA_TRACE, &ha_dev, - "[ha_ctrl] SCSI Write of %08x (%d) bytes Complete\n", - out_len, out_len); - break; - case SCSI_STS: - scsi_read(&ha_bus, &status, 1); - sim_debug(HA_TRACE, &ha_dev, - "[ha_ctrl] STATUS BYTE: %02x\n", - status); - break; - case SCSI_MSGI: - msgi_len = scsi_read(&ha_bus, msgi_buf, 64); - sim_debug(HA_TRACE, &ha_dev, - "[ha_ctrl] MESSAGE IN LENGTH %d\n", - msgi_len); - - for (i = 0; i < msgi_len; i++) { - sim_debug(HA_TRACE, &ha_dev, - "[ha_ctrl] MSGI[%02d] = %02x\n", - i, msgi_buf[i]); - } - - txn_done = TRUE; - break; - } - } - - if (ha_bus.sense_key || ha_bus.sense_code) { - sim_debug(HA_TRACE, &ha_dev, - "[ha_ctrl] SENSE KEY=%d CODE=%d INFO=%d, CKCON.\n", - ha_bus.sense_key, ha_bus.sense_code, ha_bus.sense_info); - HA_STAT(tc, HA_CKCON, 0x60); - } else { - sim_debug(HA_TRACE, &ha_dev, "[ha_ctrl] NO SENSE INFO.\n"); - HA_STAT(tc, HA_GOOD, CIO_SUCCESS); - } - - /* Release the bus */ - scsi_release(&ha_bus); -} - -void ha_fcm_express(uint8 tc) -{ - uint32 cqp, cqs; - - cqp = cio[ha_state.slot].cqp; - cqs = cio[ha_state.slot].cqs; - - /* Write the fast completion entry. */ - pwrite_b(cqp + cq_offset, ha_state.ts[tc].rep.status, BUS_PER); - pwrite_b(cqp + cq_offset + 1, ha_state.ts[tc].rep.op, BUS_PER); - pwrite_b(cqp + cq_offset + 2, ha_state.ts[tc].rep.subdev, BUS_PER); - pwrite_b(cqp + cq_offset + 3, ha_state.ts[tc].rep.ssb, BUS_PER); - - sim_debug(HA_TRACE, &ha_dev, - "[ha_fcm_express] stat=%02x, op=%02x (%d), cq_index=%d target=%d, lun=%d, ssb=%02x\n", - ha_state.ts[tc].rep.status, ha_state.ts[tc].rep.op, ha_state.ts[tc].rep.op, - (cq_offset / 4), - FC_TC(ha_state.ts[tc].rep.subdev), FC_LU(ha_state.ts[tc].rep.subdev), - ha_state.ts[tc].rep.ssb); - - if (ha_state.pump_state == PUMP_COMPLETE && cqs > 0) { - cq_offset = (cq_offset + 4) % (cqs * 4); - } else { - cq_offset = 0; - } -} - -t_stat ha_set_type(UNIT *uptr, int32 val, CONST char *cptr, void *desc) -{ - if (val < 0 || cptr || val > HA_MAX_DTYPE) { - return SCPE_ARG; - } - - if (uptr->flags & UNIT_ATT) { - return SCPE_ALATT; - } - - uptr->flags = (uptr->flags & ~UNIT_DTYPE) | (val << UNIT_V_DTYPE); - if (val == ST120_DTYPE) { - uptr->flags |= SCSI_QIC; - } - uptr->capac = (t_addr) ha_tab[val].lbn; - scsi_set_unit(&ha_bus, uptr, &ha_tab[val]); - scsi_reset_unit(uptr); - return SCPE_OK; -} - -t_stat ha_show_type(FILE *st, UNIT *uptr, int32 val, CONST void *desc) -{ - fprintf(st, "%s", ha_tab[GET_DTYPE(uptr->flags)].name); - - return SCPE_OK; -} +/* 3b2_scsi.c: CM195W SCSI Controller CIO Card + + Copyright (c) 2020-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#include "3b2_scsi.h" + +#include "sim_scsi.h" +#include "sim_tape.h" + +#include "3b2_cpu.h" +#include "3b2_io.h" +#include "3b2_mem.h" + +#define DIAG_CRC_1 0x271b114c +#define PUMP_CRC 0x201b3617 + +#define HA_SCSI_ID 0 +#define HA_MAXFR (1u << 16) + +static void ha_cmd(uint8 op, uint8 subdev, uint32 addr, + int32 len, t_bool express); +static void ha_build_req(uint8 tc, uint8 subdev, t_bool express); +static void ha_ctrl(uint8 tc); + + +HA_STATE ha_state; +SCSI_BUS ha_bus; +uint8 *ha_buf; +int8 ha_subdev_tab[8]; /* Map of subdevice to SCSI target */ +uint8 ha_subdev_cnt; +uint32 ha_crc = 0; +uint32 cq_offset = 0; +t_bool ha_conf = FALSE; + +static struct scsi_dev_t ha_tab[] = { + HA_DISK(SD155), + HA_DISK(SD300), + HA_DISK(SD327), + HA_DISK(SD630), + HA_TAPE(ST120) +}; + +#define SCSI_U_FLAGS (UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_DIS+ \ + UNIT_ROABLE+(SD155_DTYPE << UNIT_V_DTYPE)) + +UNIT ha_unit[] = { + { UDATA (&ha_svc, UNIT_DIS, 0) }, /* SCSI ID 0 - the host adapter */ + { UDATA (&ha_svc, SCSI_U_FLAGS, HA_SIZE(SD155)) }, /* SCSI ID 1 */ + { UDATA (&ha_svc, SCSI_U_FLAGS, HA_SIZE(SD155)) }, /* SCSI ID 2 */ + { UDATA (&ha_svc, SCSI_U_FLAGS, HA_SIZE(SD155)) }, /* SCSI ID 3 */ + { UDATA (&ha_svc, SCSI_U_FLAGS, HA_SIZE(SD155)) }, /* SCSI ID 4 */ + { UDATA (&ha_svc, SCSI_U_FLAGS, HA_SIZE(SD155)) }, /* SCSI ID 5 */ + { UDATA (&ha_svc, SCSI_U_FLAGS, HA_SIZE(SD155)) }, /* SCSI ID 6 */ + { UDATA (&ha_svc, SCSI_U_FLAGS, HA_SIZE(SD155)) }, /* SCSI ID 7 */ +}; + +static UNIT *cio_unit = &ha_unit[0]; + +MTAB ha_mod[] = { + { SCSI_WLK, 0, NULL, "WRITEENABLED", + &scsi_set_wlk, NULL, NULL, "Write enable disk drive" }, + { SCSI_WLK, SCSI_WLK, NULL, "LOCKED", + &scsi_set_wlk, NULL, NULL, "Write lock disk drive" }, + { MTAB_XTD|MTAB_VUN, 0, "WRITE", NULL, + NULL, &scsi_show_wlk, NULL, "Display drive writelock status" }, + { MTAB_XTD|MTAB_VUN, SD155_DTYPE, NULL, "SD155", + &ha_set_type, NULL, NULL, "Set 155MB Disk Type" }, + { MTAB_XTD|MTAB_VUN, SD300_DTYPE, NULL, "SD300", + &ha_set_type, NULL, NULL, "Set 300MB Disk Type" }, + { MTAB_XTD|MTAB_VUN, SD327_DTYPE, NULL, "SD327", + &ha_set_type, NULL, NULL, "Set 327MB Disk Type" }, + { MTAB_XTD|MTAB_VUN, SD630_DTYPE, NULL, "SD630", + &ha_set_type, NULL, NULL, "Set 630MB Disk Type" }, + { MTAB_XTD|MTAB_VUN, ST120_DTYPE, NULL, "ST120", + &ha_set_type, NULL, NULL, "Set Wangtek 120MB Tape Type" }, + { MTAB_XTD|MTAB_VUN, 0, "TYPE", NULL, + NULL, &ha_show_type, NULL, "Display device type" }, + { MTAB_XTD|MTAB_VUN, 0, "FORMAT", "FORMAT", + &scsi_set_fmt, &scsi_show_fmt, NULL, "Set/Display unit format" }, + { 0 } +}; + +#define HA_TRACE 1 + +static DEBTAB ha_debug[] = { + { "TRACE", HA_TRACE, "Call Trace" }, + { "SCMD", SCSI_DBG_CMD, "SCSI commands"}, + { "SBUS", SCSI_DBG_BUS, "SCSI bus activity" }, + { "SMSG", SCSI_DBG_MSG, "SCSI messages" }, + { "SDSK", SCSI_DBG_DSK, "SCSI disk activity" }, + { "STAP", MTSE_DBG_API, "SCSI tape activity" }, + { NULL } +}; + +DEVICE ha_dev = { + "SCSI", /* name */ + ha_unit, /* units */ + NULL, /* registers */ + ha_mod, /* modifiers */ + 8, /* #units */ + 16, /* address radix */ + 32, /* address width */ + 1, /* address incr. */ + 16, /* data radix */ + 8, /* data width */ + NULL, /* examine routine */ + NULL, /* deposit routine */ + &ha_reset, /* reset routine */ + NULL, /* boot routine */ + &ha_attach, /* attach routine */ + &ha_detach, /* detach routine */ + NULL, /* context */ + DEV_DEBUG|DEV_DISK|DEV_SECTORS, /* flags */ + 0, /* debug control flags */ + ha_debug, /* debug flag names */ + NULL, /* memory size change */ + NULL, /* logical name */ + NULL, /* help routine */ + NULL, + NULL, + NULL, +}; + +void ha_cio_reset(uint8 slot) +{ + sim_debug(HA_TRACE, &ha_dev, + "Handling CIO reset\n"); + ha_state.pump_state = PUMP_NONE; + ha_crc = 0; +} + +t_stat ha_reset(DEVICE *dptr) +{ + uint8 slot; + uint32 t, dtyp; + UNIT *uptr; + t_stat r; + + ha_state.pump_state = PUMP_NONE; + + if (ha_buf == NULL) { + ha_buf = (uint8 *)calloc(HA_MAXFR, sizeof(uint8)); + } + + r = scsi_init(&ha_bus, HA_MAXFR); + + if (r != SCPE_OK) { + return r; + } + + ha_bus.dptr = dptr; + + scsi_reset(&ha_bus); + + for (t = 0; t < 8; t++) { + uptr = dptr->units + t; + if (t == HA_SCSI_ID) { + uptr->flags = UNIT_DIS; + } + scsi_add_unit(&ha_bus, t, uptr); + dtyp = GET_DTYPE(uptr->flags); + scsi_set_unit(&ha_bus, uptr, &ha_tab[dtyp]); + scsi_reset_unit(uptr); + } + + if (dptr->flags & DEV_DIS) { + cio_remove_all(HA_ID); + ha_conf = FALSE; + return SCPE_OK; + } + + if (!ha_conf) { + r = cio_install(HA_ID, "SCSI", HA_IPL, + &ha_express, &ha_full, &ha_sysgen, &ha_cio_reset, + &slot); + if (r != SCPE_OK) { + return r; + } + ha_state.slot = slot; + ha_conf = TRUE; + } + + return SCPE_OK; +} + +static void ha_calc_subdevs() +{ + uint32 tc; + UNIT *uptr; + + ha_subdev_cnt = 0; + + memset(ha_subdev_tab, -1, 8); + + for (tc = 0; tc < 8; tc++) { + uptr = &ha_unit[tc]; + if (uptr->flags & UNIT_ATT) { + ha_subdev_tab[ha_subdev_cnt++] = tc; + } + } +} + +t_stat ha_attach(UNIT *uptr, CONST char *cptr) +{ + t_stat r; + r = scsi_attach(uptr, cptr); + ha_calc_subdevs(); + return r; +} + +t_stat ha_detach(UNIT *uptr) +{ + t_stat r; + r = scsi_detach(uptr); + ha_calc_subdevs(); + return r; +} + +t_stat ha_svc(UNIT *uptr) +{ + cio_entry cqe; + uint8 capp_data[CAPP_LEN] = {0}; + int8 i, tc = -1; + uint8 svc_req = 0; + ha_req *req = NULL; + ha_resp *rep = NULL; + + sim_debug(HA_TRACE, &ha_dev, + "[ha_svc] SERVICE ROUTINE\n"); + + /* Determine how many targets need servicing */ + for (i = 0; i < 8; i++) { + if (ha_state.ts[i].pending) { + if (req == NULL && rep == NULL) { + tc = i; + sim_debug(HA_TRACE, &ha_dev, + "[ha_svc] Found a job for target %d\n", + tc); + req = &ha_state.ts[i].req; + rep = &ha_state.ts[i].rep; + ha_state.ts[i].pending = FALSE; + } + svc_req++; + } + } + + if (tc == -1) { + return SCPE_OK; + } + + switch(rep->type) { + case HA_JOB_QUICK: + ha_fcm_express(tc); + + sim_debug(HA_TRACE, &ha_dev, + "[ha_svc] FAST MODE CQ: target=%d status=%02x op=%02x subdev=%02x ssb=%02x\n", + tc, rep->status, rep->op, rep->subdev, rep->ssb); + break; + case HA_JOB_EXPRESS: + case HA_JOB_FULL: + cqe.byte_count = rep->len; + cqe.opcode = rep->status; /* Yes, status, not opcode! */ + cqe.subdevice = rep->subdev; + cqe.address = rep->addr; + + sim_debug(HA_TRACE, &ha_dev, + "[ha_svc] CQE: target=%d, byte_count=%04x, opcode=%02x, subdevice=%02x, addr=%08x\n", + tc, cqe.byte_count, cqe.opcode, cqe.subdevice, cqe.address); + + if (rep->type == HA_JOB_EXPRESS) { + sim_debug(HA_TRACE, &ha_dev, + "[ha_svc] EXPRESS MODE CQ: target=%d, byte_count=%02x " + "op=%02x subdev=%02x address=%08x\n", + tc, cqe.byte_count, cqe.opcode, cqe.subdevice, cqe.address); + + cio_cexpress(ha_state.slot, SCQCESIZE, &cqe, capp_data); + } else { + sim_debug(HA_TRACE, &ha_dev, + "[ha_svc] FULL MODE CQ: target=%d, status=%02x op=%02x subdev=%02x ssb=%02x\n", + tc, rep->status, rep->op, rep->subdev, rep->ssb); + + cio_cqueue(ha_state.slot, 0, SCQCESIZE, &cqe, capp_data); + } + break; + } + + sim_debug(HA_TRACE, &ha_dev, + "[ha_svc] IRQ for board %d (VEC=%d). PSW_CUR_IPL=%d\n", + ha_state.slot, cio[ha_state.slot].ivec, PSW_CUR_IPL); + + CIO_SET_INT(ha_state.slot); + + /* There's more work to do after this job is done */ + if (svc_req > 1) { + sim_debug(HA_TRACE, &ha_dev, + "[ha_svc] Scheduling job to handle another %d open requests\n", + svc_req - 1); + sim_activate_abs(uptr, 1000); + } + + return SCPE_OK; +} + +void ha_sysgen(uint8 slot) +{ + uint32 sysgen_p; + uint32 alert_buf_p; + + cq_offset = 0; + + sim_debug(HA_TRACE, &ha_dev, "[ha_sysgen] Handling Sysgen.\n"); + sim_debug(HA_TRACE, &ha_dev, "[ha_sysgen] rqp=%08x\n", cio[slot].rqp); + sim_debug(HA_TRACE, &ha_dev, "[ha_sysgen] cqp=%08x\n", cio[slot].cqp); + sim_debug(HA_TRACE, &ha_dev, "[ha_sysgen] rqs=%d\n", cio[slot].rqs); + sim_debug(HA_TRACE, &ha_dev, "[ha_sysgen] cqs=%d\n", cio[slot].cqs); + sim_debug(HA_TRACE, &ha_dev, "[ha_sysgen] ivec=%d\n", cio[slot].ivec); + sim_debug(HA_TRACE, &ha_dev, "[ha_sysgen] no_rque=%d\n", cio[slot].no_rque); + + sysgen_p = pread_w(SYSGEN_PTR, BUS_PER); + alert_buf_p = pread_w(sysgen_p + 12, BUS_PER); + sim_debug(HA_TRACE, &ha_dev, "[ha_sysgen] alert_bfr=%08x\n", alert_buf_p); + + ha_state.frq = (cio[slot].no_rque == 0); + + ha_state.ts[HA_SCSI_ID].rep.type = ha_state.frq ? HA_JOB_QUICK : HA_JOB_EXPRESS; + ha_state.ts[HA_SCSI_ID].rep.addr = 0; + ha_state.ts[HA_SCSI_ID].rep.len = 0; + ha_state.ts[HA_SCSI_ID].rep.status = 3; + ha_state.ts[HA_SCSI_ID].rep.op = 0; + ha_state.ts[HA_SCSI_ID].pending = TRUE; + + if (ha_crc == PUMP_CRC) { + sim_debug(HA_TRACE, &ha_dev, + "[ha_sysgen] PUMP: NEW STATE = PUMP_SYSGEN\n"); + ha_state.pump_state = PUMP_SYSGEN; + } else { + sim_debug(HA_TRACE, &ha_dev, + "[ha_sysgen] PUMP: NEW STATE = PUMP_NONE\n"); + ha_state.pump_state = PUMP_NONE; + } + + sim_activate_abs(cio_unit, 1000); +} + +void ha_fast_queue_check() +{ + uint8 busy, op, subdev; + uint32 addr, len, rqp; + + rqp = cio[ha_state.slot].rqp; + + busy = pread_b(rqp, BUS_PER); + op = pread_b(rqp + 1, BUS_PER); + subdev = pread_b(rqp + 2, BUS_PER); + /* 4-byte timeout value at rqp + 4 not used */ + addr = pread_w(rqp + 8, BUS_PER); + len = pread_w(rqp + 12, BUS_PER); + + if (busy == 0xff || ha_state.pump_state != PUMP_COMPLETE) { + sim_debug(HA_TRACE, &ha_dev, + "[ha_fast_queue_check] Job pending (opcode=0x%02x subdev=%02x)\n", + op, subdev); + pwrite_b(rqp, 0, BUS_PER); /* Job has been taken */ + ha_cmd(op, subdev, addr, len, FALSE); + } +} + +void ha_express(uint8 slot) +{ + cio_entry rqe; + uint8 rapp_data[RAPP_LEN] = {0}; + + if (ha_state.frq) { + ha_fast_queue_check(); + } else { + cio_rexpress(slot, SCQRESIZE, &rqe, rapp_data); + + sim_debug(HA_TRACE, &ha_dev, + "[ha_express] Handling Express Request. subdev=%02x\n", + rqe.subdevice); + + ha_cmd(rqe.opcode, rqe.subdevice, rqe.address, rqe.byte_count, TRUE); + } +} + +void ha_full(uint8 slot) +{ + sim_debug(HA_TRACE, &ha_dev, + "[ha_full] Handling Full Request (INT3)\n"); + + if (ha_state.pump_state == PUMP_SYSGEN) { + sim_debug(HA_TRACE, &ha_dev, + "[ha_full] PUMP: NEW STATE = PUMP_COMPLETE\n"); + + ha_state.pump_state = PUMP_COMPLETE; + } + + if (ha_state.frq) { + ha_fast_queue_check(); + } else { + sim_debug(HA_TRACE, &ha_dev, + "[ha_full] NON_FRQ NOT HANDLED\n"); + } +} + +static void ha_boot_disk(UNIT *uptr, uint8 tc) +{ + t_seccnt sectsread; + t_stat r; + uint8 buf[HA_BLKSZ]; + uint32 i, boot_loc; + + /* Read in the Physical Descriptor (PD) block (block 0) */ + r = sim_disk_rdsect(uptr, 0, buf, §sread, 1); + + if (r != SCPE_OK) { + sim_debug(HA_TRACE, &ha_dev, + "[ha_boot_disk] Could not read LBA 0\n"); + HA_STAT(tc, HA_CKCON, CIO_SUCCESS); + return; + } + + /* Store the Physical Descriptor (PD) block at well-known + address 0x2004400 */ + sim_debug(HA_TRACE, &ha_dev, + "[ha_boot_disk] Storing PD block at 0x%08x.\n", + HA_PDINFO_ADDR); + for (i = 0; i < HA_BLKSZ; i++) { + pwrite_b(HA_PDINFO_ADDR + i, buf[i], BUS_PER); + } + + + /* The PD block points to the logical start of disk */ + boot_loc = ATOW(buf, HA_PDLS_OFF); + + sim_debug(HA_TRACE, &ha_dev, + "[ha_boot_disk] Logical Start is at 0x%x\n", + boot_loc); + + r = sim_disk_rdsect(uptr, boot_loc, buf, §sread, 1); + + sim_debug(HA_TRACE, &ha_dev, + "[ha_boot_disk] Storing boot block %d at 0x%08x.\n", + boot_loc, HA_BOOT_ADDR); + + for (i = 0; i < HA_BLKSZ; i++) { + pwrite_b(HA_BOOT_ADDR + i, buf[i], BUS_PER); + } + + sim_debug(HA_TRACE, &ha_dev, + "[ha_boot_disk] Done storing boot block at 0x%08x\n", + HA_BOOT_ADDR); + + HA_STAT(tc, HA_GOOD, CIO_SUCCESS); + + ha_state.ts[tc].rep.addr = HA_BOOT_ADDR; + ha_state.ts[tc].rep.len = HA_BLKSZ; +} + +static void ha_boot_tape(UNIT *uptr, uint8 tc) +{ + t_seccnt sectsread; + t_stat r; + uint8 buf[HA_BLKSZ]; + uint32 i; + + if (!(uptr->flags & UNIT_ATT)) { + sim_debug(HA_TRACE, &ha_dev, + "[ha_boot_tape] Target not attached\n"); + HA_STAT(tc, HA_CKCON, CIO_SUCCESS); + return; + } + + r = sim_tape_rewind(uptr); + + if (r != SCPE_OK) { + sim_debug(HA_TRACE, &ha_dev, + "[ha_boot_tape] Could not rewind tape\n"); + HA_STAT(tc, HA_CKCON, CIO_SUCCESS); + return; + } + + r = sim_tape_rdrecf(uptr, buf, §sread, HA_BLKSZ); /* Read block 0 */ + + if (r != SCPE_OK) { + sim_debug(HA_TRACE, &ha_dev, + "[ha_boot_tape] Could not read PD block.\n"); + HA_STAT(tc, HA_CKCON, CIO_SUCCESS); + return; + } + + for (i = 0; i < HA_BLKSZ; i++) { + pwrite_b(HA_BOOT_ADDR + i, buf[i], BUS_PER); + } + + sim_debug(HA_TRACE, &ha_dev, + "[ha_boot_tape] Transfered 512 bytes to 0x%08x\n", + HA_BOOT_ADDR); + + r = sim_tape_sprecf(uptr, §sread); /* Skip block 1 */ + + HA_STAT(tc, HA_GOOD, CIO_SUCCESS); + + ha_state.ts[tc].rep.addr = HA_BOOT_ADDR; + ha_state.ts[tc].rep.len = HA_BLKSZ; +} + +static void ha_read_block_tape(UNIT *uptr, uint32 addr, uint8 tc) +{ + t_seccnt sectsread; + t_stat r; + uint8 buf[HA_BLKSZ]; + uint32 i; + + if (!(uptr->flags & UNIT_ATT)) { + sim_debug(HA_TRACE, &ha_dev, + "[ha_read_block_tape] Target not attached\n"); + HA_STAT(tc, HA_CKCON, CIO_SUCCESS); + return; + } + + r = sim_tape_rdrecf(uptr, buf, §sread, HA_BLKSZ); + + if (r != SCPE_OK) { + sim_debug(HA_TRACE, &ha_dev, + "[ha_read_block_tape] Could not read next block.\n"); + HA_STAT(tc, HA_CKCON, CIO_SUCCESS); + return; + } + + for (i = 0; i < HA_BLKSZ; i++) { + pwrite_b(addr + i, buf[i], BUS_PER); + } + + sim_debug(HA_TRACE, &ha_dev, + "[ha_read_block_tape] Transfered 512 bytes to 0x%08x\n", + addr); + + HA_STAT(tc, HA_GOOD, CIO_SUCCESS); + + ha_state.ts[tc].rep.addr = addr; + ha_state.ts[tc].rep.len = HA_BLKSZ; +} + +static void ha_read_block_disk(UNIT *uptr, uint32 addr, uint8 tc, uint32 lba) +{ + t_seccnt sectsread; + t_stat r; + uint8 buf[HA_BLKSZ]; + uint32 i; + + r = sim_disk_rdsect(uptr, lba, buf, §sread, 1); + + if (r != SCPE_OK) { + sim_debug(HA_TRACE, &ha_dev, + "[ha_read_block_disk] Could not read block %d\n", + lba); + HA_STAT(tc, HA_CKCON, CIO_SUCCESS); + return; + } + + for (i = 0; i < HA_BLKSZ; i++) { + pwrite_b(addr + i, buf[i], BUS_PER); + } + + sim_debug(HA_TRACE, &ha_dev, + "[ha_read_block_disk] Transferred 512 bytes to 0x%08x\n", + addr); + + HA_STAT(tc, HA_GOOD, CIO_SUCCESS); + + ha_state.ts[tc].rep.addr = addr; + ha_state.ts[tc].rep.len = HA_BLKSZ; +} + +static void ha_write_block_disk(UNIT *uptr, uint32 addr, uint8 tc, uint32 lba) +{ + t_seccnt sectswritten; + t_stat r; + uint8 buf[HA_BLKSZ]; + uint32 i; + + for (i = 0 ; i < HA_BLKSZ; i++) { + buf[i] = pread_b(addr + i, BUS_PER); + } + + r = sim_disk_wrsect(uptr, lba, buf, §swritten, 1); + + if (r != SCPE_OK) { + sim_debug(HA_TRACE, &ha_dev, + "[ha_write_block_disk] Could not write block %d\n", + lba); + HA_STAT(tc, HA_CKCON, CIO_SUCCESS); + return; + } + + HA_STAT(tc, HA_GOOD, CIO_SUCCESS); + + ha_state.ts[tc].rep.addr = addr; + ha_state.ts[tc].rep.len = HA_BLKSZ; +} + +static void ha_build_req(uint8 tc, uint8 subdev, t_bool express) +{ + uint32 i, rqp, ptr, dma_lst, daddr_ptr; + uint32 len, addr; + cio_entry rqe; + uint8 rapp_data[RAPP_LEN] = {0}; + + /* + * There are two possible ways to get the SCSI command we've + * been asked to perform. + * + * 1. If this is a "fast mode" operation, then the SCSI command + * is embedded in the Fast Request Queue entry. + * + * 2. If this is a regular queue operation, then the SCSI command + * is embedded in a struct pointed to by the "address" field + * of the queue entry. + */ + + for (i = 0; i < HA_MAX_CMD; i++) { + ha_state.ts[tc].req.cmd[i] = 0; + } + + if (ha_state.frq) { + rqp = cio[ha_state.slot].rqp; + + subdev = pread_b(rqp + 2, BUS_PER); + + ha_state.ts[tc].req.tc = FC_TC(subdev); + ha_state.ts[tc].req.lu = FC_LU(subdev); + ha_state.ts[tc].req.timeout = pread_w(rqp + 4, BUS_PER); + ha_state.ts[tc].req.cmd_len = pread_h(rqp + 18, BUS_PER); + for (i = 0; i < HA_MAX_CMD; i++) { + ha_state.ts[tc].req.cmd[i] = pread_b(rqp + 20 + i, BUS_PER); + } + ha_state.ts[tc].req.op = ha_state.ts[tc].req.cmd[0]; + + /* Possible list of DMA scatter/gather addresses */ + dma_lst = pread_h(rqp + 16, BUS_PER) / 8; + + if (dma_lst) { + t_bool link; + + /* There's a list of address / lengths. Each entry is 8 + * bytes long. */ + ptr = pread_w(rqp + 8, BUS_PER); + link = FALSE; + + sim_debug(HA_TRACE, &ha_dev, + "[build_req] Building a list of scatter/gather addresses.\n"); + + daddr_ptr = 0; + + for (i = 0; (i < dma_lst) || link; i++) { + addr = pread_w(ptr, BUS_PER); + len = pread_w(ptr + 4, BUS_PER); + + if (len == 0) { + sim_debug(HA_TRACE, &ha_dev, + "[build_req] Found length of 0, bailing early.\n"); + break; /* Done early */ + } + + if (len > 0x1000) { + /* There's a new pointer in town */ + ptr = pread_w(ptr, BUS_PER); + sim_debug(HA_TRACE, &ha_dev, + "[build_req] New ptr=%08x\n", + ptr); + link = TRUE; + continue; + } + + sim_debug(HA_TRACE, &ha_dev, + "[build_req] daddr[%d]: addr=%08x, len=%d (%x)\n", + daddr_ptr, addr, len, len); + + ha_state.ts[tc].req.daddr[daddr_ptr].addr = addr; + ha_state.ts[tc].req.daddr[daddr_ptr].len = len; + + daddr_ptr++; + ptr += 8; + } + + ha_state.ts[tc].req.dlen = i; + } else { + /* There's only one embedded address / length */ + ha_state.ts[tc].req.daddr[0].addr = pread_w(rqp + 8, BUS_PER); + ha_state.ts[tc].req.daddr[0].len = pread_w(rqp + 12, BUS_PER); + ha_state.ts[tc].req.dlen = 1; + } + + } else { + if (express) { + cio_rexpress(ha_state.slot, SCQRESIZE, &rqe, rapp_data); + } else { + /* TODO: Find correct queue number! */ + cio_rqueue(ha_state.slot, 0, SCQRESIZE, &rqe, rapp_data); + } + + ptr = rqe.address; + + ha_state.ts[tc].req.tc = FC_TC(rqe.subdevice); + ha_state.ts[tc].req.lu = FC_LU(rqe.subdevice); + ha_state.ts[tc].req.cmd_len = pread_w(ptr + 4, BUS_PER); + ha_state.ts[tc].req.timeout = pread_w(ptr + 8, BUS_PER); + ha_state.ts[tc].req.daddr[0].addr = pread_w(ptr + 12, BUS_PER); + ha_state.ts[tc].req.daddr[0].len = rqe.byte_count; + ha_state.ts[tc].req.dlen = 1; + + sim_debug(HA_TRACE, &ha_dev, + "[build_req] [non-fast] Building a list of 1 scatter/gather addresses.\n"); + + ptr = pread_w(ptr, BUS_PER); + + for (i = 0; (i < ha_state.ts[tc].req.cmd_len) && (i < HA_MAX_CMD); i++) { + ha_state.ts[tc].req.cmd[i] = pread_b(ptr + i, BUS_PER); + } + + ha_state.ts[tc].req.op = ha_state.ts[tc].req.cmd[0]; + } +} + +static SIM_INLINE void ha_cmd_prep(uint8 tc, uint8 op, uint8 subdev, t_bool express) +{ + ha_state.ts[tc].pending = TRUE; + ha_state.ts[tc].rep.op = op; + ha_state.ts[tc].rep.subdev = subdev; + ha_state.ts[tc].rep.status = CIO_FAILURE; + ha_state.ts[tc].rep.ssb = 0; + ha_state.ts[tc].rep.len = 0; + ha_state.ts[tc].rep.addr = 0; + + if (ha_state.pump_state == PUMP_COMPLETE) { + ha_state.ts[tc].rep.op |= 0x80; + } + + if (ha_state.frq) { + ha_state.ts[tc].rep.type = HA_JOB_QUICK; + } else if (express) { + ha_state.ts[tc].rep.type = HA_JOB_EXPRESS; + } else { + ha_state.ts[tc].rep.type = HA_JOB_FULL; + } +} + +static void ha_cmd(uint8 op, uint8 subdev, uint32 addr, int32 len, t_bool express) +{ + int32 i, block; + UNIT *uptr; + SCSI_DEV *dev; + uint8 dsd_tc, tc; + + sim_debug(HA_TRACE, &ha_dev, + "[ha_cmd] --------------------------[START]---------------------------------\n"); + sim_debug(HA_TRACE, &ha_dev, + "[ha_cmd] op=%02x (%d), subdev=%02x, addr=%08x, len=%d\n", + op, op, subdev, addr, len); + + switch (op) { + case CIO_DLM: + tc = HA_SCSI_ID; + ha_cmd_prep(tc, op, subdev, express); + + for (i = 0; i < len; i++) { + ha_crc = cio_crc32_shift(ha_crc, pread_b(addr + i, BUS_PER)); + } + + sim_debug(HA_TRACE, &ha_dev, + "[ha_cmd] SCSI Download Memory: bytecnt=%04x " + "addr=%08x return_addr=%08x subdev=%02x (CRC=%08x)\n", + len, addr, addr, subdev, ha_crc); + ha_state.ts[tc].rep.status = CIO_SUCCESS; + sim_activate_abs(cio_unit, 1000); + break; + case CIO_FCF: + tc = HA_SCSI_ID; + ha_cmd_prep(tc, op, subdev, express); + + sim_debug(HA_TRACE, &ha_dev, + "[ha_cmd] SCSI Force Function Call. (CRC=%08x)\n", + ha_crc); + if (ha_crc == DIAG_CRC_1) { + pwrite_h(0x200f000, 0x1, BUS_PER); /* Test success */ + pwrite_h(0x200f002, 0x0, BUS_PER); /* Test Number */ + pwrite_h(0x200f004, 0x0, BUS_PER); /* Actual */ + pwrite_h(0x200f006, 0x0, BUS_PER); /* Expected */ + pwrite_b(0x200f008, 0x1, BUS_PER); /* Success flag again */ + } + + cio[ha_state.slot].sysgen_s = 0; + ha_state.ts[tc].rep.status = CIO_SUCCESS; + sim_activate_abs(cio_unit, 1000); + break; + case CIO_DSD: + tc = HA_SCSI_ID; + ha_cmd_prep(tc, op, subdev, express); + + sim_debug(HA_TRACE, &ha_dev, + "[ha_cmd] SCSI DSD - %d CONFIGURED DEVICES (writing to addr %08x).\n", + ha_subdev_cnt, addr); + + pwrite_h(addr, ha_subdev_cnt, BUS_PER); + + for (i = 0; i < ha_subdev_cnt; i++) { + addr += 2; + + dsd_tc = ha_subdev_tab[i]; + + if (dsd_tc < 0) { + pwrite_h(addr, 0, BUS_PER); + continue; + } + + uptr = &ha_unit[dsd_tc]; + + dev = (SCSI_DEV *)uptr->up7; + + sim_debug(HA_TRACE, &ha_dev, + "[ha_cmd] [DSD] Probing subdev %d, target %d, devtype %d\n", + i, dsd_tc, dev->devtype); + + switch(dev->devtype) { + case SCSI_DISK: + sim_debug(HA_TRACE, &ha_dev, + "[ha_cmd] [DSD] Subdev %d is DISK (writing to addr %08x)\n", + i, addr); + pwrite_h(addr, HA_DSD_DISK, BUS_PER); + break; + case SCSI_TAPE: + sim_debug(HA_TRACE, &ha_dev, + "[ha_cmd] [DSD] Subdev %d is TAPE (writing to addr %08x)\n", + i, addr); + pwrite_h(addr, HA_DSD_TAPE, BUS_PER); + break; + default: + sim_debug(HA_TRACE, &ha_dev, + "[ha_cmd] [DSD] Warning: No device type for subdev %d (Writing to addr %08x)\n", + i, addr); + pwrite_h(addr, 0, BUS_PER); + break; + } + } + + ha_state.ts[tc].rep.status = CIO_SUCCESS; + + sim_activate_abs(cio_unit, 1000); + + break; + case HA_BOOT: + tc = ha_subdev_tab[subdev & 7]; + ha_cmd_prep(tc, op, subdev, express); + + + sim_debug(HA_TRACE, &ha_dev, + "[ha_cmd] TARGET %d BOOTING.\n", + tc); + + if (tc < 0) { + ha_state.ts[tc].rep.status = CIO_TIMEOUT; + sim_activate_abs(cio_unit, 1000); + return; + } + + uptr = &ha_unit[tc]; + + if (!(uptr->flags & UNIT_ATT)) { + sim_debug(HA_TRACE, &ha_dev, + "[ha_cmd] TARGET %d NOT ATTACHED.\n", + tc); + ha_state.ts[tc].rep.status = CIO_TIMEOUT; + sim_activate_abs(cio_unit, 1000); + return; + } + + dev = (SCSI_DEV *)uptr->up7; + + switch(dev->devtype) { + case SCSI_DISK: + ha_boot_disk(uptr, tc); + break; + case SCSI_TAPE: + ha_boot_tape(uptr, tc); + break; + default: + sim_debug(HA_TRACE, &ha_dev, + "[HA_BOOT] Cannot boot target %d (not disk or tape).\n", + tc); + break; + } + + ha_state.ts[tc].rep.status = CIO_SUCCESS; + sim_activate_abs(cio_unit, 1000); + break; + case HA_READ_BLK: + tc = ha_subdev_tab[subdev & 7]; + ha_cmd_prep(tc, op, subdev, express); + + sim_debug(HA_TRACE, &ha_dev, + "[ha_cmd] SUBDEV %d TARGET %d READ BLOCK (BLOCK 0x%08x TO ADDR 0x%08x)\n", + subdev, tc, pread_w(addr, BUS_PER), pread_w(addr + 4, BUS_PER)); + + sim_debug(HA_TRACE, &ha_dev, + "[ha_read_blk] addr = %08x\n", + addr); + sim_debug(HA_TRACE, &ha_dev, + "[ha_read_blk] %08x = %08x\n", + addr, pread_w(addr, BUS_PER)); + sim_debug(HA_TRACE, &ha_dev, + "[ha_read_blk] %08x = %08x\n", + addr + 4, pread_w(addr + 4, BUS_PER)); + sim_debug(HA_TRACE, &ha_dev, + "[ha_read_blk] %08x = %08x\n", + addr + 8, pread_w(addr + 8, BUS_PER)); + sim_debug(HA_TRACE, &ha_dev, + "[ha_read_blk] %08x = %08x\n", + addr + 12, pread_w(addr + 12, BUS_PER)); + sim_debug(HA_TRACE, &ha_dev, + "[ha_read_blik] %08x = %08x\n", + addr + 16, pread_w(addr + 16, BUS_PER)); + + + if (tc < 0) { + ha_state.ts[tc].rep.status = CIO_TIMEOUT; + sim_activate_abs(cio_unit, 1000); + return; + } + + uptr = &ha_unit[tc]; + + if (!(uptr->flags & UNIT_ATT)) { + ha_state.ts[tc].rep.status = CIO_TIMEOUT; + sim_activate_abs(cio_unit, 1000); + return; + } + + dev = (SCSI_DEV *)uptr->up7; + block = pread_w(addr, BUS_PER); /* Logical block we've been asked to read */ + addr = pread_w(addr + 4, BUS_PER); /* Dereference the pointer to the destination */ + + switch(dev->devtype) { + case SCSI_TAPE: + ha_read_block_tape(uptr, addr, tc); + break; + case SCSI_DISK: + ha_read_block_disk(uptr, addr, tc, block); + break; + default: + sim_debug(HA_TRACE, &ha_dev, + "[HA_READ_BLOCK] Cannot read block %d on target %d (not disk or tape)\n", + block, tc); + break; + } + + ha_state.ts[tc].rep.status = CIO_SUCCESS; + sim_activate_abs(cio_unit, 1000); + + break; + case HA_WRITE_BLK: + tc = ha_subdev_tab[subdev & 7]; + ha_cmd_prep(tc, op, subdev, express); + + sim_debug(HA_TRACE, &ha_dev, + "[ha_cmd] SUBDEV %d TARGET %d WRITE BLOCK (BLOCK 0x%08x FROM ADDR 0x%08x)\n", + subdev, tc, pread_w(addr, BUS_PER), pread_w(addr + 4, BUS_PER)); + + sim_debug(HA_TRACE, &ha_dev, + "[ha_write_blk] addr = %08x\n", + addr); + sim_debug(HA_TRACE, &ha_dev, + "[ha_write_blk] %08x = %08x\n", + addr, pread_w(addr, BUS_PER)); + sim_debug(HA_TRACE, &ha_dev, + "[ha_write_blk] %08x = %08x\n", + addr + 4, pread_w(addr + 4, BUS_PER)); + + if (tc < 0) { + ha_state.ts[tc].rep.status = CIO_TIMEOUT; + sim_activate_abs(cio_unit, 1000); + return; + } + + uptr = &ha_unit[tc]; + + if (!(uptr->flags & UNIT_ATT)) { + ha_state.ts[tc].rep.status = CIO_TIMEOUT; + sim_activate_abs(cio_unit, 1000); + return; + } + + dev = (SCSI_DEV *)uptr->up7; + block = pread_w(addr, BUS_PER); /* Logical block we've been asked to write */ + addr = pread_w(addr + 4, BUS_PER); /* Dereference the pointer to the source */ + + switch(dev->devtype) { + case SCSI_DISK: + ha_write_block_disk(uptr, addr, tc, block); + break; + default: + sim_debug(HA_TRACE, &ha_dev, + "[ha_write_blk] Cannot write block %d on target %d (not disk)\n", + block, tc); + break; + } + + ha_state.ts[tc].rep.status = CIO_SUCCESS; + sim_activate_abs(cio_unit, 1000); + break; + case HA_CNTRL: + tc = FC_TC(subdev); + ha_cmd_prep(tc, op, subdev, express); + + sim_debug(HA_TRACE, &ha_dev, + "[ha_cmd] SCSI CONTROL (subdev=%02x addr=%08x)\n", + subdev, addr); + + ha_build_req(tc, subdev, express); + ha_ctrl(tc); + sim_activate_abs(cio_unit, 1000); + break; + case HA_VERS: + /* + * Get Host Adapter Version + */ + tc = HA_SCSI_ID; + ha_cmd_prep(tc, op, subdev, express); + + sim_debug(HA_TRACE, &ha_dev, + "[ha_cmd] SCSI GET VERSION (addr=%08x len=%08x)\n", + addr, len); + + pwrite_w(addr, HA_VERSION, BUS_PER); + ha_state.ts[tc].rep.status = CIO_SUCCESS; + sim_activate_abs(cio_unit, 1000); + + break; + case HA_DL_EEDT: + /* + * This is a request to download the Extended Equipped Device + * Table from the host adapter to the 3B2 main memory. + */ + tc = HA_SCSI_ID; + ha_cmd_prep(tc, op, subdev, express); + + sim_debug(HA_TRACE, &ha_dev, + "[ha_cmd] SCSI DOWNLOAD EDT (%d bytes to address %08x)\n", + len, addr); + + for (i = 0; i < len; i++) { + pwrite_b(addr + i, ha_state.edt[i], BUS_PER); + } + + ha_state.ts[tc].rep.status = CIO_SUCCESS; + + sim_activate_abs(cio_unit, 1000); + + break; + case HA_UL_EEDT: + /* + * This is a request to upload the Extended Equipped Device + * Table from the 3B2 main memory to the host adapter + */ + + tc = HA_SCSI_ID; + ha_cmd_prep(tc, op, subdev, express); + + sim_debug(HA_TRACE, &ha_dev, + "[ha_cmd] SCSI UPLOAD EDT (%d bytes from address %08x)\n", + len, addr); + + for (i = 0; i < len; i++) { + ha_state.edt[i] = pread_b(addr + i, BUS_PER); + } + + ha_state.ts[tc].rep.status = CIO_SUCCESS; + + sim_activate_abs(cio_unit, 1000); + + break; + case HA_EDSD: + /* + * This command is used to determine which TCs are attached to + * the SCSI bus, and what LUNs they support. + */ + tc = HA_SCSI_ID; + ha_cmd_prep(tc, op, subdev, express); + + sim_debug(HA_TRACE, &ha_dev, + "[ha_cmd] SCSI EXTENDED DSD.\n"); + + ha_state.ts[tc].rep.status = CIO_SUCCESS; + ha_state.ts[tc].rep.addr = addr; + ha_state.ts[tc].rep.len = 9; + + /* + * Loop over each SCSI ID and configure LUNs. + */ + for (i = 0; i < 8; i++) { + uptr = &ha_unit[i]; + /* + * TODO: The byte being written here is a bit mask of + * equipped luns. e.g., + * + * - 0x01 means LUN 0 is equipped, + * - 0x80 means LUN 7 is equipped, + * - 0x33 means LUNs 0, 1, 4, and 5 are equipped. + * + * For now, we only support one LUN per target, and it's + * always LUN 0. + */ + pwrite_b(addr + i, (uptr->flags & UNIT_ATT) ? 1 : 0, BUS_PER); + } + + pwrite_b(addr + 8, HA_SCSI_ID, BUS_PER); /* ID of the card */ + + sim_activate_abs(cio_unit, 1000); + + break; + case HA_RESET: + tc = HA_SCSI_ID; + ha_cmd_prep(tc, op, subdev, express); + + scsi_reset(&ha_bus); + + sim_debug(HA_TRACE, &ha_dev, + "[ha_cmd] SCSI RESET.\n"); + + ha_state.ts[tc].rep.status = CIO_SUCCESS; + ha_state.ts[tc].rep.addr = addr; + ha_state.ts[tc].rep.len = 0; + + sim_activate_abs(cio_unit, 1000); + break; + default: + tc = HA_SCSI_ID; + ha_cmd_prep(tc, op, subdev, express); + + sim_debug(HA_TRACE, &ha_dev, + "*** SCSI WARNING: UNHANDLED OPCODE 0x%02x\n", + op); + + ha_state.ts[tc].rep.status = CIO_FAILURE; + + sim_activate_abs(cio_unit, 1000); + } + + sim_debug(HA_TRACE, &ha_dev, + "[ha_cmd] ---------------------------[END]----------------------------------\n"); + +} + +/* + * Handle a raw SCSI control message. + */ +void ha_ctrl(uint8 tc) +{ + volatile t_bool txn_done; + uint32 i, j; + uint32 plen, ha_ptr; + uint32 in_len, out_len; + uint8 lu, status; + uint8 msgi_buf[64]; + uint32 msgi_len; + uint32 to_read; + + sim_debug(HA_TRACE, &ha_dev, + "[ha_ctrl] [HA_REQ] TC=%d LU=%d TIMEOUT=%d DLEN=%d\n", + ha_state.ts[tc].req.tc, + ha_state.ts[tc].req.lu, + ha_state.ts[tc].req.timeout, + ha_state.ts[tc].req.dlen); + + sim_debug(HA_TRACE, &ha_dev, + "[ha_ctrl] [HA_REQ] CMD_LEN=%d CMD=<%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x>\n", + ha_state.ts[tc].req.cmd_len, + ha_state.ts[tc].req.cmd[0], ha_state.ts[tc].req.cmd[1], + ha_state.ts[tc].req.cmd[2], ha_state.ts[tc].req.cmd[3], + ha_state.ts[tc].req.cmd[4], ha_state.ts[tc].req.cmd[5], + ha_state.ts[tc].req.cmd[6], ha_state.ts[tc].req.cmd[7], + ha_state.ts[tc].req.cmd[8], ha_state.ts[tc].req.cmd[9]); + + in_len = out_len = 0; + + /* + * These ops need special handling. + */ + switch(ha_state.ts[tc].req.op) { + case HA_TESTRDY: + /* Fail early if LU is set */ + if (ha_state.ts[tc].req.lu) { + HA_STAT(tc, HA_CKCON, CIO_TIMEOUT); + return; + } + break; + case HA_FORMAT: /* Not yet handled by sim_scsi library */ + case HA_VERIFY: /* Not yet handled by sim_scsi library */ + /* Just mimic success */ + HA_STAT(tc, HA_GOOD, CIO_SUCCESS); + return; + } + + /* Get the bus's attention */ + if (!scsi_arbitrate(&ha_bus, HA_SCSI_ID)) { + HA_STAT(tc, HA_CKCON, CIO_TIMEOUT); + return; + } + + scsi_set_atn(&ha_bus); + + if (!scsi_select(&ha_bus, ha_state.ts[tc].req.tc)) { + HA_STAT(tc, HA_CKCON, CIO_TIMEOUT); + scsi_release(&ha_bus); + return; + } + + /* Select the correct LU */ + lu = 0x80 | ha_state.ts[tc].req.lu; + scsi_write(&ha_bus, &lu, 1); + + txn_done = FALSE; + + /* TODO: Fix this. Work around a bug in command length. The host + * occasionally sends a command length of 8 for 6-byte SCSI + * commands. The sim_scsi library knows to only consume 6 bytes, + * which leaves the buffer in a bad state. */ + if (ha_state.ts[tc].req.cmd_len == 8) { + ha_state.ts[tc].req.cmd_len = 6; + } + + while (!txn_done) { + switch(ha_bus.phase) { + case SCSI_CMD: + plen = scsi_write(&ha_bus, ha_state.ts[tc].req.cmd, ha_state.ts[tc].req.cmd_len); + if (plen < ha_state.ts[tc].req.cmd_len) { + HA_STAT(tc, HA_CKCON, CIO_SUCCESS); + scsi_release(&ha_bus); + return; + } + break; + case SCSI_DATI: + /* This is a read */ + in_len = scsi_read(&ha_bus, ha_buf, HA_MAXFR); + + sim_debug(HA_TRACE, &ha_dev, + "[ha_ctrl] SCSI_DATI: Consumed %d (0x%X) bytes to ha_buf in SCSI read.\n", + in_len, in_len); + + /* We need special handling based on the op code */ + switch(ha_state.ts[tc].req.op) { + case HA_READ: + case HA_READEXT: + ha_ptr = 0; + + for (i = 0; i < ha_state.ts[tc].req.dlen; i++) { + /* + * Consume the lesser of: + * - The total bytes we consumed, or: + * - The length of the current block + */ + to_read = MIN(ha_state.ts[tc].req.daddr[i].len, in_len); + + sim_debug(HA_TRACE, &ha_dev, + "[(%02x) TC%d,LU%d] DATI: Processing %d bytes to address %08x...\n", + ha_state.ts[tc].req.op, + ha_state.ts[tc].req.tc, + ha_state.ts[tc].req.lu, + to_read, + ha_state.ts[tc].req.daddr[i].addr); + + for (j = 0; j < to_read; j++) { + pwrite_b(ha_state.ts[tc].req.daddr[i].addr + j, ha_buf[ha_ptr++], BUS_PER); + } + + if (in_len >= to_read) { + in_len -= to_read; + } else { + /* Nothing left to write */ + break; + } + } + + break; + default: + sim_debug(HA_TRACE, &ha_dev, + "[(%02x) TC%d,LU%d] DATI: Processing %d bytes to address %08x...\n", + ha_state.ts[tc].req.op, ha_state.ts[tc].req.tc, + ha_state.ts[tc].req.lu, in_len, + ha_state.ts[tc].req.daddr[0].addr); + for (i = 0; i < in_len; i++) { + sim_debug(HA_TRACE, &ha_dev, "[%04x] [DATI] 0x%02x\n", i, ha_buf[i]); + pwrite_b(ha_state.ts[tc].req.daddr[0].addr + i, ha_buf[i], BUS_PER); + } + + break; + } + + break; + case SCSI_DATO: + /* This is a write */ + ha_ptr = 0; + out_len = 0; + ha_state.ts[tc].rep.len = ha_state.ts[tc].req.dlen; + + for (i = 0; i < ha_state.ts[tc].req.dlen; i++) { + sim_debug(HA_TRACE, &ha_dev, + "[ha_ctrl] [%d] DATO: Writing %d bytes to ha_buf.\n", + i, ha_state.ts[tc].req.daddr[i].len); + + for (j = 0; j < ha_state.ts[tc].req.daddr[i].len; j++) { + ha_buf[ha_ptr++] = pread_b(ha_state.ts[tc].req.daddr[i].addr + j, BUS_PER); + if (ha_state.ts[tc].req.op == 0x15) { + sim_debug(HA_TRACE, &ha_dev, + "[ha_ctrl] [%d]\t\t%02x\n", + j, ha_buf[ha_ptr - 1]); + } + } + + out_len += ha_state.ts[tc].req.daddr[i].len; + } + + if (ha_state.ts[tc].req.op == HA_WRITE || ha_state.ts[tc].req.op == HA_WRTEXT) { + /* If total len is not on a block boundary, we have to + bump it up in order to write the whole block. */ + if (out_len % HA_BLKSZ) { + out_len = out_len + (HA_BLKSZ - (out_len % HA_BLKSZ)); + } + } + + scsi_write(&ha_bus, ha_buf, out_len); + + sim_debug(HA_TRACE, &ha_dev, + "[ha_ctrl] SCSI Write of %08x (%d) bytes Complete\n", + out_len, out_len); + break; + case SCSI_STS: + scsi_read(&ha_bus, &status, 1); + sim_debug(HA_TRACE, &ha_dev, + "[ha_ctrl] STATUS BYTE: %02x\n", + status); + break; + case SCSI_MSGI: + msgi_len = scsi_read(&ha_bus, msgi_buf, 64); + sim_debug(HA_TRACE, &ha_dev, + "[ha_ctrl] MESSAGE IN LENGTH %d\n", + msgi_len); + + for (i = 0; i < msgi_len; i++) { + sim_debug(HA_TRACE, &ha_dev, + "[ha_ctrl] MSGI[%02d] = %02x\n", + i, msgi_buf[i]); + } + + txn_done = TRUE; + break; + } + } + + if (ha_bus.sense_key || ha_bus.sense_code) { + sim_debug(HA_TRACE, &ha_dev, + "[ha_ctrl] SENSE KEY=%d CODE=%d INFO=%d, CKCON.\n", + ha_bus.sense_key, ha_bus.sense_code, ha_bus.sense_info); + HA_STAT(tc, HA_CKCON, 0x60); + } else { + sim_debug(HA_TRACE, &ha_dev, "[ha_ctrl] NO SENSE INFO.\n"); + HA_STAT(tc, HA_GOOD, CIO_SUCCESS); + } + + /* Release the bus */ + scsi_release(&ha_bus); +} + +void ha_fcm_express(uint8 tc) +{ + uint32 cqp, cqs; + + cqp = cio[ha_state.slot].cqp; + cqs = cio[ha_state.slot].cqs; + + /* Write the fast completion entry. */ + pwrite_b(cqp + cq_offset, ha_state.ts[tc].rep.status, BUS_PER); + pwrite_b(cqp + cq_offset + 1, ha_state.ts[tc].rep.op, BUS_PER); + pwrite_b(cqp + cq_offset + 2, ha_state.ts[tc].rep.subdev, BUS_PER); + pwrite_b(cqp + cq_offset + 3, ha_state.ts[tc].rep.ssb, BUS_PER); + + sim_debug(HA_TRACE, &ha_dev, + "[ha_fcm_express] stat=%02x, op=%02x (%d), cq_index=%d target=%d, lun=%d, ssb=%02x\n", + ha_state.ts[tc].rep.status, ha_state.ts[tc].rep.op, ha_state.ts[tc].rep.op, + (cq_offset / 4), + FC_TC(ha_state.ts[tc].rep.subdev), FC_LU(ha_state.ts[tc].rep.subdev), + ha_state.ts[tc].rep.ssb); + + if (ha_state.pump_state == PUMP_COMPLETE && cqs > 0) { + cq_offset = (cq_offset + 4) % (cqs * 4); + } else { + cq_offset = 0; + } +} + +t_stat ha_set_type(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + if (val < 0 || cptr || val > HA_MAX_DTYPE) { + return SCPE_ARG; + } + + if (uptr->flags & UNIT_ATT) { + return SCPE_ALATT; + } + + uptr->flags = (uptr->flags & ~UNIT_DTYPE) | (val << UNIT_V_DTYPE); + if (val == ST120_DTYPE) { + uptr->flags |= SCSI_QIC; + } + uptr->capac = (t_addr) ha_tab[val].lbn; + scsi_set_unit(&ha_bus, uptr, &ha_tab[val]); + scsi_reset_unit(uptr); + return SCPE_OK; +} + +t_stat ha_show_type(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + fprintf(st, "%s", ha_tab[GET_DTYPE(uptr->flags)].name); + + return SCPE_OK; +} diff --git a/3B2/3b2_scsi.h b/3B2/3b2_scsi.h index 9b3815fc..659042ec 100644 --- a/3B2/3b2_scsi.h +++ b/3B2/3b2_scsi.h @@ -1,288 +1,288 @@ -/* 3b2_scsi.h: CM195W SCSI Controller CIO Card - - Copyright (c) 2020-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. - */ - -#ifndef _3B2_SCSI_H_ -#define _3B2_SCSI_H_ - -#include "3b2_defs.h" - -/* CIO Opcodes */ -#define HA_BOOT 0x0a -#define HA_READ_BLK 0x0b -#define HA_WRITE_BLK 0x0c -#define HA_CNTRL 0x20 -#define HA_VERS 0x40 -#define HA_DL_EEDT 0x42 -#define HA_UL_EEDT 0x43 -#define HA_EDSD 0x44 -#define HA_RESET 0x45 - -#define HA_TESTRDY 0x00 -#define HA_FORMAT 0x04 -#define HA_WRITE 0x0a -#define HA_INQUIRY 0x12 -#define HA_MODESEL 0x15 -#define HA_MODESNS 0x1a -#define HA_RDCPCTY 0x25 -#define HA_READ 0x08 -#define HA_READEXT 0x28 -#define HA_WRTEXT 0x2a -#define HA_VERIFY 0x2f - -#define HA_PDLS_OFF 0x28 - -/* CIO Status */ -#define CIO_TIMEOUT 0x65 - -#define HA_BOOT_ADDR 0x2004000 -#define HA_PDINFO_ADDR 0x2004400 - -#define HA_ID 0x0100 -#define HA_IPL 12 - -#define HA_GOOD 0x00 -#define HA_CKCON 0x02 - -#define HA_DSD_DISK 0x100 -#define HA_DSD_TAPE 0x101 - -#define HA_VERSION 0x01 - -#define SCQRESIZE 24 -#define RAPP_LEN (SCQRESIZE - 8) -#define SCQCESIZE 16 -#define CAPP_LEN (SCQCESIZE - 8) - -#define FC_TC(x) (((x) >> 3) & 7) -#define FC_LU(x) ((x) & 7) - -#define HA_EDT_LEN 1024 - -#define HA_BLKSZ 512 - -#define HA_MAX_CMD 12 -#define INQUIRY_MAX 36 - -#define HA_STAT(tc,ha_stat,cio_stat) { \ - ha_state.ts[tc].rep.ssb = (ha_stat); \ - ha_state.ts[tc].rep.status = (cio_stat); \ - } - -#define HA_MAX_DTYPE 4 - -/* Hardware Notes - * ============== - * - * Disk Drives - * ----------- - * - * There are two emulated SCSI disk drives available. - * - * 1. 155 MB CDC Wren III (CDC 94161-9) - * 2. 327 MB CDC Wren IV (CDC 94171-9) - * - * The CDC 94161-9 was also OEMed as the "AT&T KS23483,L3" - * The CDC 94171-9 was also OEMed as the "AT&T KS23483,L25" - * - * - * Tape Drive - * ---------- - * - * Wangtek 5125EN (AT&T Part number KS23417,L2) - * - * DC600A cartridge tape at 120MB (QIC-120 format) - * - */ - - -/* Other SCSI hard disk types - * --------------------------- - * - * These geometries are supported natively and automatically - * by System V Release 3.2.3 UNIX. - * - * 1. CDC 94161-9 155 MB/148 MiB 512 B/s, 35 s/t, 9 head, 965 cyl - * 2. AT&T KS23483 327 MB/312 MiB 512 B/s, 46 s/t, 9 head, 1547 cyl - * (a.k.a CDC 94171-9) - * - * Also supported was a SCSI-to-ESDI bridge controller that used the - * Emulex MD23/S2 SCSI-to-ESDI bridge. It allowed up to four ESDI - * drives to be mapped as LUNs 0-3. - * - */ - -/* AT&T 155 MB Hard Disk (35 sec/t, 9 hd, 964 cyl) */ -#define SD155_DTYPE 0 -#define SD155_PQUAL 0x00 -#define SD155_SCSI 1 -#define SD155_BLK 512 -#define SD155_LBN 303660 -#define SD155_MANU "AT&T" -#define SD155_DESC "KS23483" -#define SD155_REV "0000" - -/* AT&T 300 MB Hard Disk (43 sec/t, 9 hd, 1514 cyl) */ -#define SD300_DTYPE 1 -#define SD300_PQUAL 0x00 -#define SD300_SCSI 1 -#define SD300_BLK 512 -#define SD300_LBN 585937 -#define SD300_MANU "AT&T" -#define SD300_DESC "KS23483" -#define SD300_REV "0000" - -/* AT&T 327 MB Hard Disk (46 sec/t, 9 hd, 1547 cyl) */ -#define SD327_DTYPE 2 -#define SD327_PQUAL 0x00 -#define SD327_SCSI 1 -#define SD327_BLK 512 -#define SD327_LBN 640458 -#define SD327_MANU "AT&T" -#define SD327_DESC "KS23483" -#define SD327_REV "0000" - -/* AT&T 630 MB Hard Disk (56 sec/t, 16 hd, 1447 cyl) */ -#define SD630_DTYPE 3 -#define SD630_PQUAL 0x00 -#define SD630_SCSI 1 -#define SD630_BLK 512 -#define SD630_LBN 1296512 -#define SD630_MANU "AT&T" -#define SD630_DESC "KS23483" -#define SD630_REV "0000" - -/* Wangtek 120MB cartridge tape */ -#define ST120_DTYPE 4 -#define ST120_PQUAL 0x00 -#define ST120_SCSI 1 -#define ST120_BLK 512 -#define ST120_LBN 1 -#define ST120_MANU "WANGTEK" -#define ST120_DESC "KS23465" -#define ST120_REV "CX17" - -#define UNIT_V_DTYPE (SCSI_V_UF + 0) -#define UNIT_M_DTYPE 0x1f -#define UNIT_DTYPE (UNIT_M_DTYPE << UNIT_V_DTYPE) - -#define GET_DTYPE(x) (((x) >> UNIT_V_DTYPE) & UNIT_M_DTYPE) -#define HA_DISK(d) \ - { SCSI_DISK, d##_PQUAL, d##_SCSI, FALSE, d##_BLK, \ - d##_LBN, d##_MANU, d##_DESC, d##_REV, #d } - -#define HA_TAPE(d) \ - { SCSI_TAPE, d##_PQUAL, d##_SCSI, TRUE, d##_BLK, \ - d##_LBN, d##_MANU, d##_DESC, d##_REV, #d } - -#define HA_SIZE(d) d##_LBN - -#define HA_JOB_QUICK 0 -#define HA_JOB_EXPRESS 1 -#define HA_JOB_FULL 2 - -typedef uint8 ha_jobtype; - -typedef struct { - uint32 addr; - uint32 len; -} haddr; - -/* - * SCSI Command Request - */ -typedef struct { - uint8 op; /* Destructured from the cmd byte array */ - uint8 tc; - uint8 lu; - uint32 timeout; - - uint8 dlen; - haddr daddr[48]; /* Support up to 48 transfer addresses */ - - uint32 dma_lst; - uint16 cmd_len; - uint8 cmd[HA_MAX_CMD]; -} ha_req; - -/* - * SCSI Command Response - */ -typedef struct { - ha_jobtype type; /* Job type */ - uint8 status; /* Result Status */ - uint8 op; /* Command Opcode */ - uint8 subdev; /* XXTTTLLL; T=Target, L=LUN */ - uint8 ssb; /* SCSI Status Byte */ - uint32 addr; /* Response address */ - uint32 len; /* Response length */ -} ha_resp; - -#define PUMP_NONE 0 -#define PUMP_SYSGEN 1 -#define PUMP_COMPLETE 2 - -/* - * SCSI Target state - */ -typedef struct { - t_bool pending; /* Service pending */ - ha_req req; /* SCSI job request */ - ha_resp rep; /* SCSI job reply */ -} ha_ts; - -/* - * General SCSI HA internal state. - */ -typedef struct { - uint8 slot; /* Card Backsplane Slot # */ - uint32 pump_state; - t_bool frq; /* Fast Request Queue enabled */ - uint8 edt[HA_EDT_LEN]; /* Equipped Device Table */ - ha_ts ts[8]; /* Target state */ -} HA_STATE; - -t_stat ha_show_type(FILE *st, UNIT *uptr, int32 val, CONST void *desc); -t_stat ha_set_type(UNIT *uptr, int32 val, CONST char *cptr, void *desc); -t_stat ha_reset(DEVICE *dptr); -t_stat ha_svc(UNIT *uptr); -t_stat ha_rq_svc(UNIT *uptr); -t_stat ha_attach(UNIT *uptr, CONST char *cptr); -t_stat ha_detach(UNIT *uptr); - -void ha_fast_queue_check(); -void ha_sysgen(uint8 slot); -void ha_express(uint8 slot); -void ha_full(uint8 slot); - -/* Fast Completion */ - -void ha_fcm_express(uint8 target); - -#endif /* _3B2_SCSI_H_ */ +/* 3b2_scsi.h: CM195W SCSI Controller CIO Card + + Copyright (c) 2020-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. + */ + +#ifndef _3B2_SCSI_H_ +#define _3B2_SCSI_H_ + +#include "3b2_defs.h" + +/* CIO Opcodes */ +#define HA_BOOT 0x0a +#define HA_READ_BLK 0x0b +#define HA_WRITE_BLK 0x0c +#define HA_CNTRL 0x20 +#define HA_VERS 0x40 +#define HA_DL_EEDT 0x42 +#define HA_UL_EEDT 0x43 +#define HA_EDSD 0x44 +#define HA_RESET 0x45 + +#define HA_TESTRDY 0x00 +#define HA_FORMAT 0x04 +#define HA_WRITE 0x0a +#define HA_INQUIRY 0x12 +#define HA_MODESEL 0x15 +#define HA_MODESNS 0x1a +#define HA_RDCPCTY 0x25 +#define HA_READ 0x08 +#define HA_READEXT 0x28 +#define HA_WRTEXT 0x2a +#define HA_VERIFY 0x2f + +#define HA_PDLS_OFF 0x28 + +/* CIO Status */ +#define CIO_TIMEOUT 0x65 + +#define HA_BOOT_ADDR 0x2004000 +#define HA_PDINFO_ADDR 0x2004400 + +#define HA_ID 0x0100 +#define HA_IPL 12 + +#define HA_GOOD 0x00 +#define HA_CKCON 0x02 + +#define HA_DSD_DISK 0x100 +#define HA_DSD_TAPE 0x101 + +#define HA_VERSION 0x01 + +#define SCQRESIZE 24 +#define RAPP_LEN (SCQRESIZE - 8) +#define SCQCESIZE 16 +#define CAPP_LEN (SCQCESIZE - 8) + +#define FC_TC(x) (((x) >> 3) & 7) +#define FC_LU(x) ((x) & 7) + +#define HA_EDT_LEN 1024 + +#define HA_BLKSZ 512 + +#define HA_MAX_CMD 12 +#define INQUIRY_MAX 36 + +#define HA_STAT(tc,ha_stat,cio_stat) { \ + ha_state.ts[tc].rep.ssb = (ha_stat); \ + ha_state.ts[tc].rep.status = (cio_stat); \ + } + +#define HA_MAX_DTYPE 4 + +/* Hardware Notes + * ============== + * + * Disk Drives + * ----------- + * + * There are two emulated SCSI disk drives available. + * + * 1. 155 MB CDC Wren III (CDC 94161-9) + * 2. 327 MB CDC Wren IV (CDC 94171-9) + * + * The CDC 94161-9 was also OEMed as the "AT&T KS23483,L3" + * The CDC 94171-9 was also OEMed as the "AT&T KS23483,L25" + * + * + * Tape Drive + * ---------- + * + * Wangtek 5125EN (AT&T Part number KS23417,L2) + * + * DC600A cartridge tape at 120MB (QIC-120 format) + * + */ + + +/* Other SCSI hard disk types + * --------------------------- + * + * These geometries are supported natively and automatically + * by System V Release 3.2.3 UNIX. + * + * 1. CDC 94161-9 155 MB/148 MiB 512 B/s, 35 s/t, 9 head, 965 cyl + * 2. AT&T KS23483 327 MB/312 MiB 512 B/s, 46 s/t, 9 head, 1547 cyl + * (a.k.a CDC 94171-9) + * + * Also supported was a SCSI-to-ESDI bridge controller that used the + * Emulex MD23/S2 SCSI-to-ESDI bridge. It allowed up to four ESDI + * drives to be mapped as LUNs 0-3. + * + */ + +/* AT&T 155 MB Hard Disk (35 sec/t, 9 hd, 964 cyl) */ +#define SD155_DTYPE 0 +#define SD155_PQUAL 0x00 +#define SD155_SCSI 1 +#define SD155_BLK 512 +#define SD155_LBN 303660 +#define SD155_MANU "AT&T" +#define SD155_DESC "KS23483" +#define SD155_REV "0000" + +/* AT&T 300 MB Hard Disk (43 sec/t, 9 hd, 1514 cyl) */ +#define SD300_DTYPE 1 +#define SD300_PQUAL 0x00 +#define SD300_SCSI 1 +#define SD300_BLK 512 +#define SD300_LBN 585937 +#define SD300_MANU "AT&T" +#define SD300_DESC "KS23483" +#define SD300_REV "0000" + +/* AT&T 327 MB Hard Disk (46 sec/t, 9 hd, 1547 cyl) */ +#define SD327_DTYPE 2 +#define SD327_PQUAL 0x00 +#define SD327_SCSI 1 +#define SD327_BLK 512 +#define SD327_LBN 640458 +#define SD327_MANU "AT&T" +#define SD327_DESC "KS23483" +#define SD327_REV "0000" + +/* AT&T 630 MB Hard Disk (56 sec/t, 16 hd, 1447 cyl) */ +#define SD630_DTYPE 3 +#define SD630_PQUAL 0x00 +#define SD630_SCSI 1 +#define SD630_BLK 512 +#define SD630_LBN 1296512 +#define SD630_MANU "AT&T" +#define SD630_DESC "KS23483" +#define SD630_REV "0000" + +/* Wangtek 120MB cartridge tape */ +#define ST120_DTYPE 4 +#define ST120_PQUAL 0x00 +#define ST120_SCSI 1 +#define ST120_BLK 512 +#define ST120_LBN 1 +#define ST120_MANU "WANGTEK" +#define ST120_DESC "KS23465" +#define ST120_REV "CX17" + +#define UNIT_V_DTYPE (SCSI_V_UF + 0) +#define UNIT_M_DTYPE 0x1f +#define UNIT_DTYPE (UNIT_M_DTYPE << UNIT_V_DTYPE) + +#define GET_DTYPE(x) (((x) >> UNIT_V_DTYPE) & UNIT_M_DTYPE) +#define HA_DISK(d) \ + { SCSI_DISK, d##_PQUAL, d##_SCSI, FALSE, d##_BLK, \ + d##_LBN, d##_MANU, d##_DESC, d##_REV, #d } + +#define HA_TAPE(d) \ + { SCSI_TAPE, d##_PQUAL, d##_SCSI, TRUE, d##_BLK, \ + d##_LBN, d##_MANU, d##_DESC, d##_REV, #d } + +#define HA_SIZE(d) d##_LBN + +#define HA_JOB_QUICK 0 +#define HA_JOB_EXPRESS 1 +#define HA_JOB_FULL 2 + +typedef uint8 ha_jobtype; + +typedef struct { + uint32 addr; + uint32 len; +} haddr; + +/* + * SCSI Command Request + */ +typedef struct { + uint8 op; /* Destructured from the cmd byte array */ + uint8 tc; + uint8 lu; + uint32 timeout; + + uint8 dlen; + haddr daddr[48]; /* Support up to 48 transfer addresses */ + + uint32 dma_lst; + uint16 cmd_len; + uint8 cmd[HA_MAX_CMD]; +} ha_req; + +/* + * SCSI Command Response + */ +typedef struct { + ha_jobtype type; /* Job type */ + uint8 status; /* Result Status */ + uint8 op; /* Command Opcode */ + uint8 subdev; /* XXTTTLLL; T=Target, L=LUN */ + uint8 ssb; /* SCSI Status Byte */ + uint32 addr; /* Response address */ + uint32 len; /* Response length */ +} ha_resp; + +#define PUMP_NONE 0 +#define PUMP_SYSGEN 1 +#define PUMP_COMPLETE 2 + +/* + * SCSI Target state + */ +typedef struct { + t_bool pending; /* Service pending */ + ha_req req; /* SCSI job request */ + ha_resp rep; /* SCSI job reply */ +} ha_ts; + +/* + * General SCSI HA internal state. + */ +typedef struct { + uint8 slot; /* Card Backsplane Slot # */ + uint32 pump_state; + t_bool frq; /* Fast Request Queue enabled */ + uint8 edt[HA_EDT_LEN]; /* Equipped Device Table */ + ha_ts ts[8]; /* Target state */ +} HA_STATE; + +t_stat ha_show_type(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +t_stat ha_set_type(UNIT *uptr, int32 val, CONST char *cptr, void *desc); +t_stat ha_reset(DEVICE *dptr); +t_stat ha_svc(UNIT *uptr); +t_stat ha_rq_svc(UNIT *uptr); +t_stat ha_attach(UNIT *uptr, CONST char *cptr); +t_stat ha_detach(UNIT *uptr); + +void ha_fast_queue_check(); +void ha_sysgen(uint8 slot); +void ha_express(uint8 slot); +void ha_full(uint8 slot); + +/* Fast Completion */ + +void ha_fcm_express(uint8 target); + +#endif /* _3B2_SCSI_H_ */ diff --git a/3B2/3b2_stddev.c b/3B2/3b2_stddev.c index 81cfd0db..a5e79777 100644 --- a/3B2/3b2_stddev.c +++ b/3B2/3b2_stddev.c @@ -1,815 +1,815 @@ -/* 3b2_stddev.c: Miscellaneous System Board Devices - - Copyright (c) 2017-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -/* - This file contains system-specific registers and devices for the - following 3B2 devices: - - - nvram Non-Volatile RAM - - tod MM58174A and MM58274C Real-Time-Clock - - flt Fault Register (Rev 3 only) -*/ - -#include "3b2_stddev.h" - -#include "3b2_cpu.h" -#include "3b2_csr.h" -#include "3b2_timer.h" - -DEBTAB sys_deb_tab[] = { - { "INIT", INIT_MSG, "Init" }, - { "READ", READ_MSG, "Read activity" }, - { "WRITE", WRITE_MSG, "Write activity" }, - { "EXECUTE", EXECUTE_MSG, "Execute activity" }, - { "IRQ", IRQ_MSG, "Interrupt activity"}, - { "TRACE", TRACE_DBG, "Detailed activity" }, - { NULL, 0 } -}; - -uint32 *NVRAM = NULL; - -/* NVRAM */ -UNIT nvram_unit = { - UDATA(NULL, UNIT_FIX+UNIT_BINK, NVRSIZE) -}; - -REG nvram_reg[] = { - { NULL } -}; - -DEVICE nvram_dev = { - "NVRAM", &nvram_unit, nvram_reg, NULL, - 1, 16, 8, 4, 16, 32, - &nvram_ex, &nvram_dep, &nvram_reset, - NULL, &nvram_attach, &nvram_detach, - NULL, DEV_DEBUG, 0, sys_deb_tab, NULL, NULL, - &nvram_help, NULL, NULL, - &nvram_description -}; - -t_stat nvram_ex(t_value *vptr, t_addr exta, UNIT *uptr, int32 sw) -{ - uint32 addr = (uint32) exta; - - if ((vptr == NULL) || (addr & 03)) { - return SCPE_ARG; - } - - if (addr >= NVRSIZE) { - return SCPE_NXM; - } - - *vptr = NVRAM[addr >> 2]; - - return SCPE_OK; -} - -t_stat nvram_dep(t_value val, t_addr exta, UNIT *uptr, int32 sw) -{ - uint32 addr = (uint32) exta; - - if (addr & 03) { - return SCPE_ARG; - } - - if (addr >= NVRSIZE) { - return SCPE_NXM; - } - - NVRAM[addr >> 2] = (uint32) val; - - return SCPE_OK; -} - -t_stat nvram_reset(DEVICE *dptr) -{ - if (NVRAM == NULL) { - NVRAM = (uint32 *)calloc(NVRSIZE >> 2, sizeof(uint32)); - memset(NVRAM, 0, sizeof(uint32) * NVRSIZE >> 2); - nvram_unit.filebuf = NVRAM; - } - - if (NVRAM == NULL) { - return SCPE_MEM; - } - - return SCPE_OK; -} - -const char *nvram_description(DEVICE *dptr) -{ - return "Non-Volatile RAM.\n"; -} - -t_stat nvram_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) -{ - fprintf(st, "Non-Volatile RAM\n\n"); - fprintf(st, "The %s device is a small battery-backed, non-volatile RAM\n", dptr->name); - fprintf(st, "used by the 3B2 to hold system configuration and diagnostic data.\n\n"); - fprintf(st, "In order for the simulator to keep track of this data while not\n"); - fprintf(st, "running, the %s device may be attached to a file, e.g.\n\n", dptr->name); - fprintf(st, " sim> ATTACH NVRAM \n"); - fprint_show_help(st, dptr); - fprint_reg_help(st, dptr); - return SCPE_OK; -} - -t_stat nvram_attach(UNIT *uptr, CONST char *cptr) -{ - t_stat r; - - /* If we've been asked to attach, make sure the ATTABLE - and BUFABLE flags are set on the unit */ - uptr->flags = uptr->flags | (UNIT_ATTABLE | UNIT_BUFABLE); - - r = attach_unit(uptr, cptr); - - if (r != SCPE_OK) { - /* Unset the ATTABLE and BUFABLE flags if we failed. */ - uptr->flags = uptr->flags & (uint32) ~(UNIT_ATTABLE | UNIT_BUFABLE); - } else { - uptr->hwmark = (uint32) uptr->capac; - } - - return r; -} - -t_stat nvram_detach(UNIT *uptr) -{ - t_stat r; - - r = detach_unit(uptr); - - if ((uptr->flags & UNIT_ATT) == 0) { - uptr->flags = uptr->flags & (uint32) ~(UNIT_ATTABLE | UNIT_BUFABLE); - } - - return r; -} - -uint32 nvram_read(uint32 pa, size_t size) -{ - uint32 offset = pa - NVRBASE; - uint32 data = 0; - uint32 sc = (~(offset & 3) << 3) & 0x1f; - - switch(size) { - case 8: - data = (NVRAM[offset >> 2] >> sc) & BYTE_MASK; - break; - case 16: - if (offset & 2) { - data = NVRAM[offset >> 2] & HALF_MASK; - } else { - data = (NVRAM[offset >> 2] >> 16) & HALF_MASK; - } - break; - case 32: - data = NVRAM[offset >> 2]; - break; - } - - return data; -} - -void nvram_write(uint32 pa, uint32 val, size_t size) -{ - uint32 offset = pa - NVRBASE; - uint32 index = offset >> 2; - uint32 sc, mask; - - switch(size) { - case 8: - sc = (~(pa & 3) << 3) & 0x1f; - mask = (uint32) (0xff << sc); - NVRAM[index] = (NVRAM[index] & ~mask) | (val << sc); - break; - case 16: - if (offset & 2) { - NVRAM[index] = (NVRAM[index] & ~HALF_MASK) | val; - } else { - NVRAM[index] = (NVRAM[index] & HALF_MASK) | (val << 16); - } - break; - case 32: - NVRAM[index] = val; - break; - } -} - -/* - * MM58174A and MM58274C Time Of Day Clock. - * - * In addition to keeping track of time of day in tenths of seconds, - * this device is also used as the simulator's primary calibrated - * real-time clock. It operates at the speed of 100Hz, with every - * tenth step incrementing the time-of-day counter. - */ - -#define TOD_12H(TD) (((TD)->clkset & 1) == 0) -#define TOD_BCDH(V) (((V) / 10) & 0xf) -#define TOD_BCDL(V) (((V) % 10) & 0xf) -#define TOD_LYEAR(TD) ((TD)->lyear == 0) -#define TOD_LYEAR_INC(TD) \ - do { \ - (TD)->lyear = (((TD)->lyear + 1) & 0x3); \ - (TD)->clkset &= 3; \ - (TD)->clkset |= (TD)->lyear << 2; \ - } while(0) - -#define CTRL_DISABLE 0x4 -#define CLKSET_PM 0x2 -#define FLAG_DATA_CHANGED 0x4 -#define FLAG_INTERRUPT 0x1 -#define MIN_DIFF 5l -#define MAX_DIFF 157680000l - -#define CLK_DELAY 10000 /* 10 milliseconds per tick */ -#define CLK_TPS 100l /* 100 ticks per second */ - -static void tod_resync(UNIT *uptr); -static void tod_tick(UNIT *uptr); -static t_stat tod_svc(UNIT *uptr); -static t_bool tod_enabled; - -int32 tmr_poll = CLK_DELAY; -int32 tmxr_poll = CLK_DELAY; - -UNIT tod_unit = { - UDATA(&tod_svc, UNIT_FIX|UNIT_BINK|UNIT_IDLE, sizeof(TOD_DATA)), CLK_DELAY -}; - -REG tod_reg[] = { - { DRDATAD(POLL, tmr_poll, 24, "Calibrated poll interval") }, - { 0 } -}; - -DEVICE tod_dev = { - "TOD", &tod_unit, tod_reg, NULL, - 1, 16, 8, 4, 16, 32, - NULL, NULL, &tod_reset, - NULL, &tod_attach, &tod_detach, - NULL, DEV_DEBUG, 0, sys_deb_tab, NULL, NULL, - &tod_help, NULL, NULL, - &tod_description -}; - -/* - * Attempt to re-sync the TOD by catching up (if lagging) and updating - * the current time stored in the TOD state. - * - * Because this process may be expensive when catching up following a - * very long time without the simulator running, the process will - * short-circuit if the delta is longer than 5 years, or if no - * previous time was recorded. - */ -static void tod_resync(UNIT *uptr) -{ - TOD_DATA *td; - time_t delta; - uint32_t catchup_ticks; - - if (!(uptr->flags & UNIT_ATT) || uptr->filebuf == NULL) { - return; - } - - td = (TOD_DATA *)uptr->filebuf; - - if (td->time > 0) { - delta = time(NULL) - td->time; - if (delta > MIN_DIFF && delta < MAX_DIFF) { - catchup_ticks = (uint32_t) delta * CLK_TPS; - sim_debug(EXECUTE_MSG, &tod_dev, - "Catching up with a delta of %ld seconds (%d ticks).\n", - delta, catchup_ticks); - while (catchup_ticks-- > 0) { - tod_tick(&tod_unit); - } - } - } - - td->time = time(NULL); -} - -t_stat tod_reset(DEVICE *dptr) -{ - int32 t; - - if (tod_unit.filebuf == NULL) { - tod_unit.filebuf = calloc(sizeof(TOD_DATA), 1); - if (tod_unit.filebuf == NULL) { - return SCPE_MEM; - } - } - - /* We start in a running state */ - tod_enabled = TRUE; - - t = sim_rtcn_init_unit(&tod_unit, tod_unit.wait, TMR_CLK); - sim_activate_after(&tod_unit, 1000000/CLK_TPS); - tmr_poll = t; - tmxr_poll = t; - - return SCPE_OK; -} - -t_stat tod_attach(UNIT *uptr, CONST char *cptr) -{ - t_stat r; - - uptr->flags = uptr->flags | (UNIT_ATTABLE | UNIT_BUFABLE); - - r = attach_unit(uptr, cptr); - - if (r != SCPE_OK) { - uptr->flags = uptr->flags & (uint32) ~(UNIT_ATTABLE | UNIT_BUFABLE); - } else { - uptr->hwmark = (uint32) uptr->capac; - } - - return r; -} - -t_stat tod_detach(UNIT *uptr) -{ - t_stat r; - - r = detach_unit(uptr); - - if ((uptr->flags & UNIT_ATT) == 0) { - uptr->flags = uptr->flags & (uint32) ~(UNIT_ATTABLE | UNIT_BUFABLE); - } - - return r; -} - -static t_stat tod_svc(UNIT *uptr) -{ - TOD_DATA *td = (TOD_DATA *)uptr->filebuf; - int32 t; - - /* Re-sync the recorded system time once every second */ - if (tod_enabled) { - tod_tick(uptr); - - if (td->tsec == 0) { - tod_resync(uptr); - } - } - - t = sim_rtcn_calb(CLK_TPS, TMR_CLK); - sim_activate_after(uptr, 1000000/CLK_TPS); - tmr_poll = t; - tmxr_poll = t; - AIO_SET_INTERRUPT_LATENCY(tmr_poll * CLK_TPS); - return SCPE_OK; -} - -/* - * The MM58174 and MM58274 consist of a set of failry "dumb" roll-over - * counters. In an ideal world, we'd just look at the real system time - * and translate that into whatever read the host needs. - * Unfortunately, since the Day-of-Week and Leap Year registers are - * totally independent of whatever the "real" date and time should be, - * this doesn't map very well, and DGMON hardware diagnostics fail. - * - * Instead, we model the behavior of the chip accurately here. Each - * rollover is cascaded to the next highest register, using the same - * logic the chip uses. - */ -static void tod_tick(UNIT *uptr) -{ - TOD_DATA *td = (TOD_DATA *)uptr->filebuf; - - if (++td->tsec > 99) { - td->tsec = 0; - td->flags |= FLAG_DATA_CHANGED; - if (++td->sec > 59) { - td->sec = 0; - if (++td->min > 59) { - td->min = 0; - td->hour++; - - /* 12-hour clock cycles from 1-12, 24-hour clock cycles from 00-23 */ - if (TOD_12H(td)) { - if (td->hour == 12) { - td->clkset ^= CLKSET_PM; - } - if (td->hour > 12) { - td->hour = 1; - } - } else if (td->hour > 23) { - td->hour = 0; - } - - if ((TOD_12H(td) && td->hour == 12) || (!TOD_12H(td) && td->hour == 0)) { - /* Manage day-of-week */ - td->wday++; - if (td->wday > 7) { - td->wday = 1; - } - td->day++; - switch(td->mon) { - case 2: /* FEB */ - if (TOD_LYEAR(td)) { - if (td->day > 29) { - td->day = 1; - } - } else { - if (td->day > 28) { - td->day = 1; - } - } - break; - case 4: /* APR */ - case 6: /* JUN */ - case 9: /* SEP */ - case 11: /* NOV */ - if (td->day > 30) { - td->day = 1; - } - break; - case 1: /* JAN */ - case 3: /* MAR */ - case 5: /* MAY */ - case 7: /* JUL */ - case 8: /* AUG */ - case 10: /* OCT */ - case 12: /* DEC */ - if (td->day > 31) { - td->day = 1; - } - break; - } - if (td->day == 1) { - if (++td->mon > 12) { - td->mon = 1; - TOD_LYEAR_INC(td); - if (++td->year > 99) { - td->year = 0; - } - } - } - } - } - } - } -} - - -uint32 tod_read(uint32 pa, size_t size) -{ - uint8 reg, val; - TOD_DATA *td = (TOD_DATA *)(tod_unit.filebuf); - - reg = pa & 0xfc; - - switch(reg) { -#if defined(REV3) - case TOD_CTRL: - val = td->flags; - td->flags &= ~(FLAG_DATA_CHANGED); - break; -#endif - case TOD_TSEC: - val = TOD_BCDH(td->tsec); - break; - case TOD_1SEC: - val = TOD_BCDL(td->sec); - break; - case TOD_10SEC: - val = TOD_BCDH(td->sec); - break; - case TOD_1MIN: - val = TOD_BCDL(td->min); - break; - case TOD_10MIN: - val = TOD_BCDH(td->min); - break; - case TOD_1HOUR: - val = TOD_BCDL(td->hour); - break; - case TOD_10HOUR: - val = TOD_BCDH(td->hour); - break; - case TOD_1DAY: - val = TOD_BCDL(td->day); - break; - case TOD_10DAY: - val = TOD_BCDH(td->day); - break; - case TOD_1MON: - val = TOD_BCDL(td->mon); - break; - case TOD_10MON: - val = TOD_BCDH(td->mon); - break; - case TOD_WDAY: - val = td->wday; - break; - case TOD_1YEAR: -#if defined(REV3) - val = TOD_BCDL(td->year); -#else - val = td->lyear; -#endif - break; -#if defined(REV3) - case TOD_10YEAR: - val = TOD_BCDH(td->year); - break; - case TOD_SET_INT: - val = td->clkset; - break; -#endif - default: - val = 0; - break; - } - - return val; -} - -void tod_write(uint32 pa, uint32 val, size_t size) -{ - uint32 reg; - TOD_DATA *td = (TOD_DATA *)(tod_unit.filebuf); - - /* reg = pa - TODBASE; */ - reg = pa & 0xfc; - - switch(reg) { -#if defined(REV3) - case TOD_CTRL: - td->ctrl = (uint8) val; - if (val & CTRL_DISABLE) { - tod_enabled = FALSE; - td->tsec = 0; - } else { - tod_enabled = TRUE; - } -#else - case TOD_TEST: - /* test mode */ -#endif - break; - case TOD_TSEC: - td->tsec = (uint8) val * 10; - break; - case TOD_1SEC: - td->sec = ((td->sec / 10) * 10) + (uint8) val; - break; - case TOD_10SEC: - td->sec = ((uint8) val * 10) + (td->sec % 10); - break; - case TOD_1MIN: - td->min = ((td->min / 10) * 10) + (uint8) val; - break; - case TOD_10MIN: - td->min = ((uint8) val * 10) + (td->min % 10); - break; - case TOD_1HOUR: - td->hour = ((td->hour / 10) * 10) + (uint8) val; - break; - case TOD_10HOUR: - td->hour = ((uint8) val * 10) + (td->hour % 10); - break; - case TOD_1DAY: - td->day = ((td->day / 10) * 10) + (uint8) val; - break; - case TOD_10DAY: - td->day = ((uint8) val * 10) + (td->day % 10); - break; - case TOD_1MON: - td->mon = ((td->mon / 10) * 10) + (uint8) val; - break; - case TOD_10MON: - td->mon = ((uint8) val * 10) + (td->mon % 10); - break; - case TOD_1YEAR: -#if defined(REV3) - td->year = ((td->year / 10) * 10) + (uint8) val; -#else - td->lyear = (uint8) val; -#endif - break; -#if defined(REV3) - case TOD_10YEAR: - td->year = ((uint8) val * 10) + (td->year % 10); - break; - case TOD_SET_INT: - td->clkset = (uint8) val; - if (!TOD_12H(td)) { - /* The AM/PM indicator is always 0 if not in 12H mode */ - td->clkset &= ~(CLKSET_PM); - } - td->lyear = (val >> 2) & 3; - break; -#else - case TOD_STARTSTOP: - tod_enabled = val & 1; - break; -#endif - case TOD_WDAY: - td->wday = (uint8)val & 0x7; - break; - default: - break; - } -} - -const char *tod_description(DEVICE *dptr) -{ -#if defined(REV3) - return("MM58274C real time clock"); -#else - return("MM58174A real time clock"); -#endif -} - -t_stat tod_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) -{ - char dname[10]; - -#if defined(REV3) - snprintf(dname, 10, "MM58274C"); -#else - snprintf(dname, 10, "MM58174A"); -#endif - - fprintf(st, "%s Time-Of-Day Clock (%s)\n\n", dname, dptr->name); - fprintf(st, "The %s controller simulates a National Semiconductor %s\n", dptr->name, dname); - fprintf(st, "real time clock. This clock keeps track of the current system time\n"); - fprintf(st, "and date.\n\n"); - fprintf(st, "In order to preserve simulated calendar time between simulator runs,\n"); - fprintf(st, "the %s clock may be attached to a file which stores its state while\n", dptr->name); - fprintf(st, "the simulator is not running, e.g.:\n\n"); - fprintf(st, " sim> ATTACH TOD \n"); - fprint_show_help(st, dptr); - fprint_reg_help(st, dptr); - return SCPE_OK; -} - -#if defined(REV3) - -/* - * Fault Register - * - * The Fault Register is composed of two 32-bit registers at addresses - * 0x4C000 and 0x4D000. These latch state of the last address to cause - * a CPU fault. - * - * Bits 00-25: Physical memory address bits - * - * Fault Register 2 does double duty. It actually consists of four - * words, each of which maps to a memory slot on the system board. If - * occupied, it records the size of memory equipped in the slot, as - * well as information about any memory faults. - * - * - * Bits Active Purpose - * ---------------------------------------------------- - * 31-26 H Upper Halfword ECC Syndrome Bits - * 25-20 H Lower Halfword ECC Syndeome Bits - * 19 L I/O Bus or BUB master on Fault - * 18 H Invert low addr bit 2 (DWORD1) - * 17 H Decrement low addr by 4 - * 16 L I/O Bus Master on Fault - * 15 L CPU accessing I/O Peripheral - * 14 L BUB Slot 0 master on fault - * 13 L CPU Accessing BUB Peripheral - * 12-11 H BUB peripheral accessed by CPU - * 10 L BUB Slot 1 master on fault - * 9 L BUB Slot 2 master on fault - * 8 L BUB Slot 3 master on fault - * 7-3 N/A Not Used - * 2 L Memory Equipped - * 1-0 H Equipped Memory Size - * - */ - -uint32 flt[2] = {0, 0}; - -UNIT flt_unit = { - UDATA(NULL, UNIT_FIX+UNIT_BINK, 64) -}; - -REG flt_reg[] = { - { HRDATAD(FLT1, flt[0], 32, "Fault Register 1") }, - { HRDATAD(FLT2, flt[1], 32, "Fault Register 2") }, - { NULL } -}; - -DEVICE flt_dev = { - "FLT", &flt_unit, flt_reg, NULL, - 1, 16, 32, 1, 16, 32, - NULL, NULL, NULL, - NULL, NULL, NULL, - NULL, DEV_DEBUG, 0, sys_deb_tab, NULL, NULL, - &flt_help, NULL, NULL, - &flt_description -}; - -/* - * Return the configured memory size for a given backplane location. - */ -static uint32 mem_size(uint8 slot) { - switch(MEM_SIZE) { - case MSIZ_8M: - if (slot <= 1) { - return MEM_EQP|MEM_4M; - } else { - return 0; - } - case MSIZ_16M: - return MEM_EQP|MEM_4M; - case MSIZ_32M: - if (slot <= 1) { - return MEM_EQP|MEM_16M; - } else { - return 0; - } - case MSIZ_64M: - return MEM_EQP|MEM_16M; - default: - return 0; - } -} - -uint32 flt_read(uint32 pa, size_t size) -{ - sim_debug(EXECUTE_MSG, &flt_dev, - "Read from FLT Register at %x\n", - pa); - - switch(pa) { - case FLTLBASE: - return flt[0]; - case FLTHBASE: - return (flt[1] & FLT_MSK) | mem_size(0); - case FLTHBASE + 4: - return (flt[1] & FLT_MSK) | mem_size(1); - case FLTHBASE + 8: - return (flt[1] & FLT_MSK) | mem_size(2); - case FLTHBASE + 12: - return (flt[1] & FLT_MSK) | mem_size(3); - default: - sim_debug(EXECUTE_MSG, &flt_dev, - "Read from FLT Register at %x: FAILURE, NO DATA!!!!\n", - pa); - return 0; - } -} - -void flt_write(uint32 pa, uint32 val, size_t size) -{ - sim_debug(EXECUTE_MSG, &flt_dev, - "Write to FLT Register at %x (val=%x)\n", - pa, val); - - return; -} - -t_stat flt_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) -{ - fprintf(st, "Fault Register\n\n"); - fprintf(st, "The %s device is a pair of 32-bit registers that hold information about\n", dptr->name); - fprintf(st, "system memory faults.\n"); - fprint_show_help(st, dptr); - fprint_reg_help(st, dptr); - return SCPE_OK; -} - -const char *flt_description(DEVICE *dptr) -{ - return "Fault Register"; -} - -#endif +/* 3b2_stddev.c: Miscellaneous System Board Devices + + Copyright (c) 2017-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +/* + This file contains system-specific registers and devices for the + following 3B2 devices: + + - nvram Non-Volatile RAM + - tod MM58174A and MM58274C Real-Time-Clock + - flt Fault Register (Rev 3 only) +*/ + +#include "3b2_stddev.h" + +#include "3b2_cpu.h" +#include "3b2_csr.h" +#include "3b2_timer.h" + +DEBTAB sys_deb_tab[] = { + { "INIT", INIT_MSG, "Init" }, + { "READ", READ_MSG, "Read activity" }, + { "WRITE", WRITE_MSG, "Write activity" }, + { "EXECUTE", EXECUTE_MSG, "Execute activity" }, + { "IRQ", IRQ_MSG, "Interrupt activity"}, + { "TRACE", TRACE_DBG, "Detailed activity" }, + { NULL, 0 } +}; + +uint32 *NVRAM = NULL; + +/* NVRAM */ +UNIT nvram_unit = { + UDATA(NULL, UNIT_FIX+UNIT_BINK, NVRSIZE) +}; + +REG nvram_reg[] = { + { NULL } +}; + +DEVICE nvram_dev = { + "NVRAM", &nvram_unit, nvram_reg, NULL, + 1, 16, 8, 4, 16, 32, + &nvram_ex, &nvram_dep, &nvram_reset, + NULL, &nvram_attach, &nvram_detach, + NULL, DEV_DEBUG, 0, sys_deb_tab, NULL, NULL, + &nvram_help, NULL, NULL, + &nvram_description +}; + +t_stat nvram_ex(t_value *vptr, t_addr exta, UNIT *uptr, int32 sw) +{ + uint32 addr = (uint32) exta; + + if ((vptr == NULL) || (addr & 03)) { + return SCPE_ARG; + } + + if (addr >= NVRSIZE) { + return SCPE_NXM; + } + + *vptr = NVRAM[addr >> 2]; + + return SCPE_OK; +} + +t_stat nvram_dep(t_value val, t_addr exta, UNIT *uptr, int32 sw) +{ + uint32 addr = (uint32) exta; + + if (addr & 03) { + return SCPE_ARG; + } + + if (addr >= NVRSIZE) { + return SCPE_NXM; + } + + NVRAM[addr >> 2] = (uint32) val; + + return SCPE_OK; +} + +t_stat nvram_reset(DEVICE *dptr) +{ + if (NVRAM == NULL) { + NVRAM = (uint32 *)calloc(NVRSIZE >> 2, sizeof(uint32)); + memset(NVRAM, 0, sizeof(uint32) * NVRSIZE >> 2); + nvram_unit.filebuf = NVRAM; + } + + if (NVRAM == NULL) { + return SCPE_MEM; + } + + return SCPE_OK; +} + +const char *nvram_description(DEVICE *dptr) +{ + return "Non-Volatile RAM.\n"; +} + +t_stat nvram_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf(st, "Non-Volatile RAM\n\n"); + fprintf(st, "The %s device is a small battery-backed, non-volatile RAM\n", dptr->name); + fprintf(st, "used by the 3B2 to hold system configuration and diagnostic data.\n\n"); + fprintf(st, "In order for the simulator to keep track of this data while not\n"); + fprintf(st, "running, the %s device may be attached to a file, e.g.\n\n", dptr->name); + fprintf(st, " sim> ATTACH NVRAM \n"); + fprint_show_help(st, dptr); + fprint_reg_help(st, dptr); + return SCPE_OK; +} + +t_stat nvram_attach(UNIT *uptr, CONST char *cptr) +{ + t_stat r; + + /* If we've been asked to attach, make sure the ATTABLE + and BUFABLE flags are set on the unit */ + uptr->flags = uptr->flags | (UNIT_ATTABLE | UNIT_BUFABLE); + + r = attach_unit(uptr, cptr); + + if (r != SCPE_OK) { + /* Unset the ATTABLE and BUFABLE flags if we failed. */ + uptr->flags = uptr->flags & (uint32) ~(UNIT_ATTABLE | UNIT_BUFABLE); + } else { + uptr->hwmark = (uint32) uptr->capac; + } + + return r; +} + +t_stat nvram_detach(UNIT *uptr) +{ + t_stat r; + + r = detach_unit(uptr); + + if ((uptr->flags & UNIT_ATT) == 0) { + uptr->flags = uptr->flags & (uint32) ~(UNIT_ATTABLE | UNIT_BUFABLE); + } + + return r; +} + +uint32 nvram_read(uint32 pa, size_t size) +{ + uint32 offset = pa - NVRBASE; + uint32 data = 0; + uint32 sc = (~(offset & 3) << 3) & 0x1f; + + switch(size) { + case 8: + data = (NVRAM[offset >> 2] >> sc) & BYTE_MASK; + break; + case 16: + if (offset & 2) { + data = NVRAM[offset >> 2] & HALF_MASK; + } else { + data = (NVRAM[offset >> 2] >> 16) & HALF_MASK; + } + break; + case 32: + data = NVRAM[offset >> 2]; + break; + } + + return data; +} + +void nvram_write(uint32 pa, uint32 val, size_t size) +{ + uint32 offset = pa - NVRBASE; + uint32 index = offset >> 2; + uint32 sc, mask; + + switch(size) { + case 8: + sc = (~(pa & 3) << 3) & 0x1f; + mask = (uint32) (0xff << sc); + NVRAM[index] = (NVRAM[index] & ~mask) | (val << sc); + break; + case 16: + if (offset & 2) { + NVRAM[index] = (NVRAM[index] & ~HALF_MASK) | val; + } else { + NVRAM[index] = (NVRAM[index] & HALF_MASK) | (val << 16); + } + break; + case 32: + NVRAM[index] = val; + break; + } +} + +/* + * MM58174A and MM58274C Time Of Day Clock. + * + * In addition to keeping track of time of day in tenths of seconds, + * this device is also used as the simulator's primary calibrated + * real-time clock. It operates at the speed of 100Hz, with every + * tenth step incrementing the time-of-day counter. + */ + +#define TOD_12H(TD) (((TD)->clkset & 1) == 0) +#define TOD_BCDH(V) (((V) / 10) & 0xf) +#define TOD_BCDL(V) (((V) % 10) & 0xf) +#define TOD_LYEAR(TD) ((TD)->lyear == 0) +#define TOD_LYEAR_INC(TD) \ + do { \ + (TD)->lyear = (((TD)->lyear + 1) & 0x3); \ + (TD)->clkset &= 3; \ + (TD)->clkset |= (TD)->lyear << 2; \ + } while(0) + +#define CTRL_DISABLE 0x4 +#define CLKSET_PM 0x2 +#define FLAG_DATA_CHANGED 0x4 +#define FLAG_INTERRUPT 0x1 +#define MIN_DIFF 5l +#define MAX_DIFF 157680000l + +#define CLK_DELAY 10000 /* 10 milliseconds per tick */ +#define CLK_TPS 100l /* 100 ticks per second */ + +static void tod_resync(UNIT *uptr); +static void tod_tick(UNIT *uptr); +static t_stat tod_svc(UNIT *uptr); +static t_bool tod_enabled; + +int32 tmr_poll = CLK_DELAY; +int32 tmxr_poll = CLK_DELAY; + +UNIT tod_unit = { + UDATA(&tod_svc, UNIT_FIX|UNIT_BINK|UNIT_IDLE, sizeof(TOD_DATA)), CLK_DELAY +}; + +REG tod_reg[] = { + { DRDATAD(POLL, tmr_poll, 24, "Calibrated poll interval") }, + { 0 } +}; + +DEVICE tod_dev = { + "TOD", &tod_unit, tod_reg, NULL, + 1, 16, 8, 4, 16, 32, + NULL, NULL, &tod_reset, + NULL, &tod_attach, &tod_detach, + NULL, DEV_DEBUG, 0, sys_deb_tab, NULL, NULL, + &tod_help, NULL, NULL, + &tod_description +}; + +/* + * Attempt to re-sync the TOD by catching up (if lagging) and updating + * the current time stored in the TOD state. + * + * Because this process may be expensive when catching up following a + * very long time without the simulator running, the process will + * short-circuit if the delta is longer than 5 years, or if no + * previous time was recorded. + */ +static void tod_resync(UNIT *uptr) +{ + TOD_DATA *td; + time_t delta; + uint32_t catchup_ticks; + + if (!(uptr->flags & UNIT_ATT) || uptr->filebuf == NULL) { + return; + } + + td = (TOD_DATA *)uptr->filebuf; + + if (td->time > 0) { + delta = time(NULL) - td->time; + if (delta > MIN_DIFF && delta < MAX_DIFF) { + catchup_ticks = (uint32_t) delta * CLK_TPS; + sim_debug(EXECUTE_MSG, &tod_dev, + "Catching up with a delta of %ld seconds (%d ticks).\n", + delta, catchup_ticks); + while (catchup_ticks-- > 0) { + tod_tick(&tod_unit); + } + } + } + + td->time = time(NULL); +} + +t_stat tod_reset(DEVICE *dptr) +{ + int32 t; + + if (tod_unit.filebuf == NULL) { + tod_unit.filebuf = calloc(sizeof(TOD_DATA), 1); + if (tod_unit.filebuf == NULL) { + return SCPE_MEM; + } + } + + /* We start in a running state */ + tod_enabled = TRUE; + + t = sim_rtcn_init_unit(&tod_unit, tod_unit.wait, TMR_CLK); + sim_activate_after(&tod_unit, 1000000/CLK_TPS); + tmr_poll = t; + tmxr_poll = t; + + return SCPE_OK; +} + +t_stat tod_attach(UNIT *uptr, CONST char *cptr) +{ + t_stat r; + + uptr->flags = uptr->flags | (UNIT_ATTABLE | UNIT_BUFABLE); + + r = attach_unit(uptr, cptr); + + if (r != SCPE_OK) { + uptr->flags = uptr->flags & (uint32) ~(UNIT_ATTABLE | UNIT_BUFABLE); + } else { + uptr->hwmark = (uint32) uptr->capac; + } + + return r; +} + +t_stat tod_detach(UNIT *uptr) +{ + t_stat r; + + r = detach_unit(uptr); + + if ((uptr->flags & UNIT_ATT) == 0) { + uptr->flags = uptr->flags & (uint32) ~(UNIT_ATTABLE | UNIT_BUFABLE); + } + + return r; +} + +static t_stat tod_svc(UNIT *uptr) +{ + TOD_DATA *td = (TOD_DATA *)uptr->filebuf; + int32 t; + + /* Re-sync the recorded system time once every second */ + if (tod_enabled) { + tod_tick(uptr); + + if (td->tsec == 0) { + tod_resync(uptr); + } + } + + t = sim_rtcn_calb(CLK_TPS, TMR_CLK); + sim_activate_after(uptr, 1000000/CLK_TPS); + tmr_poll = t; + tmxr_poll = t; + AIO_SET_INTERRUPT_LATENCY(tmr_poll * CLK_TPS); + return SCPE_OK; +} + +/* + * The MM58174 and MM58274 consist of a set of failry "dumb" roll-over + * counters. In an ideal world, we'd just look at the real system time + * and translate that into whatever read the host needs. + * Unfortunately, since the Day-of-Week and Leap Year registers are + * totally independent of whatever the "real" date and time should be, + * this doesn't map very well, and DGMON hardware diagnostics fail. + * + * Instead, we model the behavior of the chip accurately here. Each + * rollover is cascaded to the next highest register, using the same + * logic the chip uses. + */ +static void tod_tick(UNIT *uptr) +{ + TOD_DATA *td = (TOD_DATA *)uptr->filebuf; + + if (++td->tsec > 99) { + td->tsec = 0; + td->flags |= FLAG_DATA_CHANGED; + if (++td->sec > 59) { + td->sec = 0; + if (++td->min > 59) { + td->min = 0; + td->hour++; + + /* 12-hour clock cycles from 1-12, 24-hour clock cycles from 00-23 */ + if (TOD_12H(td)) { + if (td->hour == 12) { + td->clkset ^= CLKSET_PM; + } + if (td->hour > 12) { + td->hour = 1; + } + } else if (td->hour > 23) { + td->hour = 0; + } + + if ((TOD_12H(td) && td->hour == 12) || (!TOD_12H(td) && td->hour == 0)) { + /* Manage day-of-week */ + td->wday++; + if (td->wday > 7) { + td->wday = 1; + } + td->day++; + switch(td->mon) { + case 2: /* FEB */ + if (TOD_LYEAR(td)) { + if (td->day > 29) { + td->day = 1; + } + } else { + if (td->day > 28) { + td->day = 1; + } + } + break; + case 4: /* APR */ + case 6: /* JUN */ + case 9: /* SEP */ + case 11: /* NOV */ + if (td->day > 30) { + td->day = 1; + } + break; + case 1: /* JAN */ + case 3: /* MAR */ + case 5: /* MAY */ + case 7: /* JUL */ + case 8: /* AUG */ + case 10: /* OCT */ + case 12: /* DEC */ + if (td->day > 31) { + td->day = 1; + } + break; + } + if (td->day == 1) { + if (++td->mon > 12) { + td->mon = 1; + TOD_LYEAR_INC(td); + if (++td->year > 99) { + td->year = 0; + } + } + } + } + } + } + } +} + + +uint32 tod_read(uint32 pa, size_t size) +{ + uint8 reg, val; + TOD_DATA *td = (TOD_DATA *)(tod_unit.filebuf); + + reg = pa & 0xfc; + + switch(reg) { +#if defined(REV3) + case TOD_CTRL: + val = td->flags; + td->flags &= ~(FLAG_DATA_CHANGED); + break; +#endif + case TOD_TSEC: + val = TOD_BCDH(td->tsec); + break; + case TOD_1SEC: + val = TOD_BCDL(td->sec); + break; + case TOD_10SEC: + val = TOD_BCDH(td->sec); + break; + case TOD_1MIN: + val = TOD_BCDL(td->min); + break; + case TOD_10MIN: + val = TOD_BCDH(td->min); + break; + case TOD_1HOUR: + val = TOD_BCDL(td->hour); + break; + case TOD_10HOUR: + val = TOD_BCDH(td->hour); + break; + case TOD_1DAY: + val = TOD_BCDL(td->day); + break; + case TOD_10DAY: + val = TOD_BCDH(td->day); + break; + case TOD_1MON: + val = TOD_BCDL(td->mon); + break; + case TOD_10MON: + val = TOD_BCDH(td->mon); + break; + case TOD_WDAY: + val = td->wday; + break; + case TOD_1YEAR: +#if defined(REV3) + val = TOD_BCDL(td->year); +#else + val = td->lyear; +#endif + break; +#if defined(REV3) + case TOD_10YEAR: + val = TOD_BCDH(td->year); + break; + case TOD_SET_INT: + val = td->clkset; + break; +#endif + default: + val = 0; + break; + } + + return val; +} + +void tod_write(uint32 pa, uint32 val, size_t size) +{ + uint32 reg; + TOD_DATA *td = (TOD_DATA *)(tod_unit.filebuf); + + /* reg = pa - TODBASE; */ + reg = pa & 0xfc; + + switch(reg) { +#if defined(REV3) + case TOD_CTRL: + td->ctrl = (uint8) val; + if (val & CTRL_DISABLE) { + tod_enabled = FALSE; + td->tsec = 0; + } else { + tod_enabled = TRUE; + } +#else + case TOD_TEST: + /* test mode */ +#endif + break; + case TOD_TSEC: + td->tsec = (uint8) val * 10; + break; + case TOD_1SEC: + td->sec = ((td->sec / 10) * 10) + (uint8) val; + break; + case TOD_10SEC: + td->sec = ((uint8) val * 10) + (td->sec % 10); + break; + case TOD_1MIN: + td->min = ((td->min / 10) * 10) + (uint8) val; + break; + case TOD_10MIN: + td->min = ((uint8) val * 10) + (td->min % 10); + break; + case TOD_1HOUR: + td->hour = ((td->hour / 10) * 10) + (uint8) val; + break; + case TOD_10HOUR: + td->hour = ((uint8) val * 10) + (td->hour % 10); + break; + case TOD_1DAY: + td->day = ((td->day / 10) * 10) + (uint8) val; + break; + case TOD_10DAY: + td->day = ((uint8) val * 10) + (td->day % 10); + break; + case TOD_1MON: + td->mon = ((td->mon / 10) * 10) + (uint8) val; + break; + case TOD_10MON: + td->mon = ((uint8) val * 10) + (td->mon % 10); + break; + case TOD_1YEAR: +#if defined(REV3) + td->year = ((td->year / 10) * 10) + (uint8) val; +#else + td->lyear = (uint8) val; +#endif + break; +#if defined(REV3) + case TOD_10YEAR: + td->year = ((uint8) val * 10) + (td->year % 10); + break; + case TOD_SET_INT: + td->clkset = (uint8) val; + if (!TOD_12H(td)) { + /* The AM/PM indicator is always 0 if not in 12H mode */ + td->clkset &= ~(CLKSET_PM); + } + td->lyear = (val >> 2) & 3; + break; +#else + case TOD_STARTSTOP: + tod_enabled = val & 1; + break; +#endif + case TOD_WDAY: + td->wday = (uint8)val & 0x7; + break; + default: + break; + } +} + +const char *tod_description(DEVICE *dptr) +{ +#if defined(REV3) + return("MM58274C real time clock"); +#else + return("MM58174A real time clock"); +#endif +} + +t_stat tod_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + char dname[10]; + +#if defined(REV3) + snprintf(dname, 10, "MM58274C"); +#else + snprintf(dname, 10, "MM58174A"); +#endif + + fprintf(st, "%s Time-Of-Day Clock (%s)\n\n", dname, dptr->name); + fprintf(st, "The %s controller simulates a National Semiconductor %s\n", dptr->name, dname); + fprintf(st, "real time clock. This clock keeps track of the current system time\n"); + fprintf(st, "and date.\n\n"); + fprintf(st, "In order to preserve simulated calendar time between simulator runs,\n"); + fprintf(st, "the %s clock may be attached to a file which stores its state while\n", dptr->name); + fprintf(st, "the simulator is not running, e.g.:\n\n"); + fprintf(st, " sim> ATTACH TOD \n"); + fprint_show_help(st, dptr); + fprint_reg_help(st, dptr); + return SCPE_OK; +} + +#if defined(REV3) + +/* + * Fault Register + * + * The Fault Register is composed of two 32-bit registers at addresses + * 0x4C000 and 0x4D000. These latch state of the last address to cause + * a CPU fault. + * + * Bits 00-25: Physical memory address bits + * + * Fault Register 2 does double duty. It actually consists of four + * words, each of which maps to a memory slot on the system board. If + * occupied, it records the size of memory equipped in the slot, as + * well as information about any memory faults. + * + * + * Bits Active Purpose + * ---------------------------------------------------- + * 31-26 H Upper Halfword ECC Syndrome Bits + * 25-20 H Lower Halfword ECC Syndeome Bits + * 19 L I/O Bus or BUB master on Fault + * 18 H Invert low addr bit 2 (DWORD1) + * 17 H Decrement low addr by 4 + * 16 L I/O Bus Master on Fault + * 15 L CPU accessing I/O Peripheral + * 14 L BUB Slot 0 master on fault + * 13 L CPU Accessing BUB Peripheral + * 12-11 H BUB peripheral accessed by CPU + * 10 L BUB Slot 1 master on fault + * 9 L BUB Slot 2 master on fault + * 8 L BUB Slot 3 master on fault + * 7-3 N/A Not Used + * 2 L Memory Equipped + * 1-0 H Equipped Memory Size + * + */ + +uint32 flt[2] = {0, 0}; + +UNIT flt_unit = { + UDATA(NULL, UNIT_FIX+UNIT_BINK, 64) +}; + +REG flt_reg[] = { + { HRDATAD(FLT1, flt[0], 32, "Fault Register 1") }, + { HRDATAD(FLT2, flt[1], 32, "Fault Register 2") }, + { NULL } +}; + +DEVICE flt_dev = { + "FLT", &flt_unit, flt_reg, NULL, + 1, 16, 32, 1, 16, 32, + NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, DEV_DEBUG, 0, sys_deb_tab, NULL, NULL, + &flt_help, NULL, NULL, + &flt_description +}; + +/* + * Return the configured memory size for a given backplane location. + */ +static uint32 mem_size(uint8 slot) { + switch(MEM_SIZE) { + case MSIZ_8M: + if (slot <= 1) { + return MEM_EQP|MEM_4M; + } else { + return 0; + } + case MSIZ_16M: + return MEM_EQP|MEM_4M; + case MSIZ_32M: + if (slot <= 1) { + return MEM_EQP|MEM_16M; + } else { + return 0; + } + case MSIZ_64M: + return MEM_EQP|MEM_16M; + default: + return 0; + } +} + +uint32 flt_read(uint32 pa, size_t size) +{ + sim_debug(EXECUTE_MSG, &flt_dev, + "Read from FLT Register at %x\n", + pa); + + switch(pa) { + case FLTLBASE: + return flt[0]; + case FLTHBASE: + return (flt[1] & FLT_MSK) | mem_size(0); + case FLTHBASE + 4: + return (flt[1] & FLT_MSK) | mem_size(1); + case FLTHBASE + 8: + return (flt[1] & FLT_MSK) | mem_size(2); + case FLTHBASE + 12: + return (flt[1] & FLT_MSK) | mem_size(3); + default: + sim_debug(EXECUTE_MSG, &flt_dev, + "Read from FLT Register at %x: FAILURE, NO DATA!!!!\n", + pa); + return 0; + } +} + +void flt_write(uint32 pa, uint32 val, size_t size) +{ + sim_debug(EXECUTE_MSG, &flt_dev, + "Write to FLT Register at %x (val=%x)\n", + pa, val); + + return; +} + +t_stat flt_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf(st, "Fault Register\n\n"); + fprintf(st, "The %s device is a pair of 32-bit registers that hold information about\n", dptr->name); + fprintf(st, "system memory faults.\n"); + fprint_show_help(st, dptr); + fprint_reg_help(st, dptr); + return SCPE_OK; +} + +const char *flt_description(DEVICE *dptr) +{ + return "Fault Register"; +} + +#endif diff --git a/3B2/3b2_stddev.h b/3B2/3b2_stddev.h index 987ed87d..266bdb5e 100644 --- a/3B2/3b2_stddev.h +++ b/3B2/3b2_stddev.h @@ -1,138 +1,138 @@ -/* 3b2_stddev.h: Miscellaneous System Board Devices - - Copyright (c) 2017-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#ifndef _3B2_STDDEV_H_ -#define _3B2_STDDEV_H_ - -#include "3b2_defs.h" - -/* NVRAM */ -t_stat nvram_ex(t_value *vptr, t_addr exta, UNIT *uptr, int32 sw); -t_stat nvram_dep(t_value val, t_addr exta, UNIT *uptr, int32 sw); -t_stat nvram_reset(DEVICE *dptr); -uint32 nvram_read(uint32 pa, size_t size); -t_stat nvram_attach(UNIT *uptr, CONST char *cptr); -t_stat nvram_detach(UNIT *uptr); -const char *nvram_description(DEVICE *dptr); -t_stat nvram_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); -void nvram_write(uint32 pa, uint32 val, size_t size); - -typedef struct tod_data { - time_t time; /* System time */ - - uint8 ctrl; /* Control register (Rev 3 only) */ - uint8 flags; /* Data Changed & Interrutpt Flags (Rev 3 only) */ - uint8 clkset; /* Clock / Setting register (Rev 3 only) */ - - uint8 tsec; /* 1/100th seconds, 00-99 */ - uint8 sec; /* Seconds, 00-59 */ - uint8 min; /* Minutes, 00-59 */ - uint8 hour; /* Hours, 00-23 */ - uint8 day; /* Days, 00-27, 28, 29, or 30 */ - uint8 mon; /* Months, 00-11 */ - uint8 year; /* Years, 00-99 (Rev 3 only) */ - uint8 wday; /* Day of Week, 0-6 */ - uint8 lyear; /* Years since last leap year */ -} TOD_DATA; - -#if defined(REV2) - -#define TOD_TEST 0x00 -#define TOD_TSEC 0x04 -#define TOD_1SEC 0x08 -#define TOD_10SEC 0x0c -#define TOD_1MIN 0x10 -#define TOD_10MIN 0x14 -#define TOD_1HOUR 0x18 -#define TOD_10HOUR 0x1c -#define TOD_1DAY 0x20 -#define TOD_10DAY 0x24 -#define TOD_WDAY 0x28 -#define TOD_1MON 0x2c -#define TOD_10MON 0x30 -#define TOD_1YEAR 0x34 -#define TOD_STARTSTOP 0x38 -#define TOD_INT 0x3c - -#else - -#define TOD_FLAG_CHG 0x08 -#define TOD_FLAG_IRQ 0x01 - -#define TOD_CTRL 0x00 -#define TOD_TSEC 0x04 -#define TOD_1SEC 0x08 -#define TOD_10SEC 0x0c -#define TOD_1MIN 0x10 -#define TOD_10MIN 0x14 -#define TOD_1HOUR 0x18 -#define TOD_10HOUR 0x1c -#define TOD_1DAY 0x20 -#define TOD_10DAY 0x24 -#define TOD_1MON 0x28 -#define TOD_10MON 0x2c -#define TOD_1YEAR 0x30 -#define TOD_10YEAR 0x34 -#define TOD_WDAY 0x38 -#define TOD_SET_INT 0x3c - -#endif - -void tod_update_delta(); -t_stat tod_reset(DEVICE *dptr); -t_stat tod_attach(UNIT *uptr, CONST char *cptr); -t_stat tod_detach(UNIT *uptr); -t_stat tod_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); -const char *tod_description(DEVICE *dptr); -uint32 tod_read(uint32 pa, size_t size); -void tod_write(uint32, uint32 val, size_t size); - -/* Global symbols */ - -extern int32 tmxr_poll; - -#if defined(REV3) -/* Fault Register */ - -#define FLT_MSK 0xffffff00 -#define MEM_EQP 0x4 -#define MEM_4M 0x2 -#define MEM_16M 0x3 - -uint32 flt_read(uint32 pa, size_t size); -void flt_write(uint32 pa, uint32 val, size_t size); -t_stat flt_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); -const char *flt_description(DEVICE *dptr); - -extern uint32 flt[2]; - -#endif /* defined(REV3) */ - -#endif /* _3B2_STDDEV_H_ */ +/* 3b2_stddev.h: Miscellaneous System Board Devices + + Copyright (c) 2017-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#ifndef _3B2_STDDEV_H_ +#define _3B2_STDDEV_H_ + +#include "3b2_defs.h" + +/* NVRAM */ +t_stat nvram_ex(t_value *vptr, t_addr exta, UNIT *uptr, int32 sw); +t_stat nvram_dep(t_value val, t_addr exta, UNIT *uptr, int32 sw); +t_stat nvram_reset(DEVICE *dptr); +uint32 nvram_read(uint32 pa, size_t size); +t_stat nvram_attach(UNIT *uptr, CONST char *cptr); +t_stat nvram_detach(UNIT *uptr); +const char *nvram_description(DEVICE *dptr); +t_stat nvram_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); +void nvram_write(uint32 pa, uint32 val, size_t size); + +typedef struct tod_data { + time_t time; /* System time */ + + uint8 ctrl; /* Control register (Rev 3 only) */ + uint8 flags; /* Data Changed & Interrutpt Flags (Rev 3 only) */ + uint8 clkset; /* Clock / Setting register (Rev 3 only) */ + + uint8 tsec; /* 1/100th seconds, 00-99 */ + uint8 sec; /* Seconds, 00-59 */ + uint8 min; /* Minutes, 00-59 */ + uint8 hour; /* Hours, 00-23 */ + uint8 day; /* Days, 00-27, 28, 29, or 30 */ + uint8 mon; /* Months, 00-11 */ + uint8 year; /* Years, 00-99 (Rev 3 only) */ + uint8 wday; /* Day of Week, 0-6 */ + uint8 lyear; /* Years since last leap year */ +} TOD_DATA; + +#if defined(REV2) + +#define TOD_TEST 0x00 +#define TOD_TSEC 0x04 +#define TOD_1SEC 0x08 +#define TOD_10SEC 0x0c +#define TOD_1MIN 0x10 +#define TOD_10MIN 0x14 +#define TOD_1HOUR 0x18 +#define TOD_10HOUR 0x1c +#define TOD_1DAY 0x20 +#define TOD_10DAY 0x24 +#define TOD_WDAY 0x28 +#define TOD_1MON 0x2c +#define TOD_10MON 0x30 +#define TOD_1YEAR 0x34 +#define TOD_STARTSTOP 0x38 +#define TOD_INT 0x3c + +#else + +#define TOD_FLAG_CHG 0x08 +#define TOD_FLAG_IRQ 0x01 + +#define TOD_CTRL 0x00 +#define TOD_TSEC 0x04 +#define TOD_1SEC 0x08 +#define TOD_10SEC 0x0c +#define TOD_1MIN 0x10 +#define TOD_10MIN 0x14 +#define TOD_1HOUR 0x18 +#define TOD_10HOUR 0x1c +#define TOD_1DAY 0x20 +#define TOD_10DAY 0x24 +#define TOD_1MON 0x28 +#define TOD_10MON 0x2c +#define TOD_1YEAR 0x30 +#define TOD_10YEAR 0x34 +#define TOD_WDAY 0x38 +#define TOD_SET_INT 0x3c + +#endif + +void tod_update_delta(); +t_stat tod_reset(DEVICE *dptr); +t_stat tod_attach(UNIT *uptr, CONST char *cptr); +t_stat tod_detach(UNIT *uptr); +t_stat tod_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); +const char *tod_description(DEVICE *dptr); +uint32 tod_read(uint32 pa, size_t size); +void tod_write(uint32, uint32 val, size_t size); + +/* Global symbols */ + +extern int32 tmxr_poll; + +#if defined(REV3) +/* Fault Register */ + +#define FLT_MSK 0xffffff00 +#define MEM_EQP 0x4 +#define MEM_4M 0x2 +#define MEM_16M 0x3 + +uint32 flt_read(uint32 pa, size_t size); +void flt_write(uint32 pa, uint32 val, size_t size); +t_stat flt_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); +const char *flt_description(DEVICE *dptr); + +extern uint32 flt[2]; + +#endif /* defined(REV3) */ + +#endif /* _3B2_STDDEV_H_ */ diff --git a/3B2/3b2_sys.c b/3B2/3b2_sys.c index 62e36e82..68a619c6 100644 --- a/3B2/3b2_sys.c +++ b/3B2/3b2_sys.c @@ -1,192 +1,192 @@ -/* 3b2_sys.c: Common System Definition - - Copyright (c) 2021-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#include "3b2_sys.h" - -#include "3b2_cpu.h" -#include "3b2_mem.h" - -REG *sim_PC = &cpu_reg[NUM_PC]; - -/* All opcodes are 1 or 2 bytes. Operands may be up to 6 bytes, and - there may be up to 3 operands, for a maximum of 20 bytes */ -int32 sim_emax = 20; - -const char *sim_stop_messages[SCPE_BASE] = { - "Unknown error", - "Reserved Instruction", - "Breakpoint", - "Invalid Opcode", - "IRQ", - "Exception/Trap", - "Exception Stack Too Deep", - "Unimplemented MMU Feature", - "System Powered Off", - "Infinite Loop", - "Simulator Error" -}; - -/* - * ROM and Binary loader - * - * -r load ROM - * -o for memory, specify origin - * - */ -t_stat sim_load(FILE *fileref, CONST char *cptr, CONST char *fnam, int flag) -{ - t_stat r; - int32 i; - uint32 origin = 0, limit = 0; - int32 cnt = 0; - - if (flag) { - return sim_messagef(SCPE_NOFNC, "Command not implemented."); - } - - if (sim_switches & SWMASK('R')) { - origin = ROM_BASE; - limit = ROM_BASE + ROM_SIZE; - } else { - origin = 0; - limit = (uint32) cpu_unit.capac; - if (sim_switches & SWMASK('O')) { - origin = (uint32) get_uint(cptr, 16, 0xffffffff, &r); - if (r != SCPE_OK) { - return SCPE_ARG; - } - } - } - - while ((i = Fgetc (fileref)) != EOF) { - if (origin >= limit) { - return SCPE_NXM; - } - if (sim_switches & SWMASK('R')) { - pwrite_b_rom(origin, (uint8)i); - } else { - pwrite_b(origin, (uint8)i, BUS_CPU); - } - origin++; - cnt++; - } - - if (sim_switches & SWMASK('R')) { - rom_loaded = TRUE; - sim_messagef(SCPE_OK, "%d bytes loaded into ROM\n", cnt); - } else { - sim_messagef(SCPE_OK, "%d bytes loaded at address 0x%08x\n", cnt, origin - cnt); - } - - return SCPE_OK; -} - -t_stat parse_sym(CONST char *cptr, t_addr exta, UNIT *uptr, t_value *val, int32 sw) -{ - DEVICE *dptr; - t_stat r; - int32 k, num, vp; - int32 len = 4; - - if (sw & (int32) SWMASK ('B')) { - len = 1; - } else if (sw & (int32) SWMASK ('H')) { - len = 2; - } else if (sw & (int32) SWMASK ('W')) { - len = 4; - } - - // Parse cptr - num = (int32) get_uint(cptr, 16, WORD_MASK, &r); - - if (r != SCPE_OK) { - return r; - } - - if (uptr == NULL) { - uptr = &cpu_unit; - } - - dptr = find_dev_from_unit(uptr); - - if (dptr == NULL) { - return SCPE_IERR; - } - - vp = 0; - for (k = len - 1; k >= 0; k--) { - val[vp++] = (num >> (k * 8)) & 0xff; - } - - return -(vp - 1); -} - -t_stat fprint_sym(FILE *of, t_addr addr, t_value *val, UNIT *uptr, int32 sw) -{ - uint32 len = 4; - int32 k, vp, num; - unsigned int c; - - num = 0; - vp = 0; - - if (sw & (int32) SWMASK('M')) { - return fprint_sym_m(of, addr, val); - } - - if (sw & (int32) SWMASK ('B')) { - len = 1; - } else if (sw & (int32) SWMASK ('H')) { - len = 2; - } else if (sw & (int32) SWMASK ('W')) { - len = 4; - } - - if (sw & (int32) SWMASK('C')) { - len = 16; - for (k = (int32) len - 1; k >= 0; k--) { - c = (unsigned int)val[vp++]; - if (c >= 0x20 && c < 0x7f) { - fprintf(of, "%c", c); - } else { - fprintf(of, "."); - } - } - return -(vp - 1); - } - - for (k = len - 1; k >= 0; k--) { - num = num | (((int32) val[vp++]) << (k * 8)); - } - - fprint_val(of, (uint32) num, 16, len * 8, PV_RZRO); - - return -(vp - 1); -} +/* 3b2_sys.c: Common System Definition + + Copyright (c) 2021-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#include "3b2_sys.h" + +#include "3b2_cpu.h" +#include "3b2_mem.h" + +REG *sim_PC = &cpu_reg[NUM_PC]; + +/* All opcodes are 1 or 2 bytes. Operands may be up to 6 bytes, and + there may be up to 3 operands, for a maximum of 20 bytes */ +int32 sim_emax = 20; + +const char *sim_stop_messages[SCPE_BASE] = { + "Unknown error", + "Reserved Instruction", + "Breakpoint", + "Invalid Opcode", + "IRQ", + "Exception/Trap", + "Exception Stack Too Deep", + "Unimplemented MMU Feature", + "System Powered Off", + "Infinite Loop", + "Simulator Error" +}; + +/* + * ROM and Binary loader + * + * -r load ROM + * -o for memory, specify origin + * + */ +t_stat sim_load(FILE *fileref, CONST char *cptr, CONST char *fnam, int flag) +{ + t_stat r; + int32 i; + uint32 origin = 0, limit = 0; + int32 cnt = 0; + + if (flag) { + return sim_messagef(SCPE_NOFNC, "Command not implemented."); + } + + if (sim_switches & SWMASK('R')) { + origin = ROM_BASE; + limit = ROM_BASE + ROM_SIZE; + } else { + origin = 0; + limit = (uint32) cpu_unit.capac; + if (sim_switches & SWMASK('O')) { + origin = (uint32) get_uint(cptr, 16, 0xffffffff, &r); + if (r != SCPE_OK) { + return SCPE_ARG; + } + } + } + + while ((i = Fgetc (fileref)) != EOF) { + if (origin >= limit) { + return SCPE_NXM; + } + if (sim_switches & SWMASK('R')) { + pwrite_b_rom(origin, (uint8)i); + } else { + pwrite_b(origin, (uint8)i, BUS_CPU); + } + origin++; + cnt++; + } + + if (sim_switches & SWMASK('R')) { + rom_loaded = TRUE; + sim_messagef(SCPE_OK, "%d bytes loaded into ROM\n", cnt); + } else { + sim_messagef(SCPE_OK, "%d bytes loaded at address 0x%08x\n", cnt, origin - cnt); + } + + return SCPE_OK; +} + +t_stat parse_sym(CONST char *cptr, t_addr exta, UNIT *uptr, t_value *val, int32 sw) +{ + DEVICE *dptr; + t_stat r; + int32 k, num, vp; + int32 len = 4; + + if (sw & (int32) SWMASK ('B')) { + len = 1; + } else if (sw & (int32) SWMASK ('H')) { + len = 2; + } else if (sw & (int32) SWMASK ('W')) { + len = 4; + } + + // Parse cptr + num = (int32) get_uint(cptr, 16, WORD_MASK, &r); + + if (r != SCPE_OK) { + return r; + } + + if (uptr == NULL) { + uptr = &cpu_unit; + } + + dptr = find_dev_from_unit(uptr); + + if (dptr == NULL) { + return SCPE_IERR; + } + + vp = 0; + for (k = len - 1; k >= 0; k--) { + val[vp++] = (num >> (k * 8)) & 0xff; + } + + return -(vp - 1); +} + +t_stat fprint_sym(FILE *of, t_addr addr, t_value *val, UNIT *uptr, int32 sw) +{ + uint32 len = 4; + int32 k, vp, num; + unsigned int c; + + num = 0; + vp = 0; + + if (sw & (int32) SWMASK('M')) { + return fprint_sym_m(of, addr, val); + } + + if (sw & (int32) SWMASK ('B')) { + len = 1; + } else if (sw & (int32) SWMASK ('H')) { + len = 2; + } else if (sw & (int32) SWMASK ('W')) { + len = 4; + } + + if (sw & (int32) SWMASK('C')) { + len = 16; + for (k = (int32) len - 1; k >= 0; k--) { + c = (unsigned int)val[vp++]; + if (c >= 0x20 && c < 0x7f) { + fprintf(of, "%c", c); + } else { + fprintf(of, "."); + } + } + return -(vp - 1); + } + + for (k = len - 1; k >= 0; k--) { + num = num | (((int32) val[vp++]) << (k * 8)); + } + + fprint_val(of, (uint32) num, 16, len * 8, PV_RZRO); + + return -(vp - 1); +} diff --git a/3B2/3b2_sys.h b/3B2/3b2_sys.h index dc79dde8..7eb9f174 100644 --- a/3B2/3b2_sys.h +++ b/3B2/3b2_sys.h @@ -1,46 +1,46 @@ -/* 3b2_sys.h: Common System Definition - - Copyright (c) 2017-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#ifndef _3B2_SYS_H_ -#define _3B2_SYS_H_ - -#include "3b2_defs.h" - -void full_reset(); -t_stat sim_load(FILE *fileref, CONST char *cptr, CONST char *fnam, int flag); -t_stat parse_sym(CONST char *cptr, t_addr addr, UNIT *uptr, t_value *val, - int32 sw); -t_stat fprint_sym(FILE *of, t_addr addr, t_value *val, UNIT *uptr, int32 sw); - -extern char sim_name[]; -extern REG *sim_PC; -extern int32 sim_emax; - -#endif /* _3B2_SYS_H_ */ +/* 3b2_sys.h: Common System Definition + + Copyright (c) 2017-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#ifndef _3B2_SYS_H_ +#define _3B2_SYS_H_ + +#include "3b2_defs.h" + +void full_reset(); +t_stat sim_load(FILE *fileref, CONST char *cptr, CONST char *fnam, int flag); +t_stat parse_sym(CONST char *cptr, t_addr addr, UNIT *uptr, t_value *val, + int32 sw); +t_stat fprint_sym(FILE *of, t_addr addr, t_value *val, UNIT *uptr, int32 sw); + +extern char sim_name[]; +extern REG *sim_PC; +extern int32 sim_emax; + +#endif /* _3B2_SYS_H_ */ diff --git a/3B2/3b2_timer.c b/3B2/3b2_timer.c index 761d282e..6c74b540 100644 --- a/3B2/3b2_timer.c +++ b/3B2/3b2_timer.c @@ -1,506 +1,506 @@ -/* 3b2_timer.c: 8253/82C54 Interval Timer - - Copyright (c) 2021-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -/* - * The 8253/82C54 Timer IC has three interval timers, which we treat - * here as three units. - * - * In the 3B2, the three timers are assigned specific purposes: - * - * - Timer 0: SYSTEM SANITY TIMER. This timer is normally loaded with - * a short timeout and allowed to run. If it times out, it - * will generate an interrupt and cause a system - * error. Software resets the timer regularly to ensure - * that it does not time out. It is fed by a 10 kHz - * clock, so each single counting step of this timer is - * 100 microseconds. - * - * - Timer 1: UNIX INTERVAL TIMER. This is the main timer that drives - * process switching in Unix. It operates at a fixed rate, - * and the counter is set up by Unix to generate an - * interrupt once every 10 milliseconds. The timer is fed - * by a 100 kHz clock, so each single counting step of - * this timer is 10 microseconds. - * - * - Timer 2: BUS TIMEOUT TIMER. This timer is reset every time the - * IO bus is accessed, and then stopped when the IO bus - * responds. It is mainly used to determine when the IO - * bus is hung (e.g., no card is installed in a given - * slot, so nothing can respond). When it times out, it - * generates an interrupt. It is fed by a 500 kHz clock, - * so each single counting step of this timer is 2 - * microseconds. - * - * - * Implementaiton Notes - * ==================== - * - * In general, no attempt has been made to create a truly accurate - * simulation of the 8253/82C54 timer. This implementation is built - * for the 3B2, and even more specifically to pass System V timer - * "Sanity/Interval Timer" diagnostics. - * - * - The Bus Timeout Timer is not implemented other than a stub that - * is designed to pass hardware diagnostics. The simulator IO - * subsystem always sets the correct interrupt directly if the bus - * will not respond. - * - * - The System Sanity Timer is also not implemented other than a - * stub to pass diagnostics. - * - * - The main Unix Interval Timer is more fully implemented, because - * it drives system interrupts in System V UNIX. - */ - -#include "3b2_cpu.h" -#include "3b2_csr.h" -#include "3b2_defs.h" -#include "3b2_timer.h" - -#define MIN_DIVIDER 50 - -#if defined (REV3) -#define QUICK_DELAY 10 -#else -#define QUICK_DELAY 100 -#endif - -#define DELAY_US(C,N) ((TIMER_MODE(C) == 3) ? \ - (TIME_BASE[(N)] * (C)->divider) / 2 : \ - TIME_BASE[(N)] * (C)->divider) - -#if defined(REV3) -/* Microseconds per step (Version 3 system board): - * - * Timer 0: 10KHz time base - * Timer 1: 100KHz time base - * Timer 2: 500KHz time base - */ -static uint32 TIME_BASE[3] = { - 100, 10, 1 -}; -#else -/* Microseconds per step (Version 2 system board): - * - * Timer 0: 100Khz time base - * Timer 1: 100Khz time base - * Timer 2: 500Khz time base - */ -static uint32 TIME_BASE[3] = { - 10, 10, 2 -}; -#endif - -struct timer_ctr TIMERS[3]; - -UNIT timer_unit[] = { - { UDATA(&tmr_svc, UNIT_IDLE, 0) }, - { UDATA(&tmr_svc, UNIT_IDLE, 0) }, - { UDATA(&tmr_svc, UNIT_IDLE, 0) }, - { NULL } -}; - -UNIT *tmr_int_unit = &timer_unit[3]; - -REG timer_reg[] = { - { HRDATAD(DIV0, TIMERS[0].divider, 16, "Divider (0)") }, - { HRDATAD(COUNT0, TIMERS[0].val, 16, "Count (0)") }, - { HRDATAD(CTRL0, TIMERS[0].ctrl, 8, "Control (0)") }, - { HRDATAD(DIV1, TIMERS[1].divider, 16, "Divider (1)") }, - { HRDATAD(COUNT1, TIMERS[1].val, 16, "Count (1)") }, - { HRDATAD(CTRL1, TIMERS[1].ctrl, 8, "Control (1)") }, - { HRDATAD(DIV2, TIMERS[2].divider, 16, "Divider (2)") }, - { HRDATAD(COUNT2, TIMERS[2].val, 16, "Count (2)") }, - { HRDATAD(CTRL2, TIMERS[2].ctrl, 8, "Control (2)") }, - { NULL } -}; - -DEVICE timer_dev = { - "TMR", - timer_unit, - timer_reg, - NULL, - 3, - 16, - 8, - 4, - 16, - 32, - NULL, - NULL, - &timer_reset, - NULL, - NULL, - NULL, - NULL, - DEV_DEBUG, - 0, - sys_deb_tab, - NULL, - NULL, - &tmr_help, - NULL, - NULL, - &tmr_description -}; - -t_stat timer_reset(DEVICE *dptr) { - int32 i; - - memset(&TIMERS, 0, sizeof(struct timer_ctr) * 3); - - /* Store the timer/counter number in the UNIT */ - for (i = 0; i < 3; i++) { - timer_unit[i].u3 = i; - } - - return SCPE_OK; -} - -/* - * Inhibit or allow a timer externally. - */ -void timer_gate(uint8 ctrnum, t_bool inhibit) -{ - struct timer_ctr *ctr = &TIMERS[ctrnum]; - - if (inhibit) { - ctr->gate = FALSE; - sim_cancel(&timer_unit[ctrnum]); - } else { - ctr->gate = TRUE; - if (ctr->enabled && !sim_is_active(&timer_unit[ctrnum])) { - sim_activate_after(&timer_unit[ctrnum], DELAY_US(ctr, ctrnum)); - ctr->val--; - } - } -} - -static void timer_activate(uint8 ctrnum) -{ - struct timer_ctr *ctr = &TIMERS[ctrnum]; - - if (ctr->enabled && ctr->gate) { - if (ctr->divider < MIN_DIVIDER) { - /* If the timer delay is too short, we need to force a - very quick activation */ - sim_activate_abs(&timer_unit[ctrnum], QUICK_DELAY); - } else { - /* Otherwise, use a computed time in microseconds */ - sim_activate_after_abs(&timer_unit[ctrnum], DELAY_US(ctr, ctrnum)); - } - } -} - -/* - * Sanity, Non-calibrated Interval, and Bus Timeout Timer service routine - */ -t_stat tmr_svc(UNIT *uptr) -{ - int32 ctr_num = uptr->u3; - uint32 usec_delay; - struct timer_ctr *ctr = &TIMERS[ctr_num]; - - if (ctr == NULL) { - return SCPE_SUB; - } - - /* If the timer isn't enabled, do nothing. */ - if (!ctr->enabled) { - return SCPE_OK; - } - - sim_debug(EXECUTE_MSG, &timer_dev, - "[tmr_svc] Handling timeout for ctr number %d\n", - ctr_num); - - switch (ctr_num) { - case TMR_SANITY: -#if defined (REV3) - if (!CSR(CSRISTIM) && TIMER_MODE(ctr) != 4) { - cpu_nmi = TRUE; - CSRBIT(CSRSTIMO, TRUE); - CPU_SET_INT(INT_BUS_TMO); - ctr->val = 0xffff; - } -#endif - break; - case TMR_INT: - if (!CSR(CSRITIM)) { - CSRBIT(CSRCLK, TRUE); - CPU_SET_INT(INT_CLOCK); - if (ctr->enabled && ctr->gate) { - usec_delay = DELAY_US(ctr, TMR_INT); - sim_debug(EXECUTE_MSG, &timer_dev, - "[tmr_svc] Re-triggering TMR_INT in %d usec\n", usec_delay); - sim_activate_after(uptr, usec_delay); - } - ctr->val = 0xffff; - } - break; - case TMR_BUS: -#if defined (REV3) - /* Only used during diagnostics */ - if (TIMER_RW(ctr) == CLK_LSB) { - sim_debug(EXECUTE_MSG, &timer_dev, - "[tmr_svc] BUS TIMER FIRING. Setting memory fault and interrupt\n"); - CSRBIT(CSRTIMO, TRUE); - CPU_SET_INT(INT_BUS_TMO); - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); - ctr->val = 0xffff; - } -#endif - break; - } - - - return SCPE_OK; -} - -uint32 timer_read(uint32 pa, size_t size) -{ - uint32 reg; - uint16 ctr_val; - uint8 ctrnum, retval; - struct timer_ctr *ctr; - - reg = pa - TIMERBASE; - ctrnum = (reg >> 2) & 0x3; - ctr = &TIMERS[ctrnum]; - - sim_debug(EXECUTE_MSG, &timer_dev, - "timer_read: reg=%x\n", reg); - - switch (reg) { - case TIMER_REG_DIVA: - case TIMER_REG_DIVB: - case TIMER_REG_DIVC: - ctr_val = ctr->val; - - switch (TIMER_RW(ctr)) { - case CLK_LSB: - retval = ctr_val & 0xff; - break; - case CLK_MSB: - retval = (ctr_val & 0xff00) >> 8; - break; - case CLK_LMB: - if (ctr->r_ctrl_latch) { - ctr->r_ctrl_latch = FALSE; - retval = ctr->ctrl_latch; - } else if (ctr->r_cnt_latch) { - if (ctr->r_lmb) { - ctr->r_lmb = FALSE; - retval = (ctr->cnt_latch & 0xff00) >> 8; - ctr->r_cnt_latch = FALSE; - } else { - ctr->r_lmb = TRUE; - retval = ctr->cnt_latch & 0xff; - } - } else if (ctr->r_lmb) { - ctr->r_lmb = FALSE; - retval = (ctr_val & 0xff00) >> 8; - } else { - ctr->r_lmb = TRUE; - retval = ctr_val & 0xff; - } - break; - default: - retval = 0; - } - - break; - case TIMER_REG_CTRL: - retval = ctr->ctrl; - break; - case TIMER_CLR_LATCH: - /* Clearing the timer latch has a side-effect - of also clearing pending interrupts */ - CSRBIT(CSRCLK, FALSE); - CPU_CLR_INT(INT_CLOCK); - /* Acknowledge a clock tick */ - sim_rtcn_tick_ack(1, TMR_CLK); - retval = 0; - break; - default: - /* Unhandled */ - retval = 0; - break; - } - - return retval; -} - -void handle_timer_write(uint8 ctrnum, uint32 val) -{ - struct timer_ctr *ctr; - - ctr = &TIMERS[ctrnum]; - ctr->enabled = TRUE; - - switch(TIMER_RW(ctr)) { - case CLK_LSB: - ctr->divider = val & 0xff; - ctr->val = ctr->divider; - sim_debug(EXECUTE_MSG, &timer_dev, "TIMER_WRITE: CTR=%d LSB=%02x\n", ctrnum, val & 0xff); - timer_activate(ctrnum); - break; - case CLK_MSB: - ctr->divider = (val & 0xff) << 8; - ctr->val = ctr->divider; - sim_debug(EXECUTE_MSG, &timer_dev, "TIMER_WRITE: CTR=%d MSB=%02x\n", ctrnum, val & 0xff); - timer_activate(ctrnum); - break; - case CLK_LMB: - if (ctr->w_lmb) { - ctr->w_lmb = FALSE; - ctr->divider = (uint16) ((ctr->divider & 0x00ff) | ((val & 0xff) << 8)); - ctr->val = ctr->divider; - sim_debug(EXECUTE_MSG, &timer_dev, "TIMER_WRITE: CTR=%d (L/M) MSB=%02x\n", ctrnum, val & 0xff); - timer_activate(ctrnum); - } else { - ctr->w_lmb = TRUE; - ctr->divider = (ctr->divider & 0xff00) | (val & 0xff); - ctr->val = ctr->divider; - sim_debug(EXECUTE_MSG, &timer_dev, "TIMER_WRITE: CTR=%d (L/M) LSB=%02x\n", ctrnum, val & 0xff); - } - break; - default: - break; - - } -} - -void timer_write(uint32 pa, uint32 val, size_t size) -{ - uint8 reg, ctrnum; - struct timer_ctr *ctr; - - reg = (uint8) (pa - TIMERBASE); - - sim_debug(EXECUTE_MSG, &timer_dev, - "timer_write: reg=%x val=%x\n", reg, val); - - switch(reg) { - case TIMER_REG_DIVA: - handle_timer_write(0, val); - break; - case TIMER_REG_DIVB: - handle_timer_write(1, val); - break; - case TIMER_REG_DIVC: - handle_timer_write(2, val); - break; - case TIMER_REG_CTRL: - ctrnum = (val >> 6) & 3; - if (ctrnum == 3) { - if (val & 2) { - ctr = &TIMERS[0]; - if ((val & 0x20) == 0) { - ctr->ctrl_latch = (uint16) TIMERS[2].ctrl; - ctr->r_ctrl_latch = TRUE; - } - if ((val & 0x20) == 0) { - ctr->cnt_latch = ctr->val; - ctr->r_cnt_latch = TRUE; - } - } - if (val & 4) { - ctr = &TIMERS[1]; - if ((val & 0x10) == 0) { - ctr->ctrl_latch = (uint16) TIMERS[2].ctrl; - ctr->r_ctrl_latch = TRUE; - } - if ((val & 0x20) == 0) { - ctr->cnt_latch = ctr->val; - ctr->r_cnt_latch = TRUE; - } - } - if (val & 8) { - ctr = &TIMERS[2]; - if ((val & 0x10) == 0) { - ctr->ctrl_latch = (uint16) TIMERS[2].ctrl; - ctr->r_ctrl_latch = TRUE; - } - if ((val & 0x20) == 0) { - ctr->cnt_latch = ctr->val; - ctr->r_cnt_latch = TRUE; - } - } - } else { - ctr = &TIMERS[ctrnum]; - ctr->ctrl = (uint8) val; - ctr->enabled = FALSE; - ctr->w_lmb = FALSE; - ctr->r_lmb = FALSE; - ctr->val = 0xffff; - ctr->divider = 0xffff; - } - break; - case TIMER_CLR_LATCH: - sim_debug(WRITE_MSG, &timer_dev, - "unexpected write to clear timer latch\n"); - break; - default: - sim_debug(WRITE_MSG, &timer_dev, - "unknown timer register: %d\n", - reg); - } -} - -CONST char *tmr_description(DEVICE *dptr) -{ -#if defined (REV3) - return "82C54 Programmable Interval Timer"; -#else - return "8253 Programmable Interval Timer"; -#endif -} - -t_stat tmr_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) -{ -#if defined (REV3) - fprintf(st, "82C54 Programmable Interval Timer (TMR)\n\n"); - fprintf(st, "The TMR device implements three programmable timers used by the 3B2/700\n"); -#else - fprintf(st, "8253 Programmable Interval Timer (TMR)\n\n"); - fprintf(st, "The TMR device implements three programmable timers used by the 3B2/400\n"); -#endif - fprintf(st, "to perform periodic tasks and sanity checks.\n\n"); - fprintf(st, "- TMR0: Used as a system sanity timer.\n"); - fprintf(st, "- TMR1: Used as a periodic 10 millisecond interval timer.\n"); - fprintf(st, "- TMR2: Used as a bus timeout timer.\n"); - - fprint_set_help(st, dptr); - fprint_show_help(st, dptr); - fprint_reg_help(st, dptr); - - return SCPE_OK; -} +/* 3b2_timer.c: 8253/82C54 Interval Timer + + Copyright (c) 2021-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +/* + * The 8253/82C54 Timer IC has three interval timers, which we treat + * here as three units. + * + * In the 3B2, the three timers are assigned specific purposes: + * + * - Timer 0: SYSTEM SANITY TIMER. This timer is normally loaded with + * a short timeout and allowed to run. If it times out, it + * will generate an interrupt and cause a system + * error. Software resets the timer regularly to ensure + * that it does not time out. It is fed by a 10 kHz + * clock, so each single counting step of this timer is + * 100 microseconds. + * + * - Timer 1: UNIX INTERVAL TIMER. This is the main timer that drives + * process switching in Unix. It operates at a fixed rate, + * and the counter is set up by Unix to generate an + * interrupt once every 10 milliseconds. The timer is fed + * by a 100 kHz clock, so each single counting step of + * this timer is 10 microseconds. + * + * - Timer 2: BUS TIMEOUT TIMER. This timer is reset every time the + * IO bus is accessed, and then stopped when the IO bus + * responds. It is mainly used to determine when the IO + * bus is hung (e.g., no card is installed in a given + * slot, so nothing can respond). When it times out, it + * generates an interrupt. It is fed by a 500 kHz clock, + * so each single counting step of this timer is 2 + * microseconds. + * + * + * Implementaiton Notes + * ==================== + * + * In general, no attempt has been made to create a truly accurate + * simulation of the 8253/82C54 timer. This implementation is built + * for the 3B2, and even more specifically to pass System V timer + * "Sanity/Interval Timer" diagnostics. + * + * - The Bus Timeout Timer is not implemented other than a stub that + * is designed to pass hardware diagnostics. The simulator IO + * subsystem always sets the correct interrupt directly if the bus + * will not respond. + * + * - The System Sanity Timer is also not implemented other than a + * stub to pass diagnostics. + * + * - The main Unix Interval Timer is more fully implemented, because + * it drives system interrupts in System V UNIX. + */ + +#include "3b2_cpu.h" +#include "3b2_csr.h" +#include "3b2_defs.h" +#include "3b2_timer.h" + +#define MIN_DIVIDER 50 + +#if defined (REV3) +#define QUICK_DELAY 10 +#else +#define QUICK_DELAY 100 +#endif + +#define DELAY_US(C,N) ((TIMER_MODE(C) == 3) ? \ + (TIME_BASE[(N)] * (C)->divider) / 2 : \ + TIME_BASE[(N)] * (C)->divider) + +#if defined(REV3) +/* Microseconds per step (Version 3 system board): + * + * Timer 0: 10KHz time base + * Timer 1: 100KHz time base + * Timer 2: 500KHz time base + */ +static uint32 TIME_BASE[3] = { + 100, 10, 1 +}; +#else +/* Microseconds per step (Version 2 system board): + * + * Timer 0: 100Khz time base + * Timer 1: 100Khz time base + * Timer 2: 500Khz time base + */ +static uint32 TIME_BASE[3] = { + 10, 10, 2 +}; +#endif + +struct timer_ctr TIMERS[3]; + +UNIT timer_unit[] = { + { UDATA(&tmr_svc, UNIT_IDLE, 0) }, + { UDATA(&tmr_svc, UNIT_IDLE, 0) }, + { UDATA(&tmr_svc, UNIT_IDLE, 0) }, + { NULL } +}; + +UNIT *tmr_int_unit = &timer_unit[3]; + +REG timer_reg[] = { + { HRDATAD(DIV0, TIMERS[0].divider, 16, "Divider (0)") }, + { HRDATAD(COUNT0, TIMERS[0].val, 16, "Count (0)") }, + { HRDATAD(CTRL0, TIMERS[0].ctrl, 8, "Control (0)") }, + { HRDATAD(DIV1, TIMERS[1].divider, 16, "Divider (1)") }, + { HRDATAD(COUNT1, TIMERS[1].val, 16, "Count (1)") }, + { HRDATAD(CTRL1, TIMERS[1].ctrl, 8, "Control (1)") }, + { HRDATAD(DIV2, TIMERS[2].divider, 16, "Divider (2)") }, + { HRDATAD(COUNT2, TIMERS[2].val, 16, "Count (2)") }, + { HRDATAD(CTRL2, TIMERS[2].ctrl, 8, "Control (2)") }, + { NULL } +}; + +DEVICE timer_dev = { + "TMR", + timer_unit, + timer_reg, + NULL, + 3, + 16, + 8, + 4, + 16, + 32, + NULL, + NULL, + &timer_reset, + NULL, + NULL, + NULL, + NULL, + DEV_DEBUG, + 0, + sys_deb_tab, + NULL, + NULL, + &tmr_help, + NULL, + NULL, + &tmr_description +}; + +t_stat timer_reset(DEVICE *dptr) { + int32 i; + + memset(&TIMERS, 0, sizeof(struct timer_ctr) * 3); + + /* Store the timer/counter number in the UNIT */ + for (i = 0; i < 3; i++) { + timer_unit[i].u3 = i; + } + + return SCPE_OK; +} + +/* + * Inhibit or allow a timer externally. + */ +void timer_gate(uint8 ctrnum, t_bool inhibit) +{ + struct timer_ctr *ctr = &TIMERS[ctrnum]; + + if (inhibit) { + ctr->gate = FALSE; + sim_cancel(&timer_unit[ctrnum]); + } else { + ctr->gate = TRUE; + if (ctr->enabled && !sim_is_active(&timer_unit[ctrnum])) { + sim_activate_after(&timer_unit[ctrnum], DELAY_US(ctr, ctrnum)); + ctr->val--; + } + } +} + +static void timer_activate(uint8 ctrnum) +{ + struct timer_ctr *ctr = &TIMERS[ctrnum]; + + if (ctr->enabled && ctr->gate) { + if (ctr->divider < MIN_DIVIDER) { + /* If the timer delay is too short, we need to force a + very quick activation */ + sim_activate_abs(&timer_unit[ctrnum], QUICK_DELAY); + } else { + /* Otherwise, use a computed time in microseconds */ + sim_activate_after_abs(&timer_unit[ctrnum], DELAY_US(ctr, ctrnum)); + } + } +} + +/* + * Sanity, Non-calibrated Interval, and Bus Timeout Timer service routine + */ +t_stat tmr_svc(UNIT *uptr) +{ + int32 ctr_num = uptr->u3; + uint32 usec_delay; + struct timer_ctr *ctr = &TIMERS[ctr_num]; + + if (ctr == NULL) { + return SCPE_SUB; + } + + /* If the timer isn't enabled, do nothing. */ + if (!ctr->enabled) { + return SCPE_OK; + } + + sim_debug(EXECUTE_MSG, &timer_dev, + "[tmr_svc] Handling timeout for ctr number %d\n", + ctr_num); + + switch (ctr_num) { + case TMR_SANITY: +#if defined (REV3) + if (!CSR(CSRISTIM) && TIMER_MODE(ctr) != 4) { + cpu_nmi = TRUE; + CSRBIT(CSRSTIMO, TRUE); + CPU_SET_INT(INT_BUS_TMO); + ctr->val = 0xffff; + } +#endif + break; + case TMR_INT: + if (!CSR(CSRITIM)) { + CSRBIT(CSRCLK, TRUE); + CPU_SET_INT(INT_CLOCK); + if (ctr->enabled && ctr->gate) { + usec_delay = DELAY_US(ctr, TMR_INT); + sim_debug(EXECUTE_MSG, &timer_dev, + "[tmr_svc] Re-triggering TMR_INT in %d usec\n", usec_delay); + sim_activate_after(uptr, usec_delay); + } + ctr->val = 0xffff; + } + break; + case TMR_BUS: +#if defined (REV3) + /* Only used during diagnostics */ + if (TIMER_RW(ctr) == CLK_LSB) { + sim_debug(EXECUTE_MSG, &timer_dev, + "[tmr_svc] BUS TIMER FIRING. Setting memory fault and interrupt\n"); + CSRBIT(CSRTIMO, TRUE); + CPU_SET_INT(INT_BUS_TMO); + cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); + ctr->val = 0xffff; + } +#endif + break; + } + + + return SCPE_OK; +} + +uint32 timer_read(uint32 pa, size_t size) +{ + uint32 reg; + uint16 ctr_val; + uint8 ctrnum, retval; + struct timer_ctr *ctr; + + reg = pa - TIMERBASE; + ctrnum = (reg >> 2) & 0x3; + ctr = &TIMERS[ctrnum]; + + sim_debug(EXECUTE_MSG, &timer_dev, + "timer_read: reg=%x\n", reg); + + switch (reg) { + case TIMER_REG_DIVA: + case TIMER_REG_DIVB: + case TIMER_REG_DIVC: + ctr_val = ctr->val; + + switch (TIMER_RW(ctr)) { + case CLK_LSB: + retval = ctr_val & 0xff; + break; + case CLK_MSB: + retval = (ctr_val & 0xff00) >> 8; + break; + case CLK_LMB: + if (ctr->r_ctrl_latch) { + ctr->r_ctrl_latch = FALSE; + retval = ctr->ctrl_latch; + } else if (ctr->r_cnt_latch) { + if (ctr->r_lmb) { + ctr->r_lmb = FALSE; + retval = (ctr->cnt_latch & 0xff00) >> 8; + ctr->r_cnt_latch = FALSE; + } else { + ctr->r_lmb = TRUE; + retval = ctr->cnt_latch & 0xff; + } + } else if (ctr->r_lmb) { + ctr->r_lmb = FALSE; + retval = (ctr_val & 0xff00) >> 8; + } else { + ctr->r_lmb = TRUE; + retval = ctr_val & 0xff; + } + break; + default: + retval = 0; + } + + break; + case TIMER_REG_CTRL: + retval = ctr->ctrl; + break; + case TIMER_CLR_LATCH: + /* Clearing the timer latch has a side-effect + of also clearing pending interrupts */ + CSRBIT(CSRCLK, FALSE); + CPU_CLR_INT(INT_CLOCK); + /* Acknowledge a clock tick */ + sim_rtcn_tick_ack(1, TMR_CLK); + retval = 0; + break; + default: + /* Unhandled */ + retval = 0; + break; + } + + return retval; +} + +void handle_timer_write(uint8 ctrnum, uint32 val) +{ + struct timer_ctr *ctr; + + ctr = &TIMERS[ctrnum]; + ctr->enabled = TRUE; + + switch(TIMER_RW(ctr)) { + case CLK_LSB: + ctr->divider = val & 0xff; + ctr->val = ctr->divider; + sim_debug(EXECUTE_MSG, &timer_dev, "TIMER_WRITE: CTR=%d LSB=%02x\n", ctrnum, val & 0xff); + timer_activate(ctrnum); + break; + case CLK_MSB: + ctr->divider = (val & 0xff) << 8; + ctr->val = ctr->divider; + sim_debug(EXECUTE_MSG, &timer_dev, "TIMER_WRITE: CTR=%d MSB=%02x\n", ctrnum, val & 0xff); + timer_activate(ctrnum); + break; + case CLK_LMB: + if (ctr->w_lmb) { + ctr->w_lmb = FALSE; + ctr->divider = (uint16) ((ctr->divider & 0x00ff) | ((val & 0xff) << 8)); + ctr->val = ctr->divider; + sim_debug(EXECUTE_MSG, &timer_dev, "TIMER_WRITE: CTR=%d (L/M) MSB=%02x\n", ctrnum, val & 0xff); + timer_activate(ctrnum); + } else { + ctr->w_lmb = TRUE; + ctr->divider = (ctr->divider & 0xff00) | (val & 0xff); + ctr->val = ctr->divider; + sim_debug(EXECUTE_MSG, &timer_dev, "TIMER_WRITE: CTR=%d (L/M) LSB=%02x\n", ctrnum, val & 0xff); + } + break; + default: + break; + + } +} + +void timer_write(uint32 pa, uint32 val, size_t size) +{ + uint8 reg, ctrnum; + struct timer_ctr *ctr; + + reg = (uint8) (pa - TIMERBASE); + + sim_debug(EXECUTE_MSG, &timer_dev, + "timer_write: reg=%x val=%x\n", reg, val); + + switch(reg) { + case TIMER_REG_DIVA: + handle_timer_write(0, val); + break; + case TIMER_REG_DIVB: + handle_timer_write(1, val); + break; + case TIMER_REG_DIVC: + handle_timer_write(2, val); + break; + case TIMER_REG_CTRL: + ctrnum = (val >> 6) & 3; + if (ctrnum == 3) { + if (val & 2) { + ctr = &TIMERS[0]; + if ((val & 0x20) == 0) { + ctr->ctrl_latch = (uint16) TIMERS[2].ctrl; + ctr->r_ctrl_latch = TRUE; + } + if ((val & 0x20) == 0) { + ctr->cnt_latch = ctr->val; + ctr->r_cnt_latch = TRUE; + } + } + if (val & 4) { + ctr = &TIMERS[1]; + if ((val & 0x10) == 0) { + ctr->ctrl_latch = (uint16) TIMERS[2].ctrl; + ctr->r_ctrl_latch = TRUE; + } + if ((val & 0x20) == 0) { + ctr->cnt_latch = ctr->val; + ctr->r_cnt_latch = TRUE; + } + } + if (val & 8) { + ctr = &TIMERS[2]; + if ((val & 0x10) == 0) { + ctr->ctrl_latch = (uint16) TIMERS[2].ctrl; + ctr->r_ctrl_latch = TRUE; + } + if ((val & 0x20) == 0) { + ctr->cnt_latch = ctr->val; + ctr->r_cnt_latch = TRUE; + } + } + } else { + ctr = &TIMERS[ctrnum]; + ctr->ctrl = (uint8) val; + ctr->enabled = FALSE; + ctr->w_lmb = FALSE; + ctr->r_lmb = FALSE; + ctr->val = 0xffff; + ctr->divider = 0xffff; + } + break; + case TIMER_CLR_LATCH: + sim_debug(WRITE_MSG, &timer_dev, + "unexpected write to clear timer latch\n"); + break; + default: + sim_debug(WRITE_MSG, &timer_dev, + "unknown timer register: %d\n", + reg); + } +} + +CONST char *tmr_description(DEVICE *dptr) +{ +#if defined (REV3) + return "82C54 Programmable Interval Timer"; +#else + return "8253 Programmable Interval Timer"; +#endif +} + +t_stat tmr_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ +#if defined (REV3) + fprintf(st, "82C54 Programmable Interval Timer (TMR)\n\n"); + fprintf(st, "The TMR device implements three programmable timers used by the 3B2/700\n"); +#else + fprintf(st, "8253 Programmable Interval Timer (TMR)\n\n"); + fprintf(st, "The TMR device implements three programmable timers used by the 3B2/400\n"); +#endif + fprintf(st, "to perform periodic tasks and sanity checks.\n\n"); + fprintf(st, "- TMR0: Used as a system sanity timer.\n"); + fprintf(st, "- TMR1: Used as a periodic 10 millisecond interval timer.\n"); + fprintf(st, "- TMR2: Used as a bus timeout timer.\n"); + + fprint_set_help(st, dptr); + fprint_show_help(st, dptr); + fprint_reg_help(st, dptr); + + return SCPE_OK; +} diff --git a/3B2/3b2_timer.h b/3B2/3b2_timer.h index 19205b92..7d01e4ce 100644 --- a/3B2/3b2_timer.h +++ b/3B2/3b2_timer.h @@ -1,78 +1,78 @@ -/* 3b2_timer.h: 8253/82C54 Interval Timer - - Copyright (c) 2021-2022, Seth J. Morabito - - 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 THE AUTHORS OR COPYRIGHT HOLDERS - 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 the author shall - not be used in advertising or otherwise to promote the sale, use or - other dealings in this Software without prior written authorization - from the author. -*/ - -#ifndef _3B2_TIMER_H_ -#define _3B2_TIMER_H_ - -#include "3b2_defs.h" - -#define TIMER_REG_DIVA 0x03 -#define TIMER_REG_DIVB 0x07 -#define TIMER_REG_DIVC 0x0b -#define TIMER_REG_CTRL 0x0f -#define TIMER_CLR_LATCH 0x13 - -#define TIMER_MODE(ctr) (((ctr->ctrl) >> 1) & 7) -#define TIMER_RW(ctr) (((ctr->ctrl) >> 4) & 3) - -#define CLK_LATCH 0 -#define CLK_LSB 1 -#define CLK_MSB 2 -#define CLK_LMB 3 - -#define TMR_SANITY 0 -#define TMR_INT 1 -#define TMR_BUS 2 - -struct timer_ctr { - uint16 divider; - uint16 val; - uint8 ctrl_latch; - uint16 cnt_latch; - uint8 ctrl; - t_bool r_lmb; - t_bool w_lmb; - t_bool enabled; - t_bool gate; - t_bool r_ctrl_latch; - t_bool r_cnt_latch; -}; - -t_stat timer_reset(DEVICE *dptr); -uint32 timer_read(uint32 pa, size_t size); -void timer_write(uint32 pa, uint32 val, size_t size); -void timer_gate(uint8 ctrnum, t_bool inhibit); - -t_stat tmr_svc(UNIT *uptr); -t_stat tmr_int_svc(UNIT *uptr); -CONST char *tmr_description(DEVICE *dptr); -t_stat tmr_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); - -#endif /* _3B2_TIMER_H_ */ +/* 3b2_timer.h: 8253/82C54 Interval Timer + + Copyright (c) 2021-2022, Seth J. Morabito + + 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 THE AUTHORS OR COPYRIGHT HOLDERS + 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 the author shall + not be used in advertising or otherwise to promote the sale, use or + other dealings in this Software without prior written authorization + from the author. +*/ + +#ifndef _3B2_TIMER_H_ +#define _3B2_TIMER_H_ + +#include "3b2_defs.h" + +#define TIMER_REG_DIVA 0x03 +#define TIMER_REG_DIVB 0x07 +#define TIMER_REG_DIVC 0x0b +#define TIMER_REG_CTRL 0x0f +#define TIMER_CLR_LATCH 0x13 + +#define TIMER_MODE(ctr) (((ctr->ctrl) >> 1) & 7) +#define TIMER_RW(ctr) (((ctr->ctrl) >> 4) & 3) + +#define CLK_LATCH 0 +#define CLK_LSB 1 +#define CLK_MSB 2 +#define CLK_LMB 3 + +#define TMR_SANITY 0 +#define TMR_INT 1 +#define TMR_BUS 2 + +struct timer_ctr { + uint16 divider; + uint16 val; + uint8 ctrl_latch; + uint16 cnt_latch; + uint8 ctrl; + t_bool r_lmb; + t_bool w_lmb; + t_bool enabled; + t_bool gate; + t_bool r_ctrl_latch; + t_bool r_cnt_latch; +}; + +t_stat timer_reset(DEVICE *dptr); +uint32 timer_read(uint32 pa, size_t size); +void timer_write(uint32 pa, uint32 val, size_t size); +void timer_gate(uint8 ctrnum, t_bool inhibit); + +t_stat tmr_svc(UNIT *uptr); +t_stat tmr_int_svc(UNIT *uptr); +CONST char *tmr_description(DEVICE *dptr); +t_stat tmr_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); + +#endif /* _3B2_TIMER_H_ */ diff --git a/3B2/README.md b/3B2/README.md index 0afa967b..dc3fb474 100644 --- a/3B2/README.md +++ b/3B2/README.md @@ -1,135 +1,54 @@ -AT&T 3B2 Simulator -================== - -This module contains the source for two simulators: - -1. A simulator for the AT&T 3B2/400 computer (3b2 or 3B2.EXE) -2. A simulator for the AT&T 3B2/700 computer (3b2-700 or 3B2-700.EXE) - -The 3B2/400 simulator is complete, usable, and robust. The 3B2/700 -simulator is under active development and is not yet considered -stable. - -Full documentation for the 3B2 simulator is available here: - - - https://loomcom.com/3b2/emulator.html - -3B2/400 Simulator Devices -------------------------- - -The following devices are simulated. The SIMH names for the simulated -devices are given in parentheses: - - - 3B2 Model 400 System Board with 1MB, 2MB, or 4MB RAM (CSR, NVRAM) - - WE32100 CPU (CPU) - - WE32101 MMU (MMU) - - PD8253 Interval Timer (TMR) - - AM9517 DMA controller (DMAC) - - SCN2681A Integrated DUART (IU) - - TMS2793 Integrated Floppy Controller (IFLOPPY) - - uPD7261A Integrated MFM Fixed Disk Controller (IDISK) - - Non-Volatile Memory (NVRAM) - - MM58174A Time Of Day Clock (TOD) - - CM195A Ethernet Network Interface (NI) - - CM195B 4-port Serial MUX (PORTS) - - CM195H Cartridge Tape Controller (CTC) - -3B2/400 Simulator Usage ------------------------ - -To boot the 3B2 simulator into firmware mode, simply type: - - sim> BOOT - -You will be greeted with the message: - - FW ERROR 1-01: NVRAM SANITY FAILURE - DEFAULT VALUES ASSUMED - IF REPEATED, CHECK THE BATTERY - - FW ERROR 1-02: DISK SANITY FAILURE - EXECUTION HALTED - - SYSTEM FAILURE: CONSULT YOUR SYSTEM ADMINISTRATION UTILITIES GUIDE - -NVRAM and Time of Day can be saved between boots by attaching both -devices to files. - - sim> ATTACH NVRAM - sim> ATTACH TOD - -If you have no operating system installed on the hard drive, on -subsequent boots you will instead see the message - - SELF-CHECK - - - FW ERROR 1-02: DISK SANITY FAILURE - EXECUTION HALTED - - SYSTEM FAILURE: CONSULT YOUR SYSTEM ADMINISTRATION UTILITIES GUIDE - - -Once you see the `SYSTEM FAILURE` message, this is actually an -invisible prompt. To access firmware mode, type the default 3B2 -firmware password `mcp`, then press Enter or carriage return. - -You should then be prompted with: - - Enter name of program to execute [ ]: - -Here, you may type a question mark (?) and press Enter to see a list -of available firmware programs. - -Booting UNIX SVR3 on the 3B2/400 --------------------------------- - -UNIX System V UNIX is the only operating system available for the 3B2. -To boot UNIX, attach the first disk image from the 3B2 "Essential -Utilities" distribution. - - sim> ATTACH IFLOPPY - sim> BOOT - -Once you reach the `SYSTEM FAILURE` message, type `mcp` to enter -firmware mode. When prompted for the name of a program to boot, enter -`unix`, and confirm the boot device is `FD5` by pressing Enter or -carriage return. - - Enter name of program to execute [ ]: unix - Possible load devices are: - - Option Number Slot Name - --------------------------------------- - 0 0 FD5 - - Enter Load Device Option Number [0 (FD5)]: - -Installing SVR3 on the 3B2/400 ------------------------------- - -To install SVR3 to the first hard disk, first, attach a new image -to the IDISK0 device: - - sim> ATTACH IDISK0 - -Then, boot the file `idtools` from the "3B2 Maintenance Utilities - -Issue 4.0" floppy diskette. - -From `idtools`, select the `formhard` option and low-level format -integrated disk 0. Parameters for the default 72MB hard disk are: - - Drive Id: 5 - Number cylinders: 925 - Number tracks/cyl: 9 - Number sectors/track: 18 - Number bytes/sector: 512 - -After low-level formatting integrated disk 0, boot the file `unix` -from the first diskette of the 3B2 "Essential Utilities" distribution, -and follow the prompts. - -More information about installing AT&T System V Release 3.2 UNIX is -available on the web: - - - https://loomcom.com/3b2/installing_unix.html +AT&T 3B2 Simulator +================== + +This module contains the source for two simulators: + +1. A simulator for the AT&T 3B2/400 computer (3b2-400 or 3B2-400.EXE) +2. A simulator for the AT&T 3B2/700 computer (3b2-700 or 3B2-700.EXE) + +Full documentation for the 3B2 simulator is available here: + + - https://loomcom.com/3b2/emulator.html + +3B2/400 Simulator Devices +------------------------- + +The following devices are simulated. The SIMH names for the simulated +devices are given in parentheses: + + - 3B2 Model 400 System Board with 1MB, 2MB, or 4MB RAM + - Configuration and Status Register (CSR) + - WE32100 CPU (CPU) + - WE32101 MMU (MMU) + - WE32106 Math Accelerator Unit (MAU) + - PD8253 Interval Timer (TMR) + - AM9517 DMA controller (DMAC) + - SCN2681A Integrated DUART (IU) + - TMS2793 Integrated Floppy Controller (IFLOPPY) + - uPD7261A Integrated MFM Fixed Disk Controller (IDISK) + - Non-Volatile Memory (NVRAM) + - MM58174A Time Of Day Clock (TOD) + - CM195A Ethernet Network Interface (NI) + - CM195B 4-port Serial MUX (PORTS) + - CM195H Cartridge Tape Controller (CTC) + +3B2/700 Simulator Devices +------------------------- + +The following devices are simulated. The SIMH names for the simulated +devices are given in parentheses: + + - 3B2 Model 700 System Board with 8MB, 16MB, 32MB, or 64MB RAM + - Configuration and Status Registers (CSR) + - WE32200 CPU (CPU) + - WE32201 MMU (MMU) + - WE32106 Math Accelerator Unit (MAU) + - PD8253 Interval Timer (TMR) + - AM9517 DMA controller (DMAC) + - SCN2681A Integrated DUART (IU) + - TMS2793 Integrated Floppy Controller (IFLOPPY) + - Non-Volatile Memory (NVRAM) + - MM58274C Time Of Day Clock (TOD) + - CM195W SCSI Host Adapter (SCSI) + - CM195A Ethernet Network Interface (NI) + - CM195B 4-port Serial MUX (PORTS)