From b0a4fb443c380632c1ac90d473bbd3b651df66a4 Mon Sep 17 00:00:00 2001 From: Seth Morabito Date: Sat, 9 Mar 2019 10:44:52 -0800 Subject: [PATCH] 3b2: NI 10Base5 Ethernet Device - Implements an Ethernet device ("NI", for "Network Interface") for the 3B2 simulator. --- 3B2/3b2_cpu.c | 17 +- 3B2/3b2_defs.h | 28 +- 3B2/3b2_if.c | 2 - 3B2/3b2_io.c | 136 +-- 3B2/3b2_io.h | 58 +- 3B2/3b2_mmu.h | 3 + 3B2/3b2_ni.c | 1270 +++++++++++++++++++++++++++++ 3B2/3b2_ni.h | 219 +++++ 3B2/3b2_ports.c | 36 +- 3B2/3b2_sys.c | 3 + Visual Studio Projects/3B2.vcproj | 203 ++++- makefile | 6 +- 12 files changed, 1859 insertions(+), 122 deletions(-) create mode 100644 3B2/3b2_ni.c create mode 100644 3B2/3b2_ni.h diff --git a/3B2/3b2_cpu.c b/3B2/3b2_cpu.c index ec7443a9..c41e06eb 100644 --- a/3B2/3b2_cpu.c +++ b/3B2/3b2_cpu.c @@ -170,9 +170,10 @@ static DEBTAB cpu_deb_tab[] = { { "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, 0, NULL } }; UNIT cpu_unit = { UDATA (NULL, UNIT_FIX|UNIT_BINK|UNIT_IDLE, MAXMEMSIZE) }; @@ -180,9 +181,13 @@ UNIT cpu_unit = { UDATA (NULL, UNIT_FIX|UNIT_BINK|UNIT_IDLE, MAXMEMSIZE) }; #define UNIT_V_EXHALT (UNIT_V_UF + 0) /* halt to console */ #define UNIT_EXHALT (1u << UNIT_V_EXHALT) +/* + * TODO: This works fine for now, but the moment we want to emulate + * SCSI (0x0100) or EPORTS (0x0102) we're in trouble! + */ const char *cio_names[8] = { - "", "*VOID*", "*VOID*", "PORTS", - "*VOID*", "CTC", "*VOID*", "*VOID*" + "", "SBD", "NI", "PORTS", + "*VOID*", "CTC", "NAU", "*VOID*" }; MTAB cpu_mod[] = { @@ -1821,9 +1826,9 @@ t_stat sim_instr(void) if (cio[i].intr && cio[i].ipl == cpu_int_ipl && cio[i].ivec == cpu_int_vec) { - sim_debug(IO_DBG, &cpu_dev, - "[%08x] [IRQ] Handling CIO interrupt for card %d\n", - R[NUM_PC], i); + sim_debug(CIO_DBG, &cpu_dev, + "[%08x] [IRQ] Handling CIO interrupt for card %d ivec=%02x\n", + R[NUM_PC], i, cpu_int_vec); cio[i].intr = FALSE; } diff --git a/3B2/3b2_defs.h b/3B2/3b2_defs.h index c51e3f19..e3c0fe75 100644 --- a/3B2/3b2_defs.h +++ b/3B2/3b2_defs.h @@ -77,6 +77,12 @@ noret __libc_longjmp (jmp_buf buf, int val); #define HALF_MASK 0xffffu #define BYTE_MASK 0xff +/* + * Custom t_stat + */ + +#define SCPE_PEND (SCPE_OK + 1) /* CIO job already pending */ +#define SCPE_NOJOB (SCPE_OK + 2) /* No CIO job on the request queue */ /* * * Physical memory in the 3B2 is arranged as follows: @@ -141,15 +147,19 @@ noret __libc_longjmp (jmp_buf buf, int val); #define C_STACK_FAULT 9 /* Debug flags */ -#define READ_MSG 0x001 -#define WRITE_MSG 0x002 -#define DECODE_MSG 0x004 -#define EXECUTE_MSG 0x008 -#define INIT_MSG 0x010 -#define IRQ_MSG 0x020 -#define IO_DBG 0x040 -#define TRACE_DBG 0x080 -#define ERR_MSG 0x100 +#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 /* Data types operated on by instructions. NB: These integer values have meaning when decoding instructions, so this is not just an diff --git a/3B2/3b2_if.c b/3B2/3b2_if.c index 7abfcc83..c4a1def2 100644 --- a/3B2/3b2_if.c +++ b/3B2/3b2_if.c @@ -171,12 +171,10 @@ t_stat if_detach(UNIT *uptr) uint32 if_read(uint32 pa, size_t size) { uint8 reg, data; - uint32 pc; UNIT *uptr; uptr = &(if_dev.units[0]); reg = (uint8)(pa - IFBASE); - pc = R[NUM_PC]; switch (reg) { case IF_STATUS_REG: diff --git a/3B2/3b2_io.c b/3B2/3b2_io.c index 808019c2..824d8629 100644 --- a/3B2/3b2_io.c +++ b/3B2/3b2_io.c @@ -56,6 +56,7 @@ void cio_clear(uint8 cid) cio[cid].id = 0; cio[cid].exp_handler = NULL; cio[cid].full_handler = NULL; + cio[cid].reset_handler = NULL; cio[cid].sysgen = NULL; cio[cid].rqp = 0; cio[cid].cqp = 0; @@ -101,7 +102,7 @@ void cio_sysgen(uint8 cid) sysgen_p = pread_w(SYSGEN_PTR); - sim_debug(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[%08x] [SYSGEN] Starting sysgen for card %d. sysgen_p=%08x\n", R[NUM_PC], cid, sysgen_p); @@ -115,22 +116,22 @@ void cio_sysgen(uint8 cid) cio[cid].ivec = pread_b(sysgen_p + 10); cio[cid].no_rque = pread_b(sysgen_p + 11); - sim_debug(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[SYSGEN] sysgen rqp = %08x\n", cio[cid].rqp); - sim_debug(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[SYSGEN] sysgen cqp = %08x\n", cio[cid].cqp); - sim_debug(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[SYSGEN] sysgen rqs = %02x\n", cio[cid].rqs); - sim_debug(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[SYSGEN] sysgen cqs = %02x\n", cio[cid].cqs); - sim_debug(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[SYSGEN] sysgen ivec = %02x\n", cio[cid].ivec); - sim_debug(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[SYSGEN] sysgen no_rque = %02x\n", cio[cid].no_rque); @@ -138,20 +139,19 @@ void cio_sysgen(uint8 cid) if (cio[cid].sysgen != NULL) { cio[cid].sysgen(cid); } else { - sim_debug(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[%08x] [cio_sysgen] Not running custom sysgen.\n", R[NUM_PC]); } } -void cio_cexpress(uint8 cid, uint16 esize, cio_entry *cqe, uint8 *app_data) +void cio_cexpress(uint8 cid, uint32 esize, cio_entry *cqe, uint8 *app_data) { - int32 i; - uint32 cqp; + uint32 i, cqp; cqp = cio[cid].cqp; - sim_debug(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[%08x] [cio_cexpress] cqp = %08x seqbit = %d\n", R[NUM_PC], cqp, cio[cid].seqbit); @@ -170,12 +170,11 @@ void cio_cexpress(uint8 cid, uint16 esize, cio_entry *cqe, uint8 *app_data) } } -void cio_cqueue(uint8 cid, uint8 cmd_stat, uint16 esize, +void cio_cqueue(uint8 cid, uint8 cmd_stat, uint32 esize, cio_entry *cqe, uint8 *app_data) { - int32 i; - uint32 cqp, top; - uint16 lp, ulp; + uint32 i, cqp, top; + uint16 lp; /* Apply the CMD/STAT bit */ cqe->subdevice |= (cmd_stat << 7); @@ -191,7 +190,6 @@ void cio_cqueue(uint8 cid, uint8 cmd_stat, uint16 esize, /* 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); - ulp = pread_h(cqp + esize + 2); /* Load the entry at the supplied address */ pwrite_h(top + lp, cqe->byte_count); @@ -209,22 +207,17 @@ void cio_cqueue(uint8 cid, uint8 cmd_stat, uint16 esize, * start of the queue */ if (cio[cid].cqs > 0) { lp = (lp + esize) % (esize * cio[cid].cqs); - /* Store it back to the correct location */ pwrite_h(cqp + esize, lp); - } else { - sim_debug(IO_DBG, &cpu_dev, - "[%08x] [cio_cqueue] ERROR! Completion Queue Size is 0!", - R[NUM_PC]); } } /* * Retrieve the Express Entry from the Request Queue */ -void cio_rexpress(uint8 cid, uint16 esize, cio_entry *rqe, uint8 *app_data) +void cio_rexpress(uint8 cid, uint32 esize, cio_entry *rqe, uint8 *app_data) { - int32 i; + uint32 i; uint32 rqp; rqp = cio[cid].rqp; @@ -247,12 +240,12 @@ void cio_rexpress(uint8 cid, uint16 esize, cio_entry *rqe, uint8 *app_data) * be serviced. * * Returns SCPE_OK on success, or SCPE_NXM if no entry was found. + * */ -t_stat cio_rqueue(uint8 cid, uint8 qnum, uint16 esize, +t_stat cio_rqueue(uint8 cid, uint32 qnum, uint32 esize, cio_entry *rqe, uint8 *app_data) { - int32 i; - uint32 rqp, top; + uint32 i, rqp, top; uint16 lp, ulp; /* Get the physical address of the request queue in main memory */ @@ -263,6 +256,8 @@ t_stat cio_rqueue(uint8 cid, uint8 qnum, uint16 esize, lp = pread_h(rqp); ulp = pread_h(rqp + 2); + /* Check to see if the request queue is empty. If it is, there's + * nothing to take. */ if (lp == ulp) { return SCPE_NXM; } @@ -294,7 +289,7 @@ t_stat cio_rqueue(uint8 cid, uint8 qnum, uint16 esize, /* * Return the Load Pointer for the given request queue */ -uint16 cio_r_lp(uint8 cid, uint8 qnum, uint16 esize) +uint16 cio_r_lp(uint8 cid, uint32 qnum, uint32 esize) { uint32 rqp; @@ -308,7 +303,7 @@ uint16 cio_r_lp(uint8 cid, uint8 qnum, uint16 esize) /* * Return the Unload Pointer for the given request queue */ -uint16 cio_r_ulp(uint8 cid, uint8 qnum, uint16 esize) +uint16 cio_r_ulp(uint8 cid, uint32 qnum, uint32 esize) { uint32 rqp; @@ -319,14 +314,14 @@ uint16 cio_r_ulp(uint8 cid, uint8 qnum, uint16 esize) return pread_h(rqp + 2); } -uint16 cio_c_lp(uint8 cid, uint16 esize) +uint16 cio_c_lp(uint8 cid, uint32 esize) { uint32 cqp; cqp = cio[cid].cqp + esize; return pread_h(cqp); } -uint16 cio_c_ulp(uint8 cid, uint16 esize) +uint16 cio_c_ulp(uint8 cid, uint32 esize) { uint32 cqp; cqp = cio[cid].cqp + esize; @@ -337,7 +332,7 @@ uint16 cio_c_ulp(uint8 cid, uint16 esize) * Returns true if there is room in the completion queue * for a new entry. */ -t_bool cio_cqueue_avail(uint8 cid, uint16 esize) +t_bool cio_cqueue_avail(uint8 cid, uint32 esize) { uint32 lp, ulp; @@ -347,6 +342,21 @@ t_bool cio_cqueue_avail(uint8 cid, uint16 esize) return(((lp + esize) % (cio[cid].cqs * esize)) != ulp); } +t_bool cio_rqueue_avail(uint8 cid, uint32 qnum, uint32 esize) +{ + uint32 rqp, lp, ulp; + + /* Get the physical address of the request queue in main memory */ + rqp = cio[cid].rqp + + esize + + (qnum * (LUSIZE + (esize * cio[cid].rqs))); + + lp = pread_h(rqp); + ulp = pread_h(rqp + 2); + + return(lp != ulp); +} + uint32 io_read(uint32 pa, size_t size) { struct iolink *p; @@ -403,7 +413,7 @@ uint32 io_read(uint32 pa, size_t size) 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[cid].sysgen_s |= CIO_INT0; - sim_debug(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[READ] [%08x] (%d INT0) ID\n", R[NUM_PC], cid); /* Return the correct byte of our board ID */ @@ -415,7 +425,7 @@ uint32 io_read(uint32 pa, size_t size) break; case CIO_INT1: /* We've seen an INT1 but not an INT0. Time to sysgen */ cio[cid].sysgen_s |= CIO_INT0; - sim_debug(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[READ] [%08x] (%d INT0) SYSGEN\n", R[NUM_PC], cid); cio_sysgen(cid); @@ -423,7 +433,7 @@ uint32 io_read(uint32 pa, size_t size) break; case CIO_SYSGEN: /* We've already sysgen'ed */ cio[cid].sysgen_s |= CIO_INT0; /* This must come BEFORE the exp_handler */ - sim_debug(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[READ] [%08x] (%d INT0) EXPRESS JOB\n", R[NUM_PC], cid); cio[cid].exp_handler(cid); @@ -432,7 +442,7 @@ uint32 io_read(uint32 pa, size_t size) default: /* This should never happen */ stop_reason = STOP_ERR; - sim_debug(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[READ] [%08x] (%d INT0) ERROR IN STATE MACHINE sysgen_s=%02x\n", R[NUM_PC], cid, cio[cid].sysgen_s); data = 0; @@ -445,20 +455,20 @@ uint32 io_read(uint32 pa, size_t size) 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(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[READ] [%08x] (%d INT1) IGNORED\n", R[NUM_PC], cid); cio[cid].sysgen_s |= CIO_INT1; break; case CIO_INT0: /* We've seen an INT0 but not an INT1. Time to sysgen */ - sim_debug(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[READ] [%08x] (%d INT1) SYSGEN\n", R[NUM_PC], cid); cio[cid].sysgen_s |= CIO_INT1; cio_sysgen(cid); break; case CIO_SYSGEN: /* We've already sysgen'ed */ - sim_debug(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[READ] [%08x] (%d INT1) FULL\n", R[NUM_PC], cid); cio[cid].sysgen_s |= CIO_INT1; /* This must come BEFORE the full handler */ @@ -467,7 +477,7 @@ uint32 io_read(uint32 pa, size_t size) default: /* This should never happen */ stop_reason = STOP_ERR; - sim_debug(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[READ] [%08x] (%d INT1) ERROR IN STATE MACHINE sysgen_s=%02x\n", R[NUM_PC], cid, cio[cid].sysgen_s); break; @@ -475,15 +485,18 @@ uint32 io_read(uint32 pa, size_t size) return 0; /* Data returned is arbitrary */ case IOF_STAT: - sim_debug(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[READ] [%08x] (%d RESET)\n", R[NUM_PC], cid); + if (cio[cid].reset_handler) { + cio[cid].reset_handler(cid); + } cio[cid].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(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[READ] [%08x] No card at cid=%d reg=%d\n", R[NUM_PC], cid, reg); csr_data |= CSRTIMO; @@ -520,7 +533,7 @@ void io_write(uint32 pa, uint32 val, size_t size) if (cio[cid].id == 0) { /* Nothing lives here */ - sim_debug(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[WRITE] [%08x] No card at cid=%d reg=%d\n", R[NUM_PC], cid, reg); csr_data |= CSRTIMO; @@ -540,20 +553,20 @@ void io_write(uint32 pa, uint32 val, size_t size) switch(cio[cid].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(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[WRITE] [%08x] (%d INT0) ID\n", R[NUM_PC], cid); cio[cid].sysgen_s |= CIO_INT0; break; case CIO_INT1: /* We've seen an INT1 but not an INT0. Time to sysgen */ - sim_debug(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[WRITE] [%08x] (%d INT0) SYSGEN\n", R[NUM_PC], cid); cio[cid].sysgen_s |= CIO_INT0; cio_sysgen(cid); break; case CIO_SYSGEN: /* We've already sysgen'ed */ - sim_debug(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[WRITE] [%08x] (%d INT0) EXPRESS JOB\n", R[NUM_PC], cid); cio[cid].sysgen_s |= CIO_INT0; @@ -562,7 +575,7 @@ void io_write(uint32 pa, uint32 val, size_t size) default: /* This should never happen */ stop_reason = STOP_ERR; - sim_debug(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[WRITE] [%08x] (%d INT0) ERROR IN STATE MACHINE sysgen_s=%02x\n", R[NUM_PC], cid, cio[cid].sysgen_s); break; @@ -574,20 +587,20 @@ void io_write(uint32 pa, uint32 val, size_t size) 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(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[WRITE] [%08x] (%d INT1) IGNORED\n", R[NUM_PC], cid); cio[cid].sysgen_s |= CIO_INT1; break; case CIO_INT0: /* We've seen an INT0 but not an INT1. Time to sysgen */ - sim_debug(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[WRITE] [%08x] (%d INT1) SYSGEN\n", R[NUM_PC], cid); cio[cid].sysgen_s |= CIO_INT1; cio_sysgen(cid); break; case CIO_SYSGEN: /* We've already sysgen'ed */ - sim_debug(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[WRITE] [%08x] (%d INT1) FULL\n", R[NUM_PC], cid); cio[cid].sysgen_s |= CIO_INT1; @@ -596,7 +609,7 @@ void io_write(uint32 pa, uint32 val, size_t size) default: /* This should never happen */ stop_reason = STOP_ERR; - sim_debug(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[WRITE] [%08x] (%d INT1) ERROR IN STATE MACHINE sysgen_s=%02x\n", R[NUM_PC], cid, cio[cid].sysgen_s); break; @@ -604,15 +617,18 @@ void io_write(uint32 pa, uint32 val, size_t size) return; case IOF_STAT: - sim_debug(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[WRITE] [%08x] (%d RESET)\n", R[NUM_PC], cid); + if (cio[cid].reset_handler) { + cio[cid].reset_handler(cid); + } cio[cid].sysgen_s = 0; return; default: /* We should never reach here, but if we do, there's * nothing listening. */ - sim_debug(IO_DBG, &cpu_dev, + sim_debug(CIO_DBG, &cpu_dev, "[WRITE] [%08x] No card at cid=%d reg=%d\n", R[NUM_PC], cid, reg); csr_data |= CSRTIMO; @@ -640,10 +656,18 @@ void io_write(uint32 pa, uint32 val, size_t size) /* For debugging only */ void dump_entry(uint32 dbits, DEVICE *dev, CONST char *type, - uint16 esize, cio_entry *entry, uint8 *app_data) + uint32 esize, cio_entry *entry, uint8 *app_data) { + char appl[64]; + uint32 i, c_offset; + + for (i = 0, c_offset=0; i < (esize - QESIZE); i++) { + snprintf(appl + c_offset, 3, "%02x", app_data[i]); + c_offset += 2; + } + sim_debug(dbits, dev, - "*** %s ENTRY: byte_count=%04x, subdevice=%02x, opcode=%d, address=%08x\n", + "*** %s ENTRY: byte_count=%04x, subdevice=%02x, opcode=%d, address=%08x, app_data=%s\n", type, entry->byte_count, entry->subdevice, - entry->opcode, entry->address); + entry->opcode, entry->address, appl); } diff --git a/3B2/3b2_io.h b/3B2/3b2_io.h index 6dd572ff..a6e73372 100644 --- a/3B2/3b2_io.h +++ b/3B2/3b2_io.h @@ -134,6 +134,11 @@ #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 CID(pa) (((((pa) >> 0x14) & 0x1f) / 2) - 1) /* Map a card ID to a base address */ @@ -154,21 +159,22 @@ 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 sysgen_s; /* Sysgen state */ - uint8 seqbit; /* Squence Bit */ - uint8 op; /* Last received opcode */ + 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) */ + void (*reset_handler)(uint8 cid); /* 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 */ + t_bool intr; /* Card needs to interrupt */ + uint8 sysgen_s; /* Sysgen state */ + uint8 seqbit; /* Squence Bit */ + uint8 op; /* Last received opcode */ } CIO_STATE; typedef struct { @@ -213,6 +219,7 @@ typedef struct { uint32 retcode; } pump; +extern t_bool cio_skip_seqbit; extern uint16 cio_ints; extern CIO_STATE cio[CIO_SLOTS]; @@ -221,18 +228,19 @@ t_stat cio_svc(UNIT *uptr); void cio_clear(uint8 cid); uint32 cio_crc32_shift(uint32 crc, uint8 data); -void cio_cexpress(uint8 cid, uint16 esize, cio_entry *cqe, uint8 *app_data); -void cio_cqueue(uint8 cid, uint8 cmd_stat, uint16 esize, cio_entry *cqe, uint8 *app_data); -void cio_rexpress(uint8 cid, uint16 esize, cio_entry *rqe, uint8 *app_data); -t_stat cio_rqueue(uint8 cid, uint8 qnum, uint16 esize, cio_entry *rqe, uint8 *app_data); -t_bool cio_cqueue_avail(uint8 cid, uint16 esize); -uint16 cio_r_lp(uint8 cid, uint8 qnum, uint16 esize); -uint16 cio_r_ulp(uint8 cid, uint8 qnum, uint16 esize); -uint16 cio_c_lp(uint8 cid, uint16 esize); -uint16 cio_c_ulp(uint8 cid, uint16 esize); +void cio_cexpress(uint8 cid, uint32 esize, cio_entry *cqe, uint8 *app_data); +void cio_cqueue(uint8 cid, uint8 cmd_stat, uint32 esize, cio_entry *cqe, uint8 *app_data); +t_bool cio_cqueue_avail(uint8 cid, uint32 esize); +void cio_rexpress(uint8 cid, uint32 esize, cio_entry *rqe, uint8 *app_data); +t_stat cio_rqueue(uint8 cid, uint32 qnum, uint32 esize, cio_entry *rqe, uint8 *app_data); +t_bool cio_rqueue_avail(uint8 cid, uint32 qnum, uint32 esize); +uint16 cio_r_lp(uint8 cid, uint32 qnum, uint32 esize); +uint16 cio_r_ulp(uint8 cid, uint32 qnum, uint32 esize); +uint16 cio_c_lp(uint8 cid, uint32 esize); +uint16 cio_c_ulp(uint8 cid, uint32 esize); void cio_sysgen(uint8 cid); void dump_entry(uint32 dbits, DEVICE *dev, CONST char *type, - uint16 esize, cio_entry *entry, uint8 *app_data); + uint32 esize, cio_entry *entry, uint8 *app_data); #endif diff --git a/3B2/3b2_mmu.h b/3B2/3b2_mmu.h index 0bc3c713..f7f57e83 100644 --- a/3B2/3b2_mmu.h +++ b/3B2/3b2_mmu.h @@ -332,6 +332,9 @@ void pwrite_b(uint32 pa, uint8 val); void pwrite_h(uint32 pa, uint16 val); void pwrite_w(uint32 pa, uint32 val); +/* TODO: REMOVE AFTER DEBUGGING */ +uint32 safe_read_w(uint32 va); + /* Virtual memory translation */ uint32 mmu_xlate_addr(uint32 va, uint8 r_acc); t_stat mmu_decode_vaddr(uint32 vaddr, uint8 r_acc, diff --git a/3B2/3b2_ni.c b/3B2/3b2_ni.c new file mode 100644 index 00000000..cffc59c0 --- /dev/null +++ b/3B2/3b2_ni.c @@ -0,0 +1,1270 @@ +/* 3b2_ni.c: AT&T 3B2 Model 400 "NI" feature card + + Copyright (c) 2018, 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 + +extern CIO_STATE cio[CIO_SLOTS]; + +/* State container for the card */ +NI_STATE ni; + +/* 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 cid, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp); +static t_stat ni_show_queue_common(FILE *st, UNIT *uptr, int32 val, + CONST void *desc, t_bool rq); +static t_stat ni_show_rqueue(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +static t_stat ni_show_cqueue(FILE *st, UNIT *uptr, int32 val, CONST void *desc); + +/* + * 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_DIS, 0) }, + { UDATA(&ni_rq_svc, 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, 0, "RQUEUE=n", NULL, + NULL, &ni_show_rqueue, NULL, "Display Request Queue for card n" }, + { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "CQUEUE=n", NULL, + NULL, &ni_show_cqueue, NULL, "Display Completion Queue for card n" }, + { 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 */ +}; + +#define CHAR(c) ((((c) >= 0x20) && ((c) < 0x7f)) ? (c) : '.') + +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"); + + ni.callback = ni_recv_callback; + + /* 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; + + /* Schedule the queue poller in fast poll mode */ + sim_activate_abs(rq_unit, NI_QPOLL_FAST); + + /* Schedule 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"); + ethq_clear(&ni.readq); + ni.enabled = FALSE; + cio[ni.cid].intr = FALSE; + sim_cancel(ni_unit); + sim_cancel(rcv_unit); + sim_cancel(rq_unit); + sim_cancel(cio_unit); + sim_cancel(sanity_unit); + ni.callback = NULL; +} + +static void ni_cmd(uint8 cid, 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; + 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[cid].op = rentry->opcode; + + delay = NI_INT_DELAY_US; + + 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)); + } + + 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(cid, NIQESIZE, ¢ry, app_data); + } else { + cio_cqueue(cid, 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 */ + if (ni.crc == NI_DIAG_CRC1 || + ni.crc == NI_DIAG_CRC2 || + ni.crc == NI_DIAG_CRC3) { + pwrite_h(0x200f000, 0x1); /* Test success */ + pwrite_h(0x200f002, 0x0); /* Test Number */ + pwrite_h(0x200f004, 0x0); /* Actual */ + pwrite_h(0x200f006, 0x0); /* Expected */ + pwrite_b(0x200f008, 0x1); /* Success flag again */ + } + + /* 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[cid].sysgen_s = 0; + + if (cio[cid].ivec == 0 || cio[cid].ivec == 3) { + cio_cexpress(cid, NIQESIZE, ¢ry, app_data); + } else { + cio_cqueue(cid, 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); + + if (is_exp) { + cio_cexpress(cid, NIQESIZE, ¢ry, app_data); + } else { + cio_cqueue(cid, 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); + } + + 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(cid, 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(cid, CIO_STAT, NIQESIZE, ¢ry, app_data); + + break; + case NI_TURNON: + sim_debug(DBG_TRACE, &ni_dev, + "[ni_cmd] NI TURNON Operation\n"); + + if (sim_idle_enab) { + sim_clock_coschedule(rcv_unit, tmxr_poll); + } else { + sim_activate_after(rcv_unit, NI_RCV_POLL_US); + } + + ni_enable(); + + cio_cqueue(cid, CIO_STAT, NIQESIZE, ¢ry, app_data); + + break; + case NI_STATS: + sim_debug(DBG_TRACE, &ni_dev, + "[ni_cmd] NI STATS Operation\n"); + + cio_cqueue(cid, 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); + + /* 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); + + /* 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); + } + + /* 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); + ni.prot.size = pread_h(rentry->address + prot_info_offset + 4); + ni.prot.last = pread_h(rentry->address + prot_info_offset + 6); + 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); + } + } 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; + } + + /* 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; + + /* TODO: Why is this always 4 for a send? */ + centry.subdevice = 4; + + centry.byte_count = rentry->byte_count; + + /* 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; + } + + delay = 0; + + cio_cqueue(cid, CIO_STAT, NIQESIZE, ¢ry, app_data); + + break; + default: + sim_debug(DBG_TRACE, &ni_dev, + "[ni_cmd] Opcode %d Not Handled Yet\n", + rentry->opcode); + + cio_cqueue(cid, CIO_STAT, NIQESIZE, ¢ry, app_data); + + break; + } + + sim_activate_after(cio_unit, delay); +} + +t_stat ni_setmac(UNIT *uptr, int32 val, CONST char* cptr, void* desc) +{ + t_stat status; + + 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]; + + 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; + + 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 cid) +{ + cio_entry cqe; + uint8 app_data[4]; + + 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[cid].rqp, cio[cid].cqp, cio[cid].no_rque, cio[cid].rqs, cio[cid].cqs); + + /* If the card has been successfully pumped, then we respond with + * a full completion queue entry. Otherwise, an express entry is + * used. */ + if (ni.crc == NI_PUMP_CRC1 || + ni.crc == NI_PUMP_CRC2) { + cio_cqueue(cid, CIO_STAT, NIQESIZE, &cqe, app_data); + } else { + cio_cexpress(cid, 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_after(cio_unit, NI_INT_DELAY_US); +} + +/* + * Handler for CIO INT0 (express job) requests. + */ +void ni_express(uint8 cid) +{ + cio_entry rqe; + uint8 app_data[4]; + + sim_debug(DBG_TRACE, &ni_dev, + "[ni_express] Handling express CIO request.\n"); + + cio_rexpress(cid, NIQESIZE, &rqe, app_data); + ni_cmd(cid, &rqe, app_data, TRUE); +} + +/* + * Handler for CIO INT1 (full job) requests. + */ +void ni_full(uint8 cid) +{ + cio_entry rqe; + uint8 app_data[4]; + + sim_debug(DBG_TRACE, &ni_dev, + "[ni_full] INT1 received. Handling full CIO request.\n"); + + while (cio_cqueue_avail(cid, NIQESIZE) && + cio_rqueue(cid, GE_QUEUE, NIQESIZE, &rqe, app_data) == SCPE_OK) { + ni_cmd(cid, &rqe, app_data, FALSE); + } +} + +/* + * Handler for CIO RESET requests. + */ +void ni_cio_reset(uint8 cid) +{ + ni_disable(); +} + +t_stat ni_autoconfig() +{ + uint8 cid; + + /* Clear the CIO table of PORTS cards */ + for (cid = 0; cid < CIO_SLOTS; cid++) { + if (cio[cid].id == NI_ID) { + cio[cid].id = 0; + cio[cid].ipl = 0; + cio[cid].ivec = 0; + cio[cid].exp_handler = NULL; + cio[cid].full_handler = NULL; + cio[cid].reset_handler = NULL; + cio[cid].sysgen = NULL; + } + } + + /* Find the first avaialable slot */ + for (cid = 0; cid < CIO_SLOTS; cid++) { + if (cio[cid].id == 0) { + break; + } + } + + /* Do we have room? */ + if (cid >= CIO_SLOTS) { + return SCPE_NXM; + } + + /* Remember the card slot */ + ni.cid = cid; + + /* Set up the ni structure */ + cio[cid].id = NI_ID; + cio[cid].ipl = NI_IPL; + cio[cid].exp_handler = &ni_express; + cio[cid].full_handler = &ni_full; + cio[cid].reset_handler = &ni_cio_reset; + cio[cid].sysgen = &ni_sysgen; + + return SCPE_OK; +} + +t_stat ni_reset(DEVICE *dptr) +{ + t_stat status; + char uname[16]; + + sim_debug(DBG_TRACE, &ni_dev, + "[ni_reset] Resetting NI device\n"); + + /* Initial setup that should only ever be done once. */ + if (!ni.initialized) { + memset(&ni, 0, sizeof(NI_STATE)); + + 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); + + /* 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); + + /* Initialize the receive queue one time only */ + status = ethq_init(&ni.readq, NI_QUE_MAX); + if (status != SCPE_OK) { + return status; + } + + ni.initialized = TRUE; + } + + /* 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(); + + /* We make no attempt to autoconfig until the device + * is attached. */ + + return SCPE_OK; +} + +t_stat ni_rcv_svc(UNIT *uptr) +{ + t_stat status; + + /* If a CIO interrupt is alrady pending, skip this read */ + if (!cio[ni.cid].intr) { + /* Try to receive a packet */ + do { + status = eth_read(ni.eth, &ni.rd_buf, ni.callback); + } while (status); + + /* Attempt to process a packet from the queue */ + ni_process_packet(); + + /* Re-schedule the next poll */ + if (sim_idle_enab) { + sim_clock_coschedule(rcv_unit, tmxr_poll); + } else { + sim_activate_after(rcv_unit, NI_RCV_POLL_US); + } + } + + return SCPE_OK; +} + +/* + * Service used by the card to poll for available request queue + * entries. + */ + +t_stat ni_rq_svc(UNIT *uptr) +{ + int i, wp, no_rque; + cio_entry rqe; + uint8 slot[4]; + + no_rque = cio[ni.cid].no_rque - 1; + + for (i = 0; i < no_rque; i++) { + while (NI_CACHE_HAS_SPACE(i) && cio_rqueue(ni.cid, 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.cid, i+1, NIQESIZE), + cio_r_ulp(ni.cid, 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++; + } + } + + /* 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 (ni.poll_rate == NI_QPOLL_FAST) { + sim_activate_abs(rq_unit, NI_QPOLL_FAST); + } else { + if (sim_idle_enab) { + sim_clock_coschedule(rq_unit, tmxr_poll); + } else { + sim_activate_abs(rq_unit, NI_QPOLL_SLOW); + } + } + + 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; + uint8 app_data[4]; + + sim_debug(DBG_TRACE, &ni_dev, + "[ni_sanity_svc] Firing sanity timer.\n"); + + cqe.opcode = NI_SANITY; + cio_cqueue(ni.cid, CIO_STAT, NIQESIZE, &cqe, app_data); + + if (cio[ni.cid].ivec > 0) { + cio[ni.cid].intr = TRUE; + } + + sim_activate_after(sanity_unit, NI_SANITY_INTERVAL_US); + + return SCPE_OK; +} + +t_stat ni_cio_svc(UNIT *uptr) +{ + if (cio[ni.cid].ivec > 0) { + sim_debug(DBG_TRACE, &ni_dev, + "[ni_cio_svc] Handling a CIO service (Setting Interrupt) for board %d\n", ni.cid); + cio[ni.cid].intr = TRUE; + } + + return SCPE_OK; +} + +void ni_process_packet() +{ + int i, rp; + uint32 addr; + uint8 slot; + ETH_ITEM *item; + cio_entry centry; + uint8 capp_data[4]; + int len = 0; + int que_num = 0; + uint8 *rbuf; + + /* If a CIO interrupt is alrady pending, the card is disabled, or + if there's nothing to do, abort */ + if (cio[ni.cid].intr || !ni.enabled || ni.readq.count == 0) { + return; + } + + item = &ni.readq.item[ni.readq.head]; + + len = item->packet.len; + rbuf = item->packet.msg; + que_num = len > SM_PKT_MAX ? 1 : 0; + + sim_debug(DBG_IO, &ni_dev, + "[ni_process_packet] Receiving a packet of size %d (0x%x)\n", + len, len); + + /* + * Consult the cache for a job. + */ + if (ni.job_cache[que_num].rp == ni.job_cache[que_num].wp) { + sim_debug(DBG_ERR, &ni_dev, + "Job cache is empty.\n"); + return; + } + + /* + * Now that we know we have a job available, take it. + */ + 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.cid, que_num+1, NIQESIZE), + cio_r_ulp(ni.cid, que_num+1, NIQESIZE), + slot, addr); + + /* Store the packet into main memory */ + for (i = 0; i < len; i++) { + pwrite_b(addr + i, rbuf[i]); + } + + if (ni_dev.dctrl & DBG_DAT) { + dump_packet("RCV", &item->packet); + } + 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.cid, CIO_STAT, NIQESIZE, ¢ry, capp_data); + + /* Take the packet just processed off the queue */ + ethq_remove(&ni.readq); + + /* And interrupt */ + if (cio[ni.cid].ivec > 0) { + cio[ni.cid].intr = TRUE; + } +} + +/* + * Called when the eth_read is complete. + * + * We can receive packets faster than we can process them, so we just put them + * in a queue and handle them in ni_rcv_svc. + */ +void ni_recv_callback(int status) { + if (ni.enabled) { + sim_debug(DBG_IO, &ni_dev, + "[ni_recv_callback] inserting packet of len=%d (used=%d) into read queue.\n", + ni.rd_buf.len, ni.rd_buf.used); + ethq_insert(&ni.readq, ETH_ITM_NORMAL, &ni.rd_buf, SCPE_OK); + } else { + sim_debug(DBG_IO, &ni_dev, "[ni_recv_callback] Disabled. Ignoring packet.\n"); + } +} + +t_stat ni_attach(UNIT *uptr, CONST char *cptr) +{ + t_stat status; + char *tptr; + + sim_debug(DBG_TRACE, &ni_dev, "ni_attach()\n"); + + /* Run autoconfig on the device */ + status = ni_autoconfig(); + if (status != SCPE_OK) { + return status; + } + + 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); + 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); + return status; + } + + uptr->filename = tptr; + uptr->flags |= UNIT_ATT; + + status = ethq_init(&ni.readq, NI_QUE_MAX); + if (status != SCPE_OK) { + sim_debug(DBG_ERR, &ni_dev, "ni_attach failure: ethq init\n"); + eth_close(ni.eth); + free(tptr); + free(ni.eth); + uptr->flags &= ~UNIT_ATT; + return status; + } + + eth_filter(ni.eth, ni.filter_count, ni.macs, 0, 0); + + ni_reset(&ni_dev); + + return SCPE_OK; +} + +t_stat ni_detach(UNIT *uptr) +{ + sim_debug(DBG_TRACE, &ni_dev, "ni_detach()\n"); + + if (uptr->flags & UNIT_ATT) { + 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; + + 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"; + + 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, "Dropped:", ni.stats.rx_dropped + ni.readq.loss); + 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) +{ + 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) +{ + return "NI 10BASE5 Ethernet controller"; +} + +/* + * Useful routines for debugging request and completion queues + */ + +static t_stat ni_show_rqueue(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + return ni_show_queue_common(st, uptr, val, desc, TRUE); +} + +static t_stat ni_show_cqueue(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + return ni_show_queue_common(st, uptr, val, desc, FALSE); +} + +static t_stat ni_show_queue_common(FILE *st, UNIT *uptr, int32 val, + CONST void *desc, t_bool rq) +{ + uint8 cid; + char *cptr = (char *) desc; + t_stat result; + uint32 ptr, size, no_rque, i, j; + uint16 lp, ulp; + uint8 op, dev, seq, cmdstat; + + if (cptr) { + cid = (uint8) get_uint(cptr, 10, 12, &result); + if (result != SCPE_OK) { + return SCPE_ARG; + } + } else { + return SCPE_ARG; + } + + /* If the card is not sysgen'ed, give up */ + if (cio[cid].sysgen_s != CIO_SYSGEN) { + fprintf(st, "No card in slot %d, or card has not completed sysgen\n", cid); + return SCPE_ARG; + } + + fprintf(st, "---------------------------------------------------------\n"); + fprintf(st, "Sysgen Block:\n"); + fprintf(st, "---------------------------------------------------------\n"); + fprintf(st, " Request Queue Pointer: 0x%08x\n", cio[cid].rqp); + fprintf(st, " Completion Queue Pointer: 0x%08x\n", cio[cid].cqp); + fprintf(st, " Request Queue Size: 0x%02x\n", cio[cid].rqs); + fprintf(st, " Completion Queue Size: 0x%02x\n", cio[cid].cqs); + fprintf(st, " Interrupt Vector: %d (0x%02x)\n", cio[cid].ivec, cio[cid].ivec); + fprintf(st, " Number of Request Queues: %d\n", cio[cid].no_rque); + fprintf(st, "---------------------------------------------------------\n"); + + /* Get the top of the queue */ + if (rq) { + ptr = cio[cid].rqp; + size = cio[cid].rqs; + no_rque = cio[cid].no_rque; + } else { + ptr = cio[cid].cqp; + size = cio[cid].cqs; + no_rque = 0; /* Not used */ + } + + if (rq) { + fprintf(st, "Dumping %d Request Queues\n", no_rque); + } else { + fprintf(st, "Dumping Completion Queue\n"); + } + + fprintf(st, "---------------------------------------------------------\n"); + fprintf(st, "EXPRESS ENTRY:\n"); + fprintf(st, " Byte Count: %d\n", pread_h(ptr)); + fprintf(st, " Subdevice: %d\n", pread_b(ptr + 2)); + fprintf(st, " Opcode: 0x%02x\n", pread_b(ptr + 3)); + fprintf(st, " Addr/Data: 0x%08x\n", pread_w(ptr + 4)); + fprintf(st, " App Data: 0x%08x\n", pread_w(ptr + 8)); + ptr += 12; + + if (rq) { + for (i = 0; i < no_rque; i++) { + lp = pread_h(ptr); + ulp = pread_h(ptr + 2); + ptr += 4; + fprintf(st, "---------------------------------------------------------\n"); + fprintf(st, "REQUEST QUEUE %d\n", i); + fprintf(st, "---------------------------------------------------------\n"); + fprintf(st, "Load Pointer: 0x%04x (%d)\n", lp, (lp / NIQESIZE) + 1); + fprintf(st, "Unload Pointer: 0x%04x (%d)\n", ulp, (ulp / NIQESIZE) + 1); + fprintf(st, "---------------------------------------------------------\n"); + for (j = 0; j < size; j++) { + dev = pread_b(ptr + 2); + op = pread_b(ptr + 3); + seq = (dev & 0x40) >> 6; + cmdstat = (dev & 0x80) >> 7; + fprintf(st, "REQUEST ENTRY %d (@ 0x%08x)\n", j + 1, ptr); + fprintf(st, " Byte Count: 0x%04x\n", pread_h(ptr)); + fprintf(st, " Subdevice: %d\n", dev & 0x3f); + fprintf(st, " Cmd/Stat: %d\n", cmdstat); + fprintf(st, " Seqbit: %d\n", seq); + fprintf(st, " Opcode: 0x%02x (%d)\n", op, op); + fprintf(st, " Addr/Data: 0x%08x\n", pread_w(ptr + 4)); + fprintf(st, " App Data: 0x%08x\n", pread_w(ptr + 8)); + ptr += 12; + } + } + } else { + lp = pread_h(ptr); + ulp = pread_h(ptr + 2); + ptr += 4; + fprintf(st, "---------------------------------------------------------\n"); + fprintf(st, "Load Pointer: 0x%04x (%d)\n", lp, (lp / NIQESIZE) + 1); + fprintf(st, "Unload Pointer: 0x%04x (%d)\n", ulp, (ulp / NIQESIZE) + 1); + fprintf(st, "---------------------------------------------------------\n"); + for (i = 0; i < size; i++) { + dev = pread_b(ptr + 2); + op = pread_b(ptr + 3); + seq = (dev & 0x40) >> 6; + cmdstat = (dev & 0x80) >> 7; + fprintf(st, "COMPLETION ENTRY %d (@ 0x%08x)\n", i + 1, ptr); + fprintf(st, " Byte Count: 0x%04x\n", pread_h(ptr)); + fprintf(st, " Subdevice: %d\n", dev & 0x3f); + fprintf(st, " Cmd/Stat: %d\n", cmdstat); + fprintf(st, " Seqbit: %d\n", seq); + fprintf(st, " Opcode: 0x%02x (%d)\n", op, op); + fprintf(st, " Addr/Data: 0x%08x\n", pread_w(ptr + 4)); + fprintf(st, " App Data: 0x%08x\n", pread_w(ptr + 8)); + ptr += 12; + } + } + + return SCPE_OK; +} diff --git a/3B2/3b2_ni.h b/3B2/3b2_ni.h new file mode 100644 index 00000000..aaa03779 --- /dev/null +++ b/3B2/3b2_ni.h @@ -0,0 +1,219 @@ +/* 3b2_ni.h: AT&T 3B2 Model 400 "NI" feature card + + Copyright (c) 2018, 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 "3b2_io.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_TMR_WAIT_DEFAULT 3000 +#define NI_RCV_POLL_US 8000 +#define NI_XMIT_DELAY_US 60000 +#define NI_INT_DELAY_US 500 +#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 NI_DIAG_CRC1 0x795268a4 +#define NI_DIAG_CRC2 0xfab1057c +#define NI_DIAG_CRC3 0x10ca00cd +#define NI_PUMP_CRC1 0xfab1057c +#define NI_PUMP_CRC2 0xf6744bed + +#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 queue */ +#define SM_QUEUE 1 /* Small packet receive queue */ +#define LG_QUEUE 2 /* Large packet receive queue */ +#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 NI_CACHE_HAS_SPACE(i) (((ni.job_cache[(i)].wp + 1) % NI_CACHE_LEN) != ni.job_cache[(i)].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 cid; + t_bool initialized; + 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_QUE readq; + ETH_MAC macs[NI_FILTER_MAX]; /* List of all filter addresses */ + int filter_count; /* Number of filters available */ + ETH_PCALLBACK callback; +} NI_STATE; + +extern DEVICE ni_dev; + +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 cid); +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 cid); +void ni_process_packet(); +void ni_int_ack(uint8 cid); +void ni_sysgen(uint8 cid); +void ni_express(uint8 cid); +void ni_full(uint8 cid); + +#endif /* _3B2_NI_H_ */ diff --git a/3B2/3b2_ports.c b/3B2/3b2_ports.c index 651793d5..42e0dd46 100644 --- a/3B2/3b2_ports.c +++ b/3B2/3b2_ports.c @@ -93,14 +93,14 @@ extern UNIT cio_unit; #define PORTS_DIAG_CRC2 0x77a1ea56 #define PORTS_DIAG_CRC3 0x84cf938b -#define LN(cid,port) ((PORTS_LINES * ((cid) - base_cid)) + (port)) -#define LCID(ln) (((ln) / PORTS_LINES) + base_cid) +#define LN(cid,port) ((PORTS_LINES * ((cid) - ports_base_cid)) + (port)) +#define LCID(ln) (((ln) / PORTS_LINES) + ports_base_cid) #define LPORT(ln) ((ln) % PORTS_LINES) +int8 ports_base_cid; /* First cid in our contiguous block */ +uint8 ports_int_cid; /* Interrupting card ID */ +uint8 ports_int_subdev; /* Interrupting subdevice */ t_bool ports_conf = FALSE; /* Have PORTS cards been configured? */ -int8 base_cid; /* First cid in our contiguous block */ -uint8 int_cid; /* Interrupting card ID */ -uint8 int_subdev; /* Interrupting subdevice */ uint32 ports_crc; /* CRC32 of downloaded memory */ /* PORTS-specific state for each slot */ @@ -184,8 +184,8 @@ DEVICE ports_dev = { static void cio_irq(uint8 cid, uint8 dev, int32 delay) { - int_cid = cid; - int_subdev = dev & 0xf; + ports_int_cid = cid; + ports_int_subdev = dev & 0xf; sim_activate(&ports_unit[2], delay); } @@ -268,13 +268,15 @@ static void ports_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data) for (i = 0; i < rentry->byte_count; i++) { ports_crc = cio_crc32_shift(ports_crc, pread_b(rentry->address + i)); } + centry.address = rentry->address + rentry->byte_count; sim_debug(TRACE_DBG, &ports_dev, "[%08x] [ports_cmd] CIO Download Memory: bytecnt=%04x " "addr=%08x return_addr=%08x subdev=%02x (CRC=%08x)\n", R[NUM_PC], rentry->byte_count, rentry->address, centry.address, centry.subdevice, ports_crc); - centry.address = rentry->address + rentry->byte_count; + /* We intentionally do not set the subdevice in + * the completion entry */ cio_cexpress(cid, PPQESIZE, ¢ry, app_data); cio_irq(cid, rentry->subdevice, DELAY_DLM); break; @@ -523,7 +525,7 @@ void ports_sysgen(uint8 cid) cio_cexpress(cid, PPQESIZE, &cqe, app_data); cio_cqueue(cid, CIO_STAT, PPQESIZE, &cqe, app_data); - int_cid = cid; + ports_int_cid = cid; sim_activate(&ports_unit[2], DELAY_STD); } @@ -599,7 +601,7 @@ t_stat ports_reset(DEVICE *dptr) } /* Remember the base card slot */ - base_cid = cid; + ports_base_cid = cid; end_slot = (cid + (ports_desc.lines/PORTS_LINES)); @@ -669,20 +671,20 @@ t_stat ports_cio_svc(UNIT *uptr) { sim_debug(TRACE_DBG, &ports_dev, "[ports_cio_svc] IRQ for board %d device %d\n", - int_cid, int_subdev); + ports_int_cid, ports_int_subdev); - if (cio[int_cid].ivec > 0) { - cio[int_cid].intr = TRUE; + if (cio[ports_int_cid].ivec > 0) { + cio[ports_int_cid].intr = TRUE; } - switch (cio[int_cid].op) { + switch (cio[ports_int_cid].op) { case PPC_CONN: - cio[int_cid].op = PPC_ASYNC; - ports_ldsc[LN(int_cid, int_subdev)].rcve = 1; + cio[ports_int_cid].op = PPC_ASYNC; + ports_ldsc[LN(ports_int_cid, ports_int_subdev)].rcve = 1; sim_activate(&ports_unit[2], DELAY_ASYNC); break; case PPC_ASYNC: - ports_update_conn(LN(int_cid, int_subdev)); + ports_update_conn(LN(ports_int_cid, ports_int_subdev)); break; default: break; diff --git a/3B2/3b2_sys.c b/3B2/3b2_sys.c index c0f77b40..5ec44e0d 100644 --- a/3B2/3b2_sys.c +++ b/3B2/3b2_sys.c @@ -37,6 +37,7 @@ #include "3b2_mmu.h" #include "3b2_ctc.h" #include "3b2_ports.h" +#include "3b2_ni.h" #include "3b2_sysdev.h" char sim_name[] = "AT&T 3B2 Model 400"; @@ -65,6 +66,7 @@ DEVICE *sim_devices[] = { &id_dev, &ports_dev, &ctc_dev, + &ni_dev, NULL }; @@ -93,6 +95,7 @@ void full_reset() csr_reset(&csr_dev); ports_reset(&ports_dev); ctc_reset(&ctc_dev); + ni_reset(&ni_dev); } t_stat sim_load(FILE *fileref, CONST char *cptr, CONST char *fnam, int flag) diff --git a/Visual Studio Projects/3B2.vcproj b/Visual Studio Projects/3B2.vcproj index 1edf1b03..689aa8ee 100644 --- a/Visual Studio Projects/3B2.vcproj +++ b/Visual Studio Projects/3B2.vcproj @@ -40,8 +40,8 @@ + + @@ -236,6 +240,29 @@ RelativePath="..\3B2\3b2_sysdev.c" > + + + + + + + + @@ -280,6 +307,170 @@ RelativePath="..\sim_video.c" > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/makefile b/makefile index 974b9dec..f7a64e05 100644 --- a/makefile +++ b/makefile @@ -108,7 +108,7 @@ ifneq (,$(findstring besm6,$(MAKECMDGOALS))) BESM6_BUILD = true endif # building the pdp11, pdp10, or any vax simulator could use networking support -ifneq (,$(or $(findstring pdp11,$(MAKECMDGOALS)),$(findstring pdp10,$(MAKECMDGOALS)),$(findstring vax,$(MAKECMDGOALS)),$(findstring all,$(MAKECMDGOALS)))) +ifneq (,$(or $(findstring pdp11,$(MAKECMDGOALS)),$(findstring pdp10,$(MAKECMDGOALS)),$(findstring vax,$(MAKECMDGOALS)),$(findstring 3b2,$(MAKECMDGOALS))$(findstring all,$(MAKECMDGOALS)))) NETWORK_USEFUL = true ifneq (,$(findstring all,$(MAKECMDGOALS))) BUILD_MULTIPLE = s @@ -1842,8 +1842,8 @@ ATT3B2 = ${ATT3B2D}/3b2_cpu.c ${ATT3B2D}/3b2_mmu.c \ ${ATT3B2D}/3b2_id.c ${ATT3B2D}/3b2_dmac.c \ ${ATT3B2D}/3b2_sys.c ${ATT3B2D}/3b2_io.c \ ${ATT3B2D}/3b2_ports.c ${ATT3B2D}/3b2_ctc.c \ - ${ATT3B2D}/3b2_sysdev.c -ATT3B2_OPT = -I ${ATT3B2D} -DUSE_INT64 -DUSE_ADDR64 + ${ATT3B2D}/3b2_ni.c ${ATT3B2D}/3b2_sysdev.c +ATT3B2_OPT = -DUSE_INT64 -DUSE_ADDR64 -I ${ATT3B2D} ${NETWORK_OPT} # # Build everything (not the unsupported/incomplete or experimental simulators) #