From a7d0d4a6e774021da46793ddc2d8ea154e09bb21 Mon Sep 17 00:00:00 2001 From: Seth Morabito Date: Wed, 11 Apr 2018 18:07:50 -0700 Subject: [PATCH] 3b2: CIO feature card framework Adds a skeleton framework for CIO ("Common I/O") feature cards. The first feature card to be implemented will be the "PORTS" serial MUX. Part of this support involved reworking IRQ handling in the CPU. It now respects both IRQ Vector and IPL. This change also removes all 'assert(0)' calls from the simulator and replaces them with generic "Simulator Error" halts. These should only happen if there's a genuine logic error lurking somewhere. --- 3B2/3b2_cpu.c | 142 ++++++++++------- 3B2/3b2_cpu.h | 5 +- 3B2/3b2_defs.h | 46 ++++-- 3B2/3b2_id.c | 5 +- 3B2/3b2_if.c | 17 +- 3B2/3b2_io.c | 425 ++++++++++++++++++++++++++++++++++++++++++++++--- 3B2/3b2_io.h | 174 ++++++++++++++++++++ 3B2/3b2_sys.c | 3 +- 8 files changed, 720 insertions(+), 97 deletions(-) diff --git a/3B2/3b2_cpu.c b/3B2/3b2_cpu.c index 42f73baa..be044dfc 100644 --- a/3B2/3b2_cpu.c +++ b/3B2/3b2_cpu.c @@ -28,8 +28,6 @@ from the author. */ -#include - #include "3b2_cpu.h" #include "rom_400_bin.h" @@ -68,6 +66,8 @@ extern uint16 csr_data; uint32 R[16]; /* Other global CPU state */ +uint8 cpu_int_ipl = 0; /* Interrupt IPL level */ +uint8 cpu_int_vec = 0; /* Interrupt vector */ t_bool cpu_nmi = FALSE; /* If set, there has been an NMI */ int32 pc_incr = 0; /* Length (in bytes) of instruction @@ -147,6 +147,8 @@ MTAB cpu_mod[] = { &cpu_set_size, NULL, NULL, "Set Memory to 4M bytes" }, { 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", "STACK", NULL, &cpu_show_stack, NULL, "Display the current stack with optional depth" }, { MTAB_XTD|MTAB_VDV, 0, "IDLE", "IDLE", &sim_set_idle, &sim_show_idle }, @@ -566,6 +568,10 @@ t_stat cpu_boot(int32 unit_num, DEVICE *dptr) * in the PCB, if bit I in PSW is set. */ + 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); @@ -582,10 +588,6 @@ t_stat cpu_boot(int32 unit_num, DEVICE *dptr) R[NUM_PSW] &= ~PSW_ISC_MASK; R[NUM_PSW] |= 3 << PSW_ISC ; - sim_debug(EXECUTE_MSG, &cpu_dev, - ">>> CPU BOOT/RESET COMPLETE. PC=%08x SP=%08x\n", - R[NUM_PC], R[NUM_SP]); - return SCPE_OK; } @@ -973,6 +975,29 @@ void fprint_sym_hist(FILE *st, instr *ip) } } +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; + } + } + } + + fprintf(of, "Translation not possible.\n"); + + return SCPE_OK; +} + + t_stat cpu_show_hist(FILE *st, UNIT *uptr, int32 val, CONST void *desc) { uint32 i; @@ -1528,10 +1553,9 @@ static SIM_INLINE void cpu_context_switch_1(uint32 new_pcbp) } } -t_bool cpu_on_interrupt(uint8 ipl) +void cpu_on_interrupt(uint16 vec) { uint32 new_pcbp; - uint16 id = ipl; /* TODO: Does this need to be uint16? */ /* * "If a nonmaskable interrupt request is received, an auto-vector @@ -1540,7 +1564,7 @@ t_bool cpu_on_interrupt(uint8 ipl) * Interrupt-ID is fetched. The value 0 is used as the ID." */ if (cpu_nmi) { - id = 0; + vec = 0; } cpu_km = TRUE; @@ -1548,10 +1572,11 @@ t_bool cpu_on_interrupt(uint8 ipl) if (R[NUM_PSW] & PSW_QIE_MASK) { /* TODO: Maybe implement quick interrupts at some point, but the 3B2 ROM and SVR3 don't appear to use them. */ - assert(0); + stop_reason = STOP_ERR; + return; } - new_pcbp = read_w(0x8c + (4 * id), ACC_AF); + new_pcbp = read_w(0x8c + (4 * vec), ACC_AF); /* Save the old PCBP */ irq_push_word(R[NUM_PCBP]); @@ -1572,8 +1597,6 @@ t_bool cpu_on_interrupt(uint8 ipl) cpu_context_switch_3(new_pcbp); cpu_km = FALSE; - - return TRUE; } t_stat sim_instr(void) @@ -1590,6 +1613,9 @@ t_stat sim_instr(void) uint32 width, offset; uint32 mask; + /* Generic index */ + uint32 i; + operand *src1, *src2, *src3, *dst; stop_reason = 0; @@ -1698,9 +1724,20 @@ t_stat sim_instr(void) increment_modep_b(); } - /* Process pending IRQ, if applicable */ - if (PSW_CUR_IPL < cpu_ipl()) { - cpu_on_interrupt(cpu_ipl()); + /* Set the correct IRQ state */ + cpu_calc_ints(); + + if (PSW_CUR_IPL < cpu_int_ipl) { + cpu_on_interrupt(cpu_int_vec); + for (i = 0; i < CIO_SLOTS; i++) { + if (cio[i].intr && + cio[i].ipl == cpu_int_ipl && + cio[i].ivec == cpu_int_vec) { + cio[i].intr = FALSE; + } + } + cpu_int_ipl = 0; + cpu_int_vec = 0; cpu_nmi = FALSE; cpu_in_wait = FALSE; } @@ -3171,7 +3208,9 @@ static uint32 cpu_read_op(operand * op) data = sign_extend_b(R[op->reg] & BYTE_MASK); break; default: - assert(0); + stop_reason = STOP_ERR; + data = 0; + break; } op->data = data; @@ -3233,7 +3272,7 @@ static uint32 cpu_read_op(operand * op) op->data = data; return data; default: - assert(0); + stop_reason = STOP_ERR; return 0; } } @@ -3288,53 +3327,42 @@ static void cpu_write_op(operand * op, t_uint64 val) write_b(eff, val & BYTE_MASK); break; default: - assert(0); + stop_reason = STOP_ERR; + break; } } /* - * This returns the current state of the IPL (Interrupt - * Priority Level) bus. This is affected by: - * - * - Latched values in the CSR for: - * o CSRCLK 15 - * o CSRDMA 13 - * o CSRUART 13 - * o CSRDISK 11 - * o CSRPIR9 9 - * o CSRPIR8 8 - * - IRQ currently enabled for: - * o HD Ctlr. 11 + * Calculate the current state of interrupts. + * TODO: This could use a refactor. It's getting code-smelly. */ -static SIM_INLINE uint8 cpu_ipl() +static void cpu_calc_ints() { - /* CSRPIR9 is cleared by writing to c_pir8 */ + uint32 i; + + /* First scan for a CIO interrupt */ + for (i = 0; i < CIO_SLOTS; i++) { + if (cio[i].intr) { + cpu_int_ipl = cio[i].ipl; + cpu_int_vec = cio[i].ivec; + return; + } + } + + /* If none was found, look for system board interrupts */ if (csr_data & CSRPIR8) { - return 8; + cpu_int_ipl = cpu_int_vec = CPU_PIR8_IPL; + } else if (csr_data & CSRPIR9) { + cpu_int_ipl = cpu_int_vec = CPU_PIR9_IPL; + } else if (id_int() || (csr_data & CSRDISK)) { + cpu_int_ipl = cpu_int_vec = CPU_ID_IF_IPL; + } else if ((csr_data & CSRUART) || (csr_data & CSRDMA)) { + cpu_int_ipl = cpu_int_vec = CPU_IU_DMA_IPL; + } else if (csr_data & CSRCLK) { + cpu_int_ipl = cpu_int_vec = CPU_TMR_IPL; + } else { + cpu_int_ipl = cpu_int_vec = 0; } - - /* CSRPIR9 is cleared by writing to c_pir9 */ - if (csr_data & CSRPIR9) { - return 9; - } - - /* CSRDISK is cleared when the floppy "if_irq" goes low */ - if (id_int() || (csr_data & CSRDISK)) { - return 11; - } - - /* CSRDMA is cleared by write/read to 0x49011 */ - /* CSRUART is cleared when the uart "iu_irq" goes low */ - if ((csr_data & CSRUART) || (csr_data & CSRDMA)) { - return 13; - } - - /* CSRCLK is cleared by $clrclkint */ - if (csr_data & CSRCLK) { - return 15; - } - - return 0; } /* diff --git a/3B2/3b2_cpu.h b/3B2/3b2_cpu.h index feaa20ab..1de5728b 100644 --- a/3B2/3b2_cpu.h +++ b/3B2/3b2_cpu.h @@ -395,6 +395,7 @@ 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_set_halt(UNIT *uptr, int32 val, char *cptr, void *desc); t_stat cpu_clear_halt(UNIT *uptr, int32 val, char *cptr, void *desc); @@ -410,14 +411,14 @@ t_stat fprint_sym_m(FILE *of, t_addr addr, t_value *val); instr *cpu_next_instruction(void); uint8 decode_instruction(instr *instr); -t_bool cpu_on_interrupt(uint8 ipl); +void cpu_on_interrupt(uint16 vec); 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 void cpu_calc_ints(); -static SIM_INLINE uint8 cpu_ipl(); 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); diff --git a/3B2/3b2_defs.h b/3B2/3b2_defs.h index fdc9ed01..408d9491 100644 --- a/3B2/3b2_defs.h +++ b/3B2/3b2_defs.h @@ -50,6 +50,8 @@ noret __libc_longjmp (jmp_buf buf, int val); #define longjmp __libc_longjmp #endif +/* -t flag: Translate a virtual address */ +#define EX_T_FLAG 1 << 19 /* -v flag for examine routine */ #define EX_V_FLAG 1 << 21 @@ -95,6 +97,13 @@ noret __libc_longjmp (jmp_buf buf, int val); #define NUM_ISP 14 #define NUM_PC 15 +/* System board interrupt priority levels */ +#define CPU_PIR8_IPL 8 +#define CPU_PIR9_IPL 9 +#define CPU_ID_IF_IPL 11 +#define CPU_IU_DMA_IPL 13 +#define CPU_TMR_IPL 15 + #define CPU_CM (cpu_km ? L_KERNEL : ((R[NUM_PSW] >> PSW_CM) & 3)) /* Simulator stop codes */ @@ -106,6 +115,7 @@ noret __libc_longjmp (jmp_buf buf, int val); #define STOP_ESTK 6 /* Exception stack too deep */ #define STOP_MMU 7 /* Unimplemented MMU Feature */ #define STOP_POWER 8 /* System power-off */ +#define STOP_ERR 9 /* Other error */ /* Exceptional conditions handled within the instruction loop */ #define ABORT_EXC 1 /* CPU exception */ @@ -292,6 +302,18 @@ noret __libc_longjmp (jmp_buf buf, int val); #define CLK_MD4 0x08 #define CLK_MD5 0x0a +/* IO area */ + +#define MEMSIZE_REG 0x4C003 +#define CIO_BOTTOM 0x200000 +#define CIO_TOP 0x2000000 + +#define CIO_CSBIT 0x80 +#define CIO_SEQBIT 0x40 + +#define CIO_INT_DELAY 8000 + + /* Timer definitions */ #define TMR_CLK 0 /* The clock responsible for IPL 15 interrupts */ @@ -325,9 +347,6 @@ extern uint8 fault; extern DEBTAB sys_deb_tab[]; extern t_bool cpu_km; -/* Generic callback function */ -typedef void (*callback)(void); - /* global symbols from the DMAC */ typedef struct { uint8 page; @@ -378,6 +397,7 @@ extern void increment_modep_a(); extern void increment_modep_b(); /* global symbols from the MMU */ +extern t_stat mmu_decode_va(uint32 va, uint8 r_acc, t_bool fc, uint32 *pa); extern void mmu_enable(); extern void mmu_disable(); extern uint8 read_b(uint32 va, uint8 acc); @@ -388,12 +408,20 @@ extern void write_h(uint32 va, uint16 val); extern void write_w(uint32 va, uint32 val); /* Globally scoped CPU functions */ -void cpu_abort(uint8 et, uint8 isc); -void cpu_set_irq(uint8 ipl, uint8 id, uint16 csr_flags); -void cpu_clear_irq(uint8 ipl, uint16 csr_flags); +extern void cpu_abort(uint8 et, uint8 isc); +extern void cpu_set_irq(uint8 ipl, uint8 id, uint16 csr_flags); +extern void cpu_clear_irq(uint8 ipl, uint16 csr_flags); -/* Globally scoped IO functions */ -uint32 io_read(uint32 pa, size_t size); -void io_write(uint32 pa, uint32 val, size_t size); +/* global symbols from the IO system */ +extern uint32 io_read(uint32 pa, size_t size); +extern void io_write(uint32 pa, uint32 val, size_t size); +extern void cio_xfer(); +extern uint8 cio_int; +extern uint16 cio_ipl; + +/* Future Use: Global symbols from the PORTS card */ +/* extern void ports_express(uint8 cid); */ +/* extern void ports_full(uint8 cid); */ +/* extern void ports_xfer(uint8 cid); */ #endif diff --git a/3B2/3b2_id.c b/3B2/3b2_id.c index 522abfa8..1dd2b59a 100644 --- a/3B2/3b2_id.c +++ b/3B2/3b2_id.c @@ -42,7 +42,6 @@ * HD135 11 1224 15 18 512 Maxtor XT1190 */ -#include #include "3b2_id.h" /* Wait times, in CPU steps, for various actions */ @@ -525,7 +524,9 @@ uint32 id_read(uint32 pa, size_t size) } } } else { - assert(0); // cmd not Read Data or Read ID + /* cmd not Read Data or Read ID */ + stop_reason = STOP_ERR; + return 0; } return data; diff --git a/3B2/3b2_if.c b/3B2/3b2_if.c index 3d6e291e..e38531a7 100644 --- a/3B2/3b2_if.c +++ b/3B2/3b2_if.c @@ -29,7 +29,6 @@ */ #include "3b2_if.h" -#include /* * TODO: Macros used for debugging timers. Remove when debugging is complete. @@ -397,7 +396,12 @@ void if_handle_command() } break; case IF_READ_SEC_M: - assert(0); + /* 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: sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tWrite Sector %d/%d/%d\n", if_state.cmd, if_state.track, if_state.side, if_state.sector); @@ -411,7 +415,11 @@ void if_handle_command() } break; case IF_WRITE_SEC_M: - assert(0); + /* 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); @@ -421,7 +429,8 @@ void if_handle_command() break; case IF_READ_TRACK: sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tRead Track\n", if_state.cmd); - assert(0); /* NOT YET IMPLEMENTED */ + /* 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); diff --git a/3B2/3b2_io.c b/3B2/3b2_io.c index 91cea14a..8a12e60f 100644 --- a/3B2/3b2_io.c +++ b/3B2/3b2_io.c @@ -1,4 +1,4 @@ -/* 3b2_cpu.h: AT&T 3B2 Model 400 IO dispatch implemenation +/* 3b2_cpu.h: AT&T 3B2 Model 400 IO and CIO feature cards Copyright (c) 2017, Seth J. Morabito @@ -30,6 +30,8 @@ #include "3b2_io.h" +CIO_STATE cio[CIO_SLOTS] = { 0 }; + struct iolink iotable[] = { { MMUBASE, MMUBASE+MMUSIZE, &mmu_read, &mmu_write }, { IFBASE, IFBASE+IFSIZE, &if_read, &if_write }, @@ -47,13 +49,197 @@ struct iolink iotable[] = { { 0, 0, NULL, NULL} }; +void cio_sysgen(uint8 cid) +{ + uint32 sysgen_p; + uint32 cq_exp; + cio_entry cqe; + + sysgen_p = pread_w(SYSGEN_PTR); + + sim_debug(IO_D_MSG, &cpu_dev, + "[%08x] [SYSGEN] Starting sysgen for card %d. sysgen_p=%08x\n", + R[NUM_PC], cid, sysgen_p); + + /* seqbit is always reset to 0 on completion */ + cio[cid].seqbit = 0; + + cio[cid].rqp = pread_w(sysgen_p); + cio[cid].cqp = pread_w(sysgen_p + 4); + cio[cid].rqs = pread_b(sysgen_p + 8); + cio[cid].cqs = pread_b(sysgen_p + 9); + cio[cid].ivec = pread_b(sysgen_p + 10); + cio[cid].no_rque = pread_b(sysgen_p + 11); + + sim_debug(IO_D_MSG, &cpu_dev, + "[SYSGEN] sysgen rqp = %08x\n", + cio[cid].rqp); + sim_debug(IO_D_MSG, &cpu_dev, + "[SYSGEN] sysgen cqp = %08x\n", + cio[cid].cqp); + sim_debug(IO_D_MSG, &cpu_dev, + "[SYSGEN] sysgen rqs = %02x\n", + cio[cid].rqs); + sim_debug(IO_D_MSG, &cpu_dev, + "[SYSGEN] sysgen cqs = %02x\n", + cio[cid].cqs); + sim_debug(IO_D_MSG, &cpu_dev, + "[SYSGEN] sysgen ivec = %02x\n", + cio[cid].ivec); + sim_debug(IO_D_MSG, &cpu_dev, + "[SYSGEN] sysgen no_rque = %02x\n", + cio[cid].no_rque); + + cq_exp = cio[cid].cqp; + + cqe.byte_count = 0; + cqe.subdevice = 0; + cqe.opcode = 3; + cqe.address = 0; + cqe.app_data = 0; + + cio_cexpress(cid, &cqe); + sim_debug(IO_D_MSG, &cpu_dev, + "[SYSGEN] Sysgen complete. Completion Queue written.\n"); + + /* If the card has a custom sysgen handler, run it */ + if (cio[cid].sysgen != NULL) { + cio[cid].sysgen(cid); + } else { + sim_debug(IO_D_MSG, &cpu_dev, + "[%08x] [cio_sysgen] Not running custom sysgen.\n", + R[NUM_PC]); + } +} + +void cio_cexpress(uint8 cid, cio_entry *cqe) +{ + uint32 cqp; + + cqp = cio[cid].cqp; + + sim_debug(IO_D_MSG, &cpu_dev, + "[%08x] [cio_cexpress] cqp = %08x seqbit = %d\n", + R[NUM_PC], cqp, cio[cid].seqbit); + + cio[cid].seqbit ^= 1; + + if (cio[cid].seqbit) { + cqe->subdevice |= CIO_SEQBIT; + } + + pwrite_h(cqp, cqe->byte_count); + pwrite_b(cqp + 2, cqe->subdevice); + pwrite_b(cqp + 3, cqe->opcode); + pwrite_w(cqp + 4, cqe->address); + pwrite_w(cqp + 8, cqe->app_data); +} + +/* Write an entry into the Completion Queue */ +void cio_cqueue(uint8 cid, cio_entry *cqe) +{ + uint32 cqp, top; + uint16 lp; + + /* Get the physical address of the completion queue + * in main memory */ + cqp = cio[cid].cqp; + + /* Get the physical address of the first entry in + * the completion queue */ + top = cqp + QUE_OFFSET; + + /* 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 + LOAD_OFFSET); + + /* Load the entry at the supplied address */ + pwrite_h(top + lp, cqe->byte_count); + pwrite_b(top + lp + 2, cqe->subdevice); + pwrite_b(top + lp + 3, cqe->opcode); + pwrite_w(top + lp + 4, cqe->address); + pwrite_w(top + lp + 8, cqe->app_data); + + /* 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[cid].cqs > 0) { + lp = (lp + QUE_E_SIZE) % (QUE_E_SIZE * cio[cid].cqs); + + /* Store it back to the correct location */ + pwrite_h(cqp + LOAD_OFFSET, lp); + } else { + sim_debug(IO_D_MSG, &cpu_dev, + "[%08x] [cio_cqueue] ERROR! Completion Queue Size is 0!", + R[NUM_PC]); + } + +} + +/* Retrieve an entry from the Request Queue */ +void cio_rqueue(uint8 cid, cio_entry *cqe) +{ + uint32 rqp, top, i; + uint16 ulp; + + /* Get the physical address of the request queue in main memory */ + rqp = cio[cid].rqp + 12; /* Skip past the Express Queue Entry */ + + /* Scan each queue until we find one with a command in it. */ + for (i = 0; i < cio[cid].no_rque; i++) { + /* Get the physical address of the first entry in the request + * queue */ + top = rqp + 4; + + /* Check to see what we've got in the queue. */ + ulp = pread_h(rqp + 2); + + cqe->opcode = pread_b(top + ulp + 3); + + if (cqe->opcode > 0) { + break; + } + + rqp += 4 + (12 * cio[cid].rqs); + } + + if (i >= cio[cid].no_rque) { + sim_debug(IO_D_MSG, &cpu_dev, + "[%08x] [cio_rque] FAILURE! NO MORE QUEUES TO EXAMINE.\n", + R[NUM_PC]); + return; + } + + /* Retrieve the entry at the supplied address */ + cqe->byte_count = pread_h(top + ulp); + cqe->subdevice = pread_b(top + ulp + 2); + cqe->address = pread_w(top + ulp + 4); + cqe->app_data = pread_w(top + ulp + 8); + + dump_entry("REQUEST", cqe); + + /* 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[cid].rqs > 0) { + ulp = (ulp + QUE_E_SIZE) % (QUE_E_SIZE * cio[cid].rqs); + + /* Store it back to the correct location */ + pwrite_h(rqp + 2, ulp); + } else { + sim_debug(IO_D_MSG, &cpu_dev, + "[%08x] [cio_rqueue] ERROR! Request Queue Size is 0!", + R[NUM_PC]); + } +} + uint32 io_read(uint32 pa, size_t size) { struct iolink *p; + uint8 cid, reg, data; /* Special devices */ - if (pa == 0x4c003) { - /* MEMSIZE register */ + if (pa == MEMSIZE_REG) { /* It appears that the following values map to memory sizes: 0x00: 512KB ( 524,288 B) @@ -76,20 +262,112 @@ uint32 io_read(uint32 pa, size_t size) } /* IO Board Area - Unimplemented */ - if (pa >= 0x200000 && pa < 0x2000000) { - sim_debug(IO_D_MSG, &cpu_dev, "[%08x] [IO BOARD READ] ADDR=%08x\n", R[NUM_PC], pa); - /* When we implement boards, register them here - N.B.: High byte of board ID is read at 0xnnnnn0, - low byte at 0xnnnnn1 */ + if (pa >= CIO_BOTTOM && pa < CIO_TOP) { + cid = CID(pa); + reg = pa - CADDR(cid); - /* Since we have no cards in our system, there's nothing - to read. We indicate that our bus read timed out with - CSRTIMO, then abort.*/ - csr_data |= CSRTIMO; - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); - return 0; + if (cio[cid].id == 0) { + /* Nothing lives here */ + csr_data |= CSRTIMO; + 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[cid].cmdbits) { + case 0x00: /* We've never seen an INT0 or INT1 */ + case 0x01: /* We've seen an INT0 but not an INT1. */ + sim_debug(IO_D_MSG, &cpu_dev, + "[READ] [%08x] (%d INT0) ID\n", + R[NUM_PC], cid); + /* Return the correct byte of our board ID */ + if (reg == IOF_ID) { + data = (cio[cid].id >> 8) & 0xff; + } else { + data = (cio[cid].id & 0xff); + } + break; + case 0x02: /* We've seen an INT1 but not an INT0. Time to sysgen */ + sim_debug(IO_D_MSG, &cpu_dev, + "[READ] [%08x] (%d INT0) SYSGEN\n", + R[NUM_PC], cid); + cio_sysgen(cid); + data = cio[cid].ivec; + break; + case 0x03: /* We've already sysgen'ed */ + sim_debug(IO_D_MSG, &cpu_dev, + "[READ] [%08x] (%d INT0) EXPRESS JOB\n", + R[NUM_PC], cid); + cio[cid].exp_handler(cid); + data = cio[cid].ivec; + break; + default: + /* This should never happen */ + stop_reason = STOP_ERR; + sim_debug(IO_D_MSG, &cpu_dev, + "[READ] [%08x] (%d INT0) ERROR IN STATE MACHINE cmdbits=%02x\n", + R[NUM_PC], cid, cio[cid].cmdbits); + data = 0; + break; + } + + /* Record that we've seen an INT0 */ + cio[cid].cmdbits |= CIO_INT0; + return data; + case IOF_CTRL: + switch(cio[cid].cmdbits) { + case 0x00: /* We've never seen an INT0 or INT1 */ + case 0x02: /* We've seen an INT1 but not an INT0 */ + /* There's nothing to do in this instance */ + break; + case 0x01: /* We've seen an INT0 but not an INT1. Time to sysgen */ + sim_debug(IO_D_MSG, &cpu_dev, + "[READ] [%08x] (%d INT1) SYSGEN\n", + R[NUM_PC], cid); + cio_sysgen(cid); + break; + case 0x03: /* We've already sysgen'ed */ + sim_debug(IO_D_MSG, &cpu_dev, + "[READ] [%08x] (%d INT1) FULL\n", + R[NUM_PC], cid); + cio[cid].full_handler(cid); + break; + default: + /* This should never happen */ + stop_reason = STOP_ERR; + sim_debug(IO_D_MSG, &cpu_dev, + "[READ] [%08x] (%d INT1) ERROR IN STATE MACHINE cmdbits=%02x\n", + R[NUM_PC], cid, cio[cid].cmdbits); + break; + } + + /* Record that we've seen an INT1 */ + cio[cid].cmdbits |= CIO_INT1; + return 0; /* Data returned is arbitrary */ + case IOF_STAT: + sim_debug(IO_D_MSG, &cpu_dev, + "[READ] [%08x] (%d RESET)\n", + R[NUM_PC], cid); + cio[cid].cmdbits = 0; + return 0; /* Data returned is arbitrary */ + default: + /* We should never reach here, but if we do, there's + * nothing listening. */ + csr_data |= CSRTIMO; + 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); @@ -108,17 +386,109 @@ uint32 io_read(uint32 pa, size_t size) void io_write(uint32 pa, uint32 val, size_t size) { struct iolink *p; + uint8 cid, reg; - /* IO Board Area - Unimplemented */ - if (pa >= 0x200000 && pa < 0x2000000) { - sim_debug(IO_D_MSG, &cpu_dev, - "[%08x] ADDR=%08x, DATA=%08x\n", - R[NUM_PC], pa, val); - csr_data |= CSRTIMO; - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); - return; + /* Feature Card Area */ + if (pa >= CIO_BOTTOM && pa < CIO_TOP) { + cid = CID(pa); + reg = pa - CADDR(cid); + + if (cio[cid].id == 0) { + /* Nothing lives here */ + csr_data |= CSRTIMO; + 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[cid].cmdbits) { + case 0x00: /* We've never seen an INT0 or INT1 */ + case 0x01: /* We've seen an INT0 but not an INT1. */ + sim_debug(IO_D_MSG, &cpu_dev, + "[WRITE] [%08x] (%d INT0) ID\n", + R[NUM_PC], cid); + break; + case 0x02: /* We've seen an INT1 but not an INT0. Time to sysgen */ + sim_debug(IO_D_MSG, &cpu_dev, + "[READ] [%08x] (%d INT0) SYSGEN\n", + R[NUM_PC], cid); + cio_sysgen(cid); + break; + case 0x03: /* We've already sysgen'ed */ + sim_debug(IO_D_MSG, &cpu_dev, + "[READ] [%08x] (%d INT0) EXPRESS JOB\n", + R[NUM_PC], cid); + cio[cid].exp_handler(cid); + break; + default: + /* This should never happen */ + stop_reason = STOP_ERR; + sim_debug(IO_D_MSG, &cpu_dev, + "[READ] [%08x] (%d INT0) ERROR IN STATE MACHINE cmdbits=%02x\n", + R[NUM_PC], cid, cio[cid].cmdbits); + break; + } + + /* Record that we've seen an INT0 */ + cio[cid].cmdbits |= CIO_INT0; + return; + case IOF_CTRL: + switch(cio[cid].cmdbits) { + case 0x00: /* We've never seen an INT0 or INT1 */ + case 0x02: /* We've seen an INT1 but not an INT0 */ + /* There's nothing to do in this instance */ + sim_debug(IO_D_MSG, &cpu_dev, + "[WRITE] [%08x] (%d INT1)\n", + R[NUM_PC], cid); + break; + case 0x01: /* We've seen an INT0 but not an INT1. Time to sysgen */ + sim_debug(IO_D_MSG, &cpu_dev, + "[WRITE] [%08x] (%d INT1) SYSGEN\n", + R[NUM_PC], cid); + cio_sysgen(cid); + break; + case 0x03: /* We've already sysgen'ed */ + sim_debug(IO_D_MSG, &cpu_dev, + "[WRITE] [%08x] (%d INT1) FULL\n", + R[NUM_PC], cid); + cio[cid].full_handler(cid); + break; + default: + /* This should never happen */ + stop_reason = STOP_ERR; + sim_debug(IO_D_MSG, &cpu_dev, + "[WRITE] [%08x] (%d INT1) ERROR IN STATE MACHINE cmdbits=%02x\n", + R[NUM_PC], cid, cio[cid].cmdbits); + break; + } + + /* Record that we've seen an INT1 */ + cio[cid].cmdbits |= CIO_INT1; + return; + case IOF_STAT: + sim_debug(IO_D_MSG, &cpu_dev, + "[WRITE] [%08x] (%d RESET)\n", + R[NUM_PC], cid); + cio[cid].cmdbits = 0; + return; + default: + /* We should never reach here, but if we do, there's + * nothing listening. */ + csr_data |= CSRTIMO; + 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); @@ -133,3 +503,14 @@ void io_write(uint32 pa, uint32 val, size_t size) csr_data |= CSRTIMO; cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); } + + +/* For debugging only */ +void dump_entry(CONST char *type, cio_entry *entry) +{ + sim_debug(IO_D_MSG, &cpu_dev, + "*** %s ENTRY: byte_count=%04x, subdevice=%02x,\n" + " opcode=%d, address=%08x, app_data=%08x\n", + type, entry->byte_count, entry->subdevice, + entry->opcode, entry->address, entry->app_data); +} diff --git a/3B2/3b2_io.h b/3B2/3b2_io.h index c32289f5..660ef850 100644 --- a/3B2/3b2_io.h +++ b/3B2/3b2_io.h @@ -28,6 +28,72 @@ 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 + * - A set of pointers for load and unload from the queue + * - Zero or more Queue Entries + * + * | Address | Size | Contents | + * +---------------+------+-----------------------------------------+ + * | QUEUE_P | 12 | Express Queue Entry [1] | + * | QUEUE_P + 12 | 2 | Load Pointer | + * | QUEUE_P + 14 | 2 | Unload Pointer | + * | QUEUE_P + 16 | 12 | Entry 0 [1] | + * | QUEUE_P + 28 | 12 | Entry 1 [1] | + * | ... | ... | ... | + * + * [1] See Queue Entry above + */ + #ifndef _3B2_IO_H_ #define _3B2_IO_H_ @@ -38,6 +104,69 @@ #include "3b2_dmac.h" #include "3b2_mmu.h" +#include "sim_tmxr.h" + +#define IOF_ID 0 +#define IOF_VEC 1 +#define IOF_CTRL 3 +#define IOF_STAT 5 + +#define SYSGEN_PTR PHYS_MEM_BASE +#define CIO_LOAD_SIZE 0x4 +#define CIO_ENTRY_SIZE 0x0c +#define CIO_QUE_OFFSET 0x10 +#define CIO_SLOTS 12 + +/* CIO opcodes */ +#define CIO_DLM 1 +#define CIO_ULM 2 +#define CIO_FCF 3 +#define CIO_DOS 4 +#define CIO_DSD 5 + +/* Map a physical address to a card ID */ +#define CID(pa) (((((pa) >> 0x14) & 0x1f) / 2) - 1) +/* Map a card ID to a base address */ +#define CADDR(bid) (((((bid) + 1) * 2) << 0x14)) + +#define CIO_INT0 0x1 +#define CIO_INT1 0x2 + +/* Offsets into the request/completion queues of various values */ +#define LOAD_OFFSET 12 +#define ULOAD_OFFSET 14 +#define QUE_OFFSET 16 +#define QUE_E_SIZE 12 + +#define CIO_SYGEN_MASK 0x3 + +typedef struct { + uint16 id; /* Card ID */ + void (*exp_handler)(uint8 cid); /* Handler for express jobs */ + void (*full_handler)(uint8 cid); /* Handler for full jobs */ + void (*sysgen)(uint8 cid); /* Sysgen routine (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 */ + t_bool intr; /* Card needs to interrupt */ + uint8 cmdbits; /* Commands received since RESET */ + uint8 seqbit; /* Squence Bit */ + uint8 op; /* Last received opcode */ + TMLN *lines[4]; /* Terminal Multiplexer lines */ +} CIO_STATE; + +typedef struct { + uint16 byte_count; + uint8 subdevice; + uint8 opcode; + uint32 address; + uint32 app_data; +} cio_entry; + struct iolink { uint32 low; uint32 high; @@ -45,4 +174,49 @@ struct iolink { void (*write)(uint32 pa, uint32 val, size_t size); }; +/* 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; + +extern uint16 cio_ints; +extern CIO_STATE cio[CIO_SLOTS]; + +t_stat cio_reset(DEVICE *dptr); +t_stat cio_svc(UNIT *uptr); + +/* Put an entry into the Completion Queue's Express entry */ +void cio_cexpress(uint8 cid, cio_entry *cqe); +/* Put an entry into the Completion Queue */ +void cio_cqueue(uint8 cid, cio_entry *cqe); +/* Get an entry from the Request Queue */ +void cio_rqueue(uint8 cid, cio_entry *cqe); +/* Perform a Sysgen */ +void cio_sysgen(uint8 cid); +/* Debugging only */ +void dump_entry(CONST char *type, cio_entry *entry); + #endif diff --git a/3B2/3b2_sys.c b/3B2/3b2_sys.c index 3c57d867..60b1eaf4 100644 --- a/3B2/3b2_sys.c +++ b/3B2/3b2_sys.c @@ -73,7 +73,8 @@ const char *sim_stop_messages[] = { "Exception/Trap", "Exception Stack Too Deep", "Unimplemented MMU Feature", - "System Powered Off" + "System Powered Off", + "Simulator Error" }; void full_reset()