H316: Resurrecting the ARPAnet IMP (from Bob Armstrong)
This summer a group of us worked together to resurrect the original ARPAnet IMP software, and I’m now happy to say that the IMP lives again in simulation. It’s possible to run the original IMP software on a modified version of the H316 simh and to set up a virtual network of simulated IMPs talking to each other. IMP to IMP connections, which would have originally been carried over leased telephone lines, are tunneled over IP. As far as we can tell, everything works pretty much as it did in the early 1970s. IMPs are able to exchange routing information, console to console communications, network statistics, and they would carry host traffic if there were hosts on the network. The hooks are in there to allow simh to support the IMP side of the 1822 host interface, and the next step would be to recover the OS for an ARPAnet era host and then extend the corresponding simulator to talk to the IMP simulation.
This commit is contained in:
parent
00afa58bc4
commit
65402fbaa1
29 changed files with 11413 additions and 117 deletions
314
H316/h316_cpu.c
314
H316/h316_cpu.c
|
@ -25,6 +25,12 @@
|
||||||
|
|
||||||
cpu H316/H516 CPU
|
cpu H316/H516 CPU
|
||||||
|
|
||||||
|
21-May-13 RLA Add IMP/TIP support
|
||||||
|
Move SMK/OTK instructions here (from CLK)
|
||||||
|
Make SET CPU DMA work as documented
|
||||||
|
Implement extended interrupts
|
||||||
|
Add "interrupt taken" flag to CPU HISTORY
|
||||||
|
Add "break on write" breakpoints
|
||||||
19-Nov-11 RMS Fixed XR behavior (Adrian Wise)
|
19-Nov-11 RMS Fixed XR behavior (Adrian Wise)
|
||||||
19-Nov-11 RMS Fixed bugs in double precision, normalization, SC (Adrian Wise)
|
19-Nov-11 RMS Fixed bugs in double precision, normalization, SC (Adrian Wise)
|
||||||
10-Jan-10 RMS Fixed bugs in LDX, STX introduced in 3.8-1 (Theo Engel)
|
10-Jan-10 RMS Fixed bugs in LDX, STX introduced in 3.8-1 (Theo Engel)
|
||||||
|
@ -176,12 +182,19 @@
|
||||||
unknown I/O device and stop_dev flag set
|
unknown I/O device and stop_dev flag set
|
||||||
I/O error in I/O simulator
|
I/O error in I/O simulator
|
||||||
|
|
||||||
2. Interrupts. Interrupts are maintained by two parallel variables:
|
2. Interrupts. Interrupts are maintained by parallel variables:
|
||||||
|
|
||||||
dev_int device interrupt flags
|
dev_int[2] device interrupt flags
|
||||||
dev_enb device interrupt enable flags
|
dev_enb[2] device interrupt enable flags
|
||||||
|
|
||||||
In addition, dev_int contains the interrupt enable and interrupt no
|
Note that these are actually arrays of two 16 bit words each. The first
|
||||||
|
word of each vector contains the bits for the standard interrupt devices,
|
||||||
|
and the second word is the bits for the extended interrupts 1..17. The
|
||||||
|
IMP uses these extended interrupts, however this was a standard H316 option
|
||||||
|
and is in no way IMP specific. Actually the H316 supported up to 48 extra
|
||||||
|
interrupts, but it seems like overkill to implement them all.
|
||||||
|
|
||||||
|
In addition, dev_int[0] contains the interrupt enable and interrupt no
|
||||||
defer flags. If interrupt enable and interrupt no defer are set, and
|
defer flags. If interrupt enable and interrupt no defer are set, and
|
||||||
at least one interrupt request is pending, then an interrupt occurs.
|
at least one interrupt request is pending, then an interrupt occurs.
|
||||||
The order of flags in these variables corresponds to the order
|
The order of flags in these variables corresponds to the order
|
||||||
|
@ -212,6 +225,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "h316_defs.h"
|
#include "h316_defs.h"
|
||||||
|
#ifdef VM_IMPTIP
|
||||||
|
#include "h316_imp.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#define PCQ_SIZE 64 /* must be 2**n */
|
#define PCQ_SIZE 64 /* must be 2**n */
|
||||||
#define PCQ_MASK (PCQ_SIZE - 1)
|
#define PCQ_MASK (PCQ_SIZE - 1)
|
||||||
|
@ -242,6 +258,7 @@ typedef struct {
|
||||||
int32 xr;
|
int32 xr;
|
||||||
int32 ea;
|
int32 ea;
|
||||||
int32 opnd;
|
int32 opnd;
|
||||||
|
t_bool iack; // [RLA] TRUE if an interrupt occurred
|
||||||
} InstHistory;
|
} InstHistory;
|
||||||
|
|
||||||
uint16 M[MAXMEMSIZE] = { 0 }; /* memory */
|
uint16 M[MAXMEMSIZE] = { 0 }; /* memory */
|
||||||
|
@ -259,6 +276,9 @@ int32 sc = 0; /* shift count */
|
||||||
int32 ss[4]; /* sense switches */
|
int32 ss[4]; /* sense switches */
|
||||||
int32 dev_int = 0; /* dev ready */
|
int32 dev_int = 0; /* dev ready */
|
||||||
int32 dev_enb = 0; /* dev enable */
|
int32 dev_enb = 0; /* dev enable */
|
||||||
|
uint32 ext_ints = 0; // [RLA] 16 if extended interrupts enabled
|
||||||
|
uint16 dev_ext_int = 0; // [RLA] extended interrupt request bitmap
|
||||||
|
uint16 dev_ext_enb = 0; // [RLA] extended interrupt enable bitmap
|
||||||
int32 ind_max = 8; /* iadr nest limit */
|
int32 ind_max = 8; /* iadr nest limit */
|
||||||
int32 stop_inst = 1; /* stop on ill inst */
|
int32 stop_inst = 1; /* stop on ill inst */
|
||||||
int32 stop_dev = 2; /* stop on ill dev */
|
int32 stop_dev = 2; /* stop on ill dev */
|
||||||
|
@ -276,6 +296,9 @@ int32 hst_p = 0; /* history pointer */
|
||||||
int32 hst_lnt = 0; /* history length */
|
int32 hst_lnt = 0; /* history length */
|
||||||
InstHistory *hst = NULL; /* instruction history */
|
InstHistory *hst = NULL; /* instruction history */
|
||||||
|
|
||||||
|
extern int32 sim_int_char;
|
||||||
|
extern DEVICE *sim_devices[];
|
||||||
|
|
||||||
t_bool devtab_init (void);
|
t_bool devtab_init (void);
|
||||||
int32 dmaio (int32 inst, int32 fnc, int32 dat, int32 dev);
|
int32 dmaio (int32 inst, int32 fnc, int32 dat, int32 dev);
|
||||||
int32 undio (int32 inst, int32 fnc, int32 dat, int32 dev);
|
int32 undio (int32 inst, int32 fnc, int32 dat, int32 dev);
|
||||||
|
@ -289,6 +312,11 @@ t_stat cpu_show_hist (FILE *st, UNIT *uptr, int32 val, void *desc);
|
||||||
t_stat cpu_show_dma (FILE *st, UNIT *uptr, int32 val, void *desc);
|
t_stat cpu_show_dma (FILE *st, UNIT *uptr, int32 val, void *desc);
|
||||||
t_stat cpu_set_nchan (UNIT *uptr, int32 val, char *cptr, void *desc);
|
t_stat cpu_set_nchan (UNIT *uptr, int32 val, char *cptr, void *desc);
|
||||||
t_stat cpu_show_nchan (FILE *st, UNIT *uptr, int32 val, void *desc);
|
t_stat cpu_show_nchan (FILE *st, UNIT *uptr, int32 val, void *desc);
|
||||||
|
t_stat cpu_set_interrupts (UNIT *uptr, int32 val, char *cptr, void *desc);
|
||||||
|
t_stat cpu_show_interrupts (FILE *st, UNIT *uptr, int32 val, void *desc);
|
||||||
|
int32 sim_ota_2024 (int32 inst, int32 fnc, int32 dat, int32 dev);
|
||||||
|
int32 cpu_interrupt (int32 vec);
|
||||||
|
int32 cpu_ext_interrupt (void);
|
||||||
|
|
||||||
/* CPU data structures
|
/* CPU data structures
|
||||||
|
|
||||||
|
@ -298,7 +326,7 @@ t_stat cpu_show_nchan (FILE *st, UNIT *uptr, int32 val, void *desc);
|
||||||
cpu_mod CPU modifiers list
|
cpu_mod CPU modifiers list
|
||||||
*/
|
*/
|
||||||
|
|
||||||
DIB cpu_dib = { DMA, IOBUS, 1, &dmaio };
|
DIB cpu_dib = { DMA, 1, IOBUS, IOBUS, INT_V_NONE, INT_V_NONE, &dmaio, 0 };
|
||||||
|
|
||||||
UNIT cpu_unit = {
|
UNIT cpu_unit = {
|
||||||
UDATA (NULL, UNIT_FIX+UNIT_BINK+UNIT_EXT+UNIT_HSA+UNIT_DMC, MAXMEMSIZE)
|
UDATA (NULL, UNIT_FIX+UNIT_BINK+UNIT_EXT+UNIT_HSA+UNIT_DMC, MAXMEMSIZE)
|
||||||
|
@ -324,6 +352,8 @@ REG cpu_reg[] = {
|
||||||
{ FLDATA (START, dev_int, INT_V_START) },
|
{ FLDATA (START, dev_int, INT_V_START) },
|
||||||
{ ORDATA (DEVINT, dev_int, 16), REG_RO },
|
{ ORDATA (DEVINT, dev_int, 16), REG_RO },
|
||||||
{ ORDATA (DEVENB, dev_enb, 16), REG_RO },
|
{ ORDATA (DEVENB, dev_enb, 16), REG_RO },
|
||||||
|
{ ORDATA (EXTINT, dev_ext_int, 16), REG_RO },
|
||||||
|
{ ORDATA (EXTENB, dev_ext_enb, 16), REG_RO },
|
||||||
{ ORDATA (CHREQ, chan_req, DMA_MAX + DMC_MAX) },
|
{ ORDATA (CHREQ, chan_req, DMA_MAX + DMC_MAX) },
|
||||||
{ BRDATA (DMAAD, dma_ad, 8, 16, DMA_MAX) },
|
{ BRDATA (DMAAD, dma_ad, 8, 16, DMA_MAX) },
|
||||||
{ BRDATA (DMAWC, dma_wc, 8, 16, DMA_MAX) },
|
{ BRDATA (DMAWC, dma_wc, 8, 16, DMA_MAX) },
|
||||||
|
@ -353,18 +383,14 @@ MTAB cpu_mod[] = {
|
||||||
{ UNIT_MSIZE, 32768, NULL, "32K", &cpu_set_size },
|
{ UNIT_MSIZE, 32768, NULL, "32K", &cpu_set_size },
|
||||||
{ MTAB_XTD | MTAB_VDV, 0, "channels", "CHANNELS",
|
{ MTAB_XTD | MTAB_VDV, 0, "channels", "CHANNELS",
|
||||||
&cpu_set_nchan, &cpu_show_nchan, NULL },
|
&cpu_set_nchan, &cpu_show_nchan, NULL },
|
||||||
|
{ MTAB_XTD | MTAB_VDV, 0, NULL, "DMA", // [RLA] this is the way it's
|
||||||
|
&cpu_set_nchan, NULL, NULL }, // [RLA] documented to work!
|
||||||
{ UNIT_DMC, 0, "no DMC", "NODMC", NULL },
|
{ UNIT_DMC, 0, "no DMC", "NODMC", NULL },
|
||||||
{ UNIT_DMC, UNIT_DMC, "DMC", "DMC", NULL },
|
{ UNIT_DMC, UNIT_DMC, "DMC", "DMC", NULL },
|
||||||
{ MTAB_XTD|MTAB_VDV|MTAB_NMO|MTAB_SHP, 0, "HISTORY", "HISTORY",
|
{ MTAB_XTD|MTAB_VDV|MTAB_NMO|MTAB_SHP, 0, "HISTORY", "HISTORY",
|
||||||
&cpu_set_hist, &cpu_show_hist },
|
&cpu_set_hist, &cpu_show_hist },
|
||||||
{ MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "DMA1", NULL,
|
{ MTAB_XTD | MTAB_VDV, 0, "extended interrupts", "EXTINT",
|
||||||
NULL, &cpu_show_dma, NULL },
|
&cpu_set_interrupts, &cpu_show_interrupts, NULL },
|
||||||
{ MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DMA2", NULL,
|
|
||||||
NULL, &cpu_show_dma, NULL },
|
|
||||||
{ MTAB_XTD | MTAB_VDV | MTAB_NMO, 2, "DMA3", NULL,
|
|
||||||
NULL, &cpu_show_dma, NULL },
|
|
||||||
{ MTAB_XTD | MTAB_VDV | MTAB_NMO, 3, "DMA4", NULL,
|
|
||||||
NULL, &cpu_show_dma, NULL },
|
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -380,9 +406,10 @@ t_stat sim_instr (void)
|
||||||
{
|
{
|
||||||
int32 AR, BR, MB, Y, t1, t2, t3, skip, dev;
|
int32 AR, BR, MB, Y, t1, t2, t3, skip, dev;
|
||||||
uint32 ut;
|
uint32 ut;
|
||||||
|
t_bool iack; // [RLA] TRUE if an interrupt was taken this cycle
|
||||||
t_stat reason;
|
t_stat reason;
|
||||||
t_stat Ea (int32 inst, int32 *addr);
|
t_stat Ea (int32 inst, int32 *addr);
|
||||||
void Write (int32 addr, int32 val);
|
t_stat Write (int32 addr, int32 val); // [RLA] Write() can now cause a break
|
||||||
int32 Add16 (int32 val1, int32 val2);
|
int32 Add16 (int32 val1, int32 val2);
|
||||||
int32 Add31 (int32 val1, int32 val2);
|
int32 Add31 (int32 val1, int32 val2);
|
||||||
int32 Operate (int32 MB, int32 AR);
|
int32 Operate (int32 MB, int32 AR);
|
||||||
|
@ -443,6 +470,7 @@ if (chan_req) { /* channel request? */
|
||||||
return STOP_DMAER;
|
return STOP_DMAER;
|
||||||
if ((r = t >> IOT_V_REASON) != 0)
|
if ((r = t >> IOT_V_REASON) != 0)
|
||||||
return r;
|
return r;
|
||||||
|
// [RLA] Note that we intentionally ignore address breaks here!
|
||||||
Write (ad, t & DMASK); /* write to mem */
|
Write (ad, t & DMASK); /* write to mem */
|
||||||
}
|
}
|
||||||
else { /* no, output */
|
else { /* no, output */
|
||||||
|
@ -464,6 +492,7 @@ if (chan_req) { /* channel request? */
|
||||||
}
|
}
|
||||||
else { /* DMC */
|
else { /* DMC */
|
||||||
st = (st & DMA_IN) | ((ad + 1) & X_AMASK);
|
st = (st & DMA_IN) | ((ad + 1) & X_AMASK);
|
||||||
|
// [RLA] Note that we intentionally ignore address breaks here!
|
||||||
Write (dmcad, st); /* update start */
|
Write (dmcad, st); /* update start */
|
||||||
end = Read (dmcad + 1); /* get end */
|
end = Read (dmcad + 1); /* get end */
|
||||||
if (((ad ^ end) & X_AMASK) == 0) { /* start == end? */
|
if (((ad ^ end) & X_AMASK) == 0) { /* start == end? */
|
||||||
|
@ -479,13 +508,15 @@ if (chan_req) { /* channel request? */
|
||||||
|
|
||||||
/* Interrupts */
|
/* Interrupts */
|
||||||
|
|
||||||
if ((dev_int & (INT_PEND|INT_NMI|dev_enb)) > INT_PEND) {/* int req? */
|
//[RLA] Todo - add WDT interrupts ????
|
||||||
pme = ext; /* save extend */
|
iack = FALSE;
|
||||||
if (cpu_unit.flags & UNIT_EXT) /* ext opt? extend on */
|
if ((dev_int & (INT_PEND|INT_NMI|dev_enb)) > INT_PEND) { // [RLA] check for standard interrupt
|
||||||
ext = 1;
|
MB = cpu_interrupt(M_INT); iack = TRUE;
|
||||||
dev_int = dev_int & ~INT_ON; /* intr off */
|
}
|
||||||
MB = 0120000 | M_INT; /* inst = JST* 63 */
|
else if ( ((dev_ext_int & dev_ext_enb) != 0) // [RLA] check for extended interrupt
|
||||||
}
|
&& ((dev_int & INT_PEND) == INT_PEND) ) {
|
||||||
|
MB = cpu_ext_interrupt(); iack = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
/* Instruction fetch */
|
/* Instruction fetch */
|
||||||
|
|
||||||
|
@ -512,6 +543,7 @@ if (hst_lnt) { /* instr hist? */
|
||||||
hst[hst_p].ar = AR;
|
hst[hst_p].ar = AR;
|
||||||
hst[hst_p].br = BR;
|
hst[hst_p].br = BR;
|
||||||
hst[hst_p].xr = XR;
|
hst[hst_p].xr = XR;
|
||||||
|
hst[hst_p].iack = iack; // [RLA] record if interrupt taken
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Memory reference instructions */
|
/* Memory reference instructions */
|
||||||
|
@ -547,9 +579,9 @@ switch (I_GETOP (MB)) { /* case on <1:6> */
|
||||||
case 004: case 024: case 044: case 064: /* STA */
|
case 004: case 024: case 044: case 064: /* STA */
|
||||||
if ((reason = Ea (MB, &Y))) /* eff addr */
|
if ((reason = Ea (MB, &Y))) /* eff addr */
|
||||||
break;
|
break;
|
||||||
Write (Y, AR); /* store A */
|
if ((reason = Write(Y, AR))) break; /* [RLA] store A */
|
||||||
if (dp) { /* double prec? */
|
if (dp) { /* double prec? */
|
||||||
Write (Y | 1, BR); /* store B */
|
if ((reason = Write(Y | 1, BR))) break; /* [RLA] store B */
|
||||||
sc = 0;
|
sc = 0;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -590,7 +622,7 @@ switch (I_GETOP (MB)) { /* case on <1:6> */
|
||||||
if ((reason = Ea (MB, &Y))) /* eff addr */
|
if ((reason = Ea (MB, &Y))) /* eff addr */
|
||||||
break;
|
break;
|
||||||
MB = NEWA (Read (Y), PC); /* merge old PC */
|
MB = NEWA (Read (Y), PC); /* merge old PC */
|
||||||
Write (Y, MB);
|
if ((reason = Write(Y, MB))) break; // [RLA]
|
||||||
PCQ_ENTRY;
|
PCQ_ENTRY;
|
||||||
PC = NEWA (PC, Y + 1); /* set new PC */
|
PC = NEWA (PC, Y + 1); /* set new PC */
|
||||||
break;
|
break;
|
||||||
|
@ -609,7 +641,7 @@ switch (I_GETOP (MB)) { /* case on <1:6> */
|
||||||
if ((reason = Ea (MB, &Y))) /* eff addr */
|
if ((reason = Ea (MB, &Y))) /* eff addr */
|
||||||
break;
|
break;
|
||||||
MB = (Read (Y) + 1) & DMASK; /* incr, rewrite */
|
MB = (Read (Y) + 1) & DMASK; /* incr, rewrite */
|
||||||
Write (Y, MB);
|
if ((reason = Write(Y, MB))) break; // [RLA]
|
||||||
if (MB == 0) /* skip if zero */
|
if (MB == 0) /* skip if zero */
|
||||||
PC = NEWA (PC, PC + 1);
|
PC = NEWA (PC, PC + 1);
|
||||||
break;
|
break;
|
||||||
|
@ -618,14 +650,14 @@ switch (I_GETOP (MB)) { /* case on <1:6> */
|
||||||
if ((reason = Ea (MB, &Y))) /* eff addr */
|
if ((reason = Ea (MB, &Y))) /* eff addr */
|
||||||
break;
|
break;
|
||||||
MB = Read (Y);
|
MB = Read (Y);
|
||||||
Write (Y, AR); /* A to mem */
|
if ((reason = Write(Y, AR))) break; /* [RLA] A to mem */
|
||||||
AR = MB; /* mem to A */
|
AR = MB; /* mem to A */
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 015: case 055: /* STX */
|
case 015: case 055: /* STX */
|
||||||
if ((reason = Ea (MB & ~IDX, &Y))) /* eff addr */
|
if ((reason = Ea (MB & ~IDX, &Y))) /* eff addr */
|
||||||
break;
|
break;
|
||||||
Write (Y, XR); /* store XR */
|
if ((reason = Write(Y, XR))) break; /* [RLA] store XR */
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 035: case 075: /* LDX */
|
case 035: case 075: /* LDX */
|
||||||
|
@ -695,7 +727,11 @@ switch (I_GETOP (MB)) { /* case on <1:6> */
|
||||||
|
|
||||||
case 074: /* OTA */
|
case 074: /* OTA */
|
||||||
dev = MB & DEVMASK;
|
dev = MB & DEVMASK;
|
||||||
t2 = iotab[dev] (ioOTA, I_GETFNC (MB), AR, dev);
|
// [RLA] OTA w/devices 20 or 24 are SMK or OTK!
|
||||||
|
if ((dev == 020) || (dev == 024))
|
||||||
|
t2 = sim_ota_2024(ioOTA, I_GETFNC (MB), AR, dev);
|
||||||
|
else
|
||||||
|
t2 = iotab[dev] (ioOTA, I_GETFNC (MB), AR, dev);
|
||||||
reason = t2 >> IOT_V_REASON;
|
reason = t2 >> IOT_V_REASON;
|
||||||
if (t2 & IOT_SKIP) /* skip? */
|
if (t2 & IOT_SKIP) /* skip? */
|
||||||
PC = NEWA (PC, PC + 1);
|
PC = NEWA (PC, PC + 1);
|
||||||
|
@ -1055,13 +1091,18 @@ return SCPE_OK;
|
||||||
|
|
||||||
/* Write memory */
|
/* Write memory */
|
||||||
|
|
||||||
void Write (int32 addr, int32 val)
|
t_stat Write (int32 addr, int32 val)
|
||||||
{
|
{
|
||||||
if (((addr == 0) || (addr >= 020)) && MEM_ADDR_OK (addr))
|
// [RLA] Write() now checks for address breaks ...
|
||||||
|
if (((addr == 0) || (addr >= 020)) && MEM_ADDR_OK (addr))
|
||||||
M[addr] = val;
|
M[addr] = val;
|
||||||
if (addr == M_XR) /* write XR loc? */
|
if (addr == M_XR) /* write XR loc? */
|
||||||
XR = val; /* update XR */
|
XR = val;
|
||||||
return;
|
// [RLA] Implement "break on memory write" ...
|
||||||
|
if (sim_brk_summ && sim_brk_test (addr, SWMASK ('W')))
|
||||||
|
return STOP_IBKPT;
|
||||||
|
else
|
||||||
|
return SCPE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add */
|
/* Add */
|
||||||
|
@ -1086,6 +1127,51 @@ else C = 0;
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [RLA] Standard (fixed vector) interrupt action ...
|
||||||
|
int32 cpu_interrupt (int32 vec) {
|
||||||
|
pme = ext; /* save extend */
|
||||||
|
if (cpu_unit.flags & UNIT_EXT) ext = 1; /* ext opt? extend on */
|
||||||
|
dev_int = dev_int & ~INT_ON; /* intr off */
|
||||||
|
return 0120000 | vec; /* inst = JST* vector */
|
||||||
|
}
|
||||||
|
|
||||||
|
// [RLA] Extended (priority) interrupt action ...
|
||||||
|
int32 cpu_ext_interrupt (void) {
|
||||||
|
// Unlike the standard interrupts, which have a fixed vector shared by all
|
||||||
|
// devices, the extended interrupts have a unique vector for every device.
|
||||||
|
// Moreover, extended interrupts are prioritized so that the lowest numbered
|
||||||
|
// interrupts have priority. That means we have to actually scan the bitmap
|
||||||
|
// of active interrupts to figure out which one to take.
|
||||||
|
//
|
||||||
|
// One uncomfortable thing about the external interrupts is that it appears
|
||||||
|
// that they were edge triggered - once an interrupt on a given level was
|
||||||
|
// granted, that interrupt wouldn't occur again until another edge occurred on
|
||||||
|
// the same request. I'm "uncomfortable" with this because it's different from
|
||||||
|
// the way the standard interrupt works - that's completely level sensitive.
|
||||||
|
// Still, this Honeywell document
|
||||||
|
//
|
||||||
|
// http://bitsavers.informatik.uni-stuttgart.de/pdf/honeywell/series16/h316/70130072167D_316_Interfacing_Apr73.pdf
|
||||||
|
//
|
||||||
|
// (read Chapter 4, Priority Interrupts, the very first paragraph) at least
|
||||||
|
// seems to imply edge triggering. And the IMP firmware is written as if they
|
||||||
|
// are edge triggered - there are many cases (modem output, task, RTC) where
|
||||||
|
// the IMP code does nothing to clear the interrupt request flag. So we're
|
||||||
|
// going with edge triggered version for now...
|
||||||
|
int32 i; uint16 m, irq;
|
||||||
|
irq = dev_ext_int & dev_ext_enb;
|
||||||
|
for (i = 1, m = SIGN; m != 0; ++i, m >>= 1) {
|
||||||
|
if ((irq & m) != 0) {
|
||||||
|
// Extended interrupts are edge triggered (see above) - when this
|
||||||
|
// interrupt is granted, clear the request ...
|
||||||
|
CLR_EXT_INT(m);
|
||||||
|
return cpu_interrupt(M_INT+i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we get here, it means that we were called with no interrupt bits set.
|
||||||
|
// That really should never happen, so just HALT ...
|
||||||
|
return(0);
|
||||||
|
}
|
||||||
|
|
||||||
/* Unimplemented I/O device */
|
/* Unimplemented I/O device */
|
||||||
|
|
||||||
int32 undio (int32 op, int32 fnc, int32 val, int32 dev)
|
int32 undio (int32 op, int32 fnc, int32 val, int32 dev)
|
||||||
|
@ -1093,6 +1179,63 @@ int32 undio (int32 op, int32 fnc, int32 val, int32 dev)
|
||||||
return ((stop_dev << IOT_V_REASON) | val);
|
return ((stop_dev << IOT_V_REASON) | val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* [RLA] Special I/O devices */
|
||||||
|
|
||||||
|
int32 sim_ota_2024 (int32 inst, int32 fnc, int32 dat, int32 dev)
|
||||||
|
{
|
||||||
|
// OTA instructions with a device code of 20 or 24 are really SMK
|
||||||
|
// (Set interrupt Mask) instructions. OTA 20 sets the standard H316
|
||||||
|
// interrupt mask, and OTA 120, OTA 220 and OTA 320 set the extended
|
||||||
|
// interrupt mask (of which only one, OTA 120, is used by the IMP).
|
||||||
|
//
|
||||||
|
// Further, OTA 1020 is the OTK instruction which sets special CPU
|
||||||
|
// flags (single or double precision HSA, extended addressing mode,
|
||||||
|
// the carry flag, etc).
|
||||||
|
//
|
||||||
|
// The original simh implementation handled the regular SMK and OTK
|
||||||
|
// as special cases in the CLK device. Why the CLK device??? Because
|
||||||
|
// it also uses device code 20! Shame - these have nothing to do with
|
||||||
|
// the clock!
|
||||||
|
//
|
||||||
|
// This routine implements these special OTKs as part of the CPU.
|
||||||
|
// That allows us to implement the extra interrupt masks needed by the
|
||||||
|
// IMP, and it also allows the CLK device to be disabled without losing
|
||||||
|
// the SMK or OTK instructions. The clock was an option on the original
|
||||||
|
// H316 and is not required to be present, and the IMP in particular
|
||||||
|
// needs it to be disabled.
|
||||||
|
|
||||||
|
// Although OTA 24 is reserved nothing we currently simulate uses it!
|
||||||
|
if (dev == 024) return IOBADFNC (dat);
|
||||||
|
|
||||||
|
// Device code 20...
|
||||||
|
switch (fnc) {
|
||||||
|
case 000: // SMK 020 - set standard interrupt mask
|
||||||
|
dev_enb = dat; break;
|
||||||
|
case 001: // SMK 120 - set extended interrupt mask #1
|
||||||
|
if (ext_ints < 16) return IOBADFNC(dat);
|
||||||
|
dev_ext_enb = dat; break;
|
||||||
|
case 002: // SMK 220 - set extended interrupt mask #2
|
||||||
|
case 003: // SMK 320 - set extended interrupt mask #3
|
||||||
|
return IOBADFNC(dat);
|
||||||
|
case 010: // OTK - output keys
|
||||||
|
C = (dat >> 15) & 1; /* set C */
|
||||||
|
if (cpu_unit.flags & UNIT_HSA) /* HSA included? */
|
||||||
|
dp = (dat >> 14) & 1; /* set dp */
|
||||||
|
if (cpu_unit.flags & UNIT_EXT) { /* ext opt? */
|
||||||
|
if (dat & 020000) { /* ext set? */
|
||||||
|
ext = 1; /* yes, set */
|
||||||
|
extoff_pending = 0;
|
||||||
|
}
|
||||||
|
else extoff_pending = 1; /* no, clr later */
|
||||||
|
}
|
||||||
|
sc = dat & 037; /* set sc */
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return IOBADFNC (dat);
|
||||||
|
}
|
||||||
|
return dat;
|
||||||
|
}
|
||||||
|
|
||||||
/* DMA control */
|
/* DMA control */
|
||||||
|
|
||||||
int32 dmaio (int32 inst, int32 fnc, int32 dat, int32 dev)
|
int32 dmaio (int32 inst, int32 fnc, int32 dat, int32 dev)
|
||||||
|
@ -1287,7 +1430,7 @@ C = 0;
|
||||||
dp = 0;
|
dp = 0;
|
||||||
ext = pme = extoff_pending = 0;
|
ext = pme = extoff_pending = 0;
|
||||||
dev_int = dev_int & ~(INT_PEND|INT_NMI);
|
dev_int = dev_int & ~(INT_PEND|INT_NMI);
|
||||||
dev_enb = 0;
|
dev_ext_int = dev_enb = dev_ext_enb = 0;
|
||||||
for (i = 0; i < DMA_MAX; i++)
|
for (i = 0; i < DMA_MAX; i++)
|
||||||
dma_ad[i] = dma_wc[i] = dma_eor[i] = 0;
|
dma_ad[i] = dma_wc[i] = dma_eor[i] = 0;
|
||||||
chan_req = 0;
|
chan_req = 0;
|
||||||
|
@ -1295,7 +1438,10 @@ pcq_r = find_reg ("PCQ", NULL, dptr);
|
||||||
if (pcq_r)
|
if (pcq_r)
|
||||||
pcq_r->qptr = 0;
|
pcq_r->qptr = 0;
|
||||||
else return SCPE_IERR;
|
else return SCPE_IERR;
|
||||||
sim_brk_types = sim_brk_dflt = SWMASK ('E');
|
// [RLA] We now have two break types - "E" (break on execution) and also "W"
|
||||||
|
// [RLA] (break on write)...
|
||||||
|
sim_brk_types = SWMASK('W') | SWMASK('E');
|
||||||
|
sim_brk_dflt = SWMASK ('E');
|
||||||
return SCPE_OK;
|
return SCPE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1349,6 +1495,28 @@ for (i = MEMSIZE; i < MAXMEMSIZE; i++)
|
||||||
return SCPE_OK;
|
return SCPE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* [RLA] Set/Show number of interrupts supported */
|
||||||
|
|
||||||
|
t_stat cpu_set_interrupts (UNIT *uptr, int32 val, char *cptr, void *desc)
|
||||||
|
{
|
||||||
|
uint32 newint; t_stat ret;
|
||||||
|
if (cptr == NULL) return SCPE_ARG;
|
||||||
|
newint = get_uint (cptr, 10, 49, &ret);
|
||||||
|
if (ret != SCPE_OK) return ret;
|
||||||
|
if ((newint != 0) && (newint != 16)) return SCPE_ARG;
|
||||||
|
ext_ints = newint;
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
t_stat cpu_show_interrupts (FILE *st, UNIT *uptr, int32 val, void *desc)
|
||||||
|
{
|
||||||
|
if (ext_ints == 0)
|
||||||
|
fprintf(st,"standard interrupts");
|
||||||
|
else
|
||||||
|
fprintf(st,"extended interrupts = %d", ext_ints);
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
t_stat cpu_set_nchan (UNIT *uptr, int32 val, char *cptr, void *desc)
|
t_stat cpu_set_nchan (UNIT *uptr, int32 val, char *cptr, void *desc)
|
||||||
{
|
{
|
||||||
uint32 i, newmax;
|
uint32 i, newmax;
|
||||||
|
@ -1373,7 +1541,7 @@ t_stat cpu_show_nchan (FILE *st, UNIT *uptr, int32 val, void *desc)
|
||||||
{
|
{
|
||||||
if (dma_nch)
|
if (dma_nch)
|
||||||
fprintf (st, "DMA channels = %d", dma_nch);
|
fprintf (st, "DMA channels = %d", dma_nch);
|
||||||
else fprintf (st, "no DMA channels");
|
else fprintf (st, "no DMA");
|
||||||
return SCPE_OK;
|
return SCPE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1481,17 +1649,42 @@ return SCPE_OK;
|
||||||
|
|
||||||
/* Set up I/O dispatch and channel maps */
|
/* Set up I/O dispatch and channel maps */
|
||||||
|
|
||||||
|
// [RLA] Check for DMC conflicts (on both DMC channels!) ...
|
||||||
|
t_bool set_chanmap (DEVICE *dptr, DIB *dibp, uint32 dno, uint32 chan)
|
||||||
|
{
|
||||||
|
if ((chan < DMC_V_DMC1) && (chan >= dma_nch)) {
|
||||||
|
printf ("%s configured for DMA channel %d\n", sim_dname (dptr), chan + 1);
|
||||||
|
if (sim_log)
|
||||||
|
fprintf (sim_log, "%s configured for DMA channel %d\n", sim_dname (dptr), chan + 1);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
if ((chan >= DMC_V_DMC1) && !(cpu_unit.flags & UNIT_DMC)) {
|
||||||
|
printf ("%s configured for DMC, option disabled\n", sim_dname (dptr));
|
||||||
|
if (sim_log)
|
||||||
|
fprintf (sim_log, "%s configured for DMC, option disabled\n", sim_dname (dptr));
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
if (chan_map[chan]) { /* channel conflict? */
|
||||||
|
printf ("%s DMA/DMC channel conflict, devno = %02o\n", sim_dname (dptr), dno);
|
||||||
|
if (sim_log)
|
||||||
|
fprintf (sim_log, "%s DMA/DMC channel conflict, devno = %02o\n", sim_dname (dptr), dno);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
chan_map[chan] = dno; /* channel back map */
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
t_bool devtab_init (void)
|
t_bool devtab_init (void)
|
||||||
{
|
{
|
||||||
DEVICE *dptr;
|
DEVICE *dptr;
|
||||||
DIB *dibp;
|
DIB *dibp;
|
||||||
uint32 i, j, dno, chan;
|
uint32 i, j, dno;
|
||||||
|
|
||||||
for (i = 0; i < DEV_MAX; i++)
|
for (i = 0; i < DEV_MAX; i++)
|
||||||
iotab[i] = NULL;
|
iotab[i] = NULL;
|
||||||
for (i = 0; i < (DMA_MAX + DMC_MAX); i++)
|
for (i = 0; i < (DMA_MAX + DMC_MAX); i++)
|
||||||
chan_map[i] = 0;
|
chan_map[i] = 0;
|
||||||
for (i = 0; (dptr = sim_devices[i]); i++) { /* loop thru devices */
|
for (i = 0; (dptr = sim_devices[i]); i++) { /* loop thru devices */
|
||||||
dibp = (DIB *) dptr->ctxt; /* get DIB */
|
dibp = (DIB *) dptr->ctxt; /* get DIB */
|
||||||
if ((dibp == NULL) || (dptr->flags & DEV_DIS)) /* exist, enabled? */
|
if ((dibp == NULL) || (dptr->flags & DEV_DIS)) /* exist, enabled? */
|
||||||
continue;
|
continue;
|
||||||
|
@ -1507,34 +1700,18 @@ for (i = 0; (dptr = sim_devices[i]); i++) { /* loop thru devices *
|
||||||
}
|
}
|
||||||
iotab[dno + j] = dibp->io; /* set I/O routine */
|
iotab[dno + j] = dibp->io; /* set I/O routine */
|
||||||
} /* end for */
|
} /* end for */
|
||||||
if (dibp->chan) { /* DMA/DMC? */
|
// [RLA] set up the channel map
|
||||||
chan = dibp->chan - 1;
|
if (dibp->chan != 0)
|
||||||
if ((chan < DMC_V_DMC1) && (chan >= dma_nch)) {
|
if (set_chanmap(dptr, dibp, dno, dibp->chan-1)) return TRUE;
|
||||||
printf ("%s configured for DMA channel %d\n",
|
if (dibp->chan2 != 0)
|
||||||
sim_dname (dptr), chan + 1);
|
if (set_chanmap(dptr, dibp, dno, dibp->chan2-1)) return TRUE;
|
||||||
if (sim_log)
|
// [RLA] If the device uses extended interrupts, check that they're enabled.
|
||||||
fprintf (sim_log, "%s configured for DMA channel %d\n",
|
if ((dibp->inum != INT_V_NONE) && (dibp->inum >= INT_V_EXTD) && (ext_ints == 0)) {
|
||||||
sim_dname (dptr), chan + 1);
|
printf ("%s uses extended interrupts but that option is disabled\n", sim_dname (dptr));
|
||||||
return TRUE;
|
if (sim_log)
|
||||||
}
|
fprintf (sim_log, "%s uses extended interrupts but that option is disabled\n", sim_dname (dptr));
|
||||||
if ((chan >= DMC_V_DMC1) && !(cpu_unit.flags & UNIT_DMC)) {
|
return TRUE;
|
||||||
printf ("%s configured for DMC, option disabled\n",
|
}
|
||||||
sim_dname (dptr));
|
|
||||||
if (sim_log)
|
|
||||||
fprintf (sim_log, "%s configured for DMC, option disabled\n",
|
|
||||||
sim_dname (dptr));
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
if (chan_map[chan]) { /* channel conflict? */
|
|
||||||
printf ("%s DMA/DMC channel conflict, devno = %02o\n",
|
|
||||||
sim_dname (dptr), dno);
|
|
||||||
if (sim_log)
|
|
||||||
fprintf (sim_log, "%s DMA/DMC channel conflict, devno = %02o\n",
|
|
||||||
sim_dname (dptr), dno);
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
chan_map[chan] = dno; /* channel back map */
|
|
||||||
}
|
|
||||||
} /* end for */
|
} /* end for */
|
||||||
for (i = 0; i < DEV_MAX; i++) { /* fill in blanks */
|
for (i = 0; i < DEV_MAX; i++) { /* fill in blanks */
|
||||||
if (iotab[i] == NULL)
|
if (iotab[i] == NULL)
|
||||||
|
@ -1598,7 +1775,8 @@ else lnt = hst_lnt;
|
||||||
di = hst_p - lnt; /* work forward */
|
di = hst_p - lnt; /* work forward */
|
||||||
if (di < 0)
|
if (di < 0)
|
||||||
di = di + hst_lnt;
|
di = di + hst_lnt;
|
||||||
fprintf (st, "PC C A B X ea IR\n\n");
|
fprintf (st, " PC C A B X ea IR\n");
|
||||||
|
fprintf (st, "----- - ------ ------ ------ ----- -----------\n\n");
|
||||||
for (k = 0; k < lnt; k++) { /* print specified */
|
for (k = 0; k < lnt; k++) { /* print specified */
|
||||||
h = &hst[(++di) % hst_lnt]; /* entry pointer */
|
h = &hst[(++di) % hst_lnt]; /* entry pointer */
|
||||||
if (h->pc & HIST_PC) { /* instruction? */
|
if (h->pc & HIST_PC) { /* instruction? */
|
||||||
|
@ -1615,6 +1793,8 @@ for (k = 0; k < lnt; k++) { /* print specified */
|
||||||
op = I_GETOP (h->ir) & 017; /* base op */
|
op = I_GETOP (h->ir) & 017; /* base op */
|
||||||
if (has_opnd[op])
|
if (has_opnd[op])
|
||||||
fprintf (st, " [%06o]", h->opnd);
|
fprintf (st, " [%06o]", h->opnd);
|
||||||
|
if (h->iack) // [RLA]
|
||||||
|
fprintf(st, " INTERRUPT"); // [RLA]
|
||||||
fputc ('\n', st); /* end line */
|
fputc ('\n', st); /* end line */
|
||||||
} /* end else instruction */
|
} /* end else instruction */
|
||||||
} /* end for */
|
} /* end for */
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
used in advertising or otherwise to promote the sale, use or other dealings
|
used in advertising or otherwise to promote the sale, use or other dealings
|
||||||
in this Software without prior written authorization from Robert M Supnik.
|
in this Software without prior written authorization from Robert M Supnik.
|
||||||
|
|
||||||
|
31-May-13 RLA DIB - add second channel, interrupt and user parameter
|
||||||
19-Nov-11 RMS Removed XR macro, added XR_LOC macro (from Adrian Wise)
|
19-Nov-11 RMS Removed XR macro, added XR_LOC macro (from Adrian Wise)
|
||||||
22-May-10 RMS Added check for 64b definitions
|
22-May-10 RMS Added check for 64b definitions
|
||||||
15-Feb-05 RMS Added start button interrupt
|
15-Feb-05 RMS Added start button interrupt
|
||||||
|
@ -110,11 +111,15 @@
|
||||||
/* Device information block */
|
/* Device information block */
|
||||||
|
|
||||||
struct h316_dib {
|
struct h316_dib {
|
||||||
uint32 dev; /* device number */
|
uint32 dev; /* device number */
|
||||||
uint32 chan; /* dma/dmc channel */
|
uint32 num; /* number of slots */
|
||||||
uint32 num; /* number of slots */
|
uint32 chan; /* dma/dmc channel */
|
||||||
int32 (*io) (int32 inst, int32 fnc, int32 dat, int32 dev); };
|
uint32 chan2; /* alternate DMA/DMD channel */
|
||||||
|
uint32 inum; /* interrupt number */
|
||||||
|
uint32 inum2; /* alternate interrupt */
|
||||||
|
int32 (*io) (int32 inst, int32 fnc, int32 dat, int32 dev);
|
||||||
|
uint32 u3; /* "user" parameter #1 */
|
||||||
|
};
|
||||||
typedef struct h316_dib DIB;
|
typedef struct h316_dib DIB;
|
||||||
|
|
||||||
/* DMA/DMC channel numbers */
|
/* DMA/DMC channel numbers */
|
||||||
|
@ -142,33 +147,35 @@ typedef struct h316_dib DIB;
|
||||||
|
|
||||||
/* I/O device codes */
|
/* I/O device codes */
|
||||||
|
|
||||||
#define PTR 001 /* paper tape reader */
|
#define PTR 001 /* paper tape reader */
|
||||||
#define PTP 002 /* paper tape punch */
|
#define PTP 002 /* paper tape punch */
|
||||||
#define LPT 003 /* line printer */
|
#define LPT 003 /* line printer */
|
||||||
#define TTY 004 /* console */
|
#define TTY 004 /* console */
|
||||||
#define CDR 005 /* card reader */
|
#define CDR 005 /* card reader */
|
||||||
#define MT 010 /* mag tape data */
|
#define MT 010 /* mag tape data */
|
||||||
#define CLK_KEYS 020 /* clock/keys (CPU) */
|
#define CLK_KEYS 020 /* clock/keys (CPU) */
|
||||||
#define FHD 022 /* fixed head disk */
|
#define FHD 022 /* fixed head disk */
|
||||||
#define DMA 024 /* DMA control */
|
#define DMA 024 /* DMA control */
|
||||||
#define DP 025 /* moving head disk */
|
#define DP 025 /* moving head disk */
|
||||||
#define DEV_MAX 64
|
#define DEV_MAX 64
|
||||||
|
|
||||||
/* Interrupt flags, definitions correspond to SMK bits */
|
/* Interrupt flags, definitions correspond to SMK bits */
|
||||||
|
|
||||||
#define INT_V_CLK 0 /* clock */
|
#define INT_V_CLK 0 /* clock */
|
||||||
#define INT_V_MPE 1 /* parity error */
|
#define INT_V_MPE 1 /* parity error */
|
||||||
#define INT_V_LPT 2 /* line printer */
|
#define INT_V_LPT 2 /* line printer */
|
||||||
#define INT_V_CDR 4 /* card reader */
|
#define INT_V_CDR 4 /* card reader */
|
||||||
#define INT_V_TTY 5 /* teletype */
|
#define INT_V_TTY 5 /* teletype */
|
||||||
#define INT_V_PTP 6 /* paper tape punch */
|
#define INT_V_PTP 6 /* paper tape punch */
|
||||||
#define INT_V_PTR 7 /* paper tape reader */
|
#define INT_V_PTR 7 /* paper tape reader */
|
||||||
#define INT_V_FHD 8 /* fixed head disk */
|
#define INT_V_FHD 8 /* fixed head disk */
|
||||||
#define INT_V_DP 12 /* moving head disk */
|
#define INT_V_DP 12 /* moving head disk */
|
||||||
#define INT_V_MT 15 /* mag tape */
|
#define INT_V_MT 15 /* mag tape */
|
||||||
#define INT_V_START 16 /* start button */
|
#define INT_V_START 16 /* start button */
|
||||||
#define INT_V_NODEF 17 /* int not deferred */
|
#define INT_V_NODEF 17 /* int not deferred */
|
||||||
#define INT_V_ON 18 /* int on */
|
#define INT_V_ON 18 /* int on */
|
||||||
|
#define INT_V_EXTD 16 /* first extended interrupt */
|
||||||
|
#define INT_V_NONE -1 /* no interrupt used */
|
||||||
|
|
||||||
/* I/O macros */
|
/* I/O macros */
|
||||||
|
|
||||||
|
@ -195,14 +202,22 @@ typedef struct h316_dib DIB;
|
||||||
#define INT_NMI (INT_START)
|
#define INT_NMI (INT_START)
|
||||||
#define INT_PEND (INT_ON | INT_NODEF)
|
#define INT_PEND (INT_ON | INT_NODEF)
|
||||||
|
|
||||||
#define SET_INT(x) dev_int = dev_int | (x)
|
// [RLA] These macros now all affect the standard interrupts. We'll leave
|
||||||
|
// [RLA] them alone for backward compatibility with the existing code.
|
||||||
|
#define SET_INT(x) dev_int = dev_int | (x)
|
||||||
#define CLR_INT(x) dev_int = dev_int & ~(x)
|
#define CLR_INT(x) dev_int = dev_int & ~(x)
|
||||||
#define TST_INT(x) ((dev_int & (x)) != 0)
|
#define TST_INT(x) ((dev_int & (x)) != 0)
|
||||||
#define CLR_ENB(x) dev_enb = dev_enb & ~(x)
|
#define CLR_ENB(x) dev_enb = dev_enb & ~(x)
|
||||||
#define TST_INTREQ(x) ((dev_int & dev_enb & (x)) != 0)
|
#define TST_INTREQ(x) ((dev_int & dev_enb & (x)) != 0)
|
||||||
|
|
||||||
|
// [RLA] These macros are functionally identical, but affect extended interrupts.
|
||||||
|
#define SET_EXT_INT(x) dev_ext_int = dev_ext_int | (x)
|
||||||
|
#define CLR_EXT_INT(x) dev_ext_int = dev_ext_int & ~(x)
|
||||||
|
#define TST_EXT_INT(x) ((dev_ext_int & (x)) != 0)
|
||||||
|
#define CLR_EXT_ENB(x) dev_ext_enb = dev_ext_enb & ~(x)
|
||||||
|
#define TST_EXT_INTREQ(x) ((dev_ext_int & dev_ext_enb & (x)) != 0)
|
||||||
|
|
||||||
/* Prototypes */
|
/* Prototypes */
|
||||||
|
|
||||||
t_stat io_set_iobus (UNIT *uptr, int32 val, char *cptr, void *desc);
|
t_stat io_set_iobus (UNIT *uptr, int32 val, char *cptr, void *desc);
|
||||||
t_stat io_set_dma (UNIT *uptr, int32 val, char *cptr, void *desc);
|
t_stat io_set_dma (UNIT *uptr, int32 val, char *cptr, void *desc);
|
||||||
t_stat io_set_dmc (UNIT *uptr, int32 val, char *cptr, void *desc);
|
t_stat io_set_dmc (UNIT *uptr, int32 val, char *cptr, void *desc);
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
4651 disk subsystem
|
4651 disk subsystem
|
||||||
4720 disk subsystem
|
4720 disk subsystem
|
||||||
|
|
||||||
|
3-Jul-13 RLA compatibility changes for extended interrupts
|
||||||
19-Mar-12 RMS Fixed declaration of chan_req (Mark Pizzolato)
|
19-Mar-12 RMS Fixed declaration of chan_req (Mark Pizzolato)
|
||||||
04-Sep-05 RMS Fixed missing return (Peter Schorn)
|
04-Sep-05 RMS Fixed missing return (Peter Schorn)
|
||||||
15-Jul-05 RMS Fixed bug in attach routine
|
15-Jul-05 RMS Fixed bug in attach routine
|
||||||
|
@ -269,7 +270,7 @@ t_stat dp_showformat (FILE *st, UNIT *uptr, int32 val, void *desc);
|
||||||
dp_mod DP modifier list
|
dp_mod DP modifier list
|
||||||
*/
|
*/
|
||||||
|
|
||||||
DIB dp_dib = { DP, DMC1, 1, &dpio };
|
DIB dp_dib = { DP, 1, DMC1, IOBUS, INT_V_DP, INT_V_NONE, &dpio, 0 };
|
||||||
|
|
||||||
UNIT dp_unit[] = {
|
UNIT dp_unit[] = {
|
||||||
{ UDATA (&dp_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
|
{ UDATA (&dp_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
fhd 516-4400 fixed head disk
|
fhd 516-4400 fixed head disk
|
||||||
|
|
||||||
03-Sep-13 RMS Added explicit void * cast
|
03-Sep-13 RMS Added explicit void * cast
|
||||||
|
3-Jul-13 RLA compatibility changes for extended interrupts
|
||||||
19-Mar-12 RMS Fixed declaration of chan_req (Mark Pizzolato)
|
19-Mar-12 RMS Fixed declaration of chan_req (Mark Pizzolato)
|
||||||
15-May-06 RMS Fixed bug in autosize attach (David Gesswein)
|
15-May-06 RMS Fixed bug in autosize attach (David Gesswein)
|
||||||
04-Jan-04 RMS Changed sim_fsize calling sequence
|
04-Jan-04 RMS Changed sim_fsize calling sequence
|
||||||
|
@ -114,7 +115,7 @@ uint32 fhd_csword (uint32 cs, uint32 ch);
|
||||||
fhd_reg register list
|
fhd_reg register list
|
||||||
*/
|
*/
|
||||||
|
|
||||||
DIB fhd_dib = { FHD, IOBUS, 1, &fhdio };
|
DIB fhd_dib = { FHD, 1, IOBUS, IOBUS, INT_V_FHD, INT_V_NONE, &fhdio, 0 };
|
||||||
|
|
||||||
UNIT fhd_unit = {
|
UNIT fhd_unit = {
|
||||||
UDATA (&fhd_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BUFABLE+UNIT_MUSTBUF,
|
UDATA (&fhd_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BUFABLE+UNIT_MUSTBUF,
|
||||||
|
|
325
H316/h316_hi.c
Normal file
325
H316/h316_hi.c
Normal file
|
@ -0,0 +1,325 @@
|
||||||
|
/* h316_hi.c- BBN ARPAnet IMP Host Interface
|
||||||
|
Based on the SIMH simulator package written by Robert M Supnik.
|
||||||
|
|
||||||
|
Copyright (c) 2013 Robert Armstrong, bob@jfcl.com.
|
||||||
|
|
||||||
|
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
|
||||||
|
ROBERT ARMSTRONG 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 Robert Armstrong shall not be
|
||||||
|
used in advertising or otherwise to promote the sale, use or other dealings
|
||||||
|
in this Software without prior written authorization from Robert Armstrong.
|
||||||
|
|
||||||
|
hi host interface
|
||||||
|
|
||||||
|
21-May-13 RLA New file
|
||||||
|
|
||||||
|
The host interface is one of the BBN engineered devices unique to the
|
||||||
|
ARPAnet IMP. This is the famous "1822" card which connected each IMP to a
|
||||||
|
host computer - a DECSYSTEM-10, an SDS Sigma 7, an IBM 360/90, a CDC6600,
|
||||||
|
or any one of many other ARPAnet hosts. The idea is to simulate this
|
||||||
|
interface by using a TCP/UDP connection to another simh instance emulating
|
||||||
|
the host machine and running the ARPAnet host software.
|
||||||
|
|
||||||
|
Presently the details of the host interface card are not well known, and
|
||||||
|
this implementation is simply a place holder. It's enough to allow the IMP
|
||||||
|
software to run, but not actually to communicate with a host. The IMP simply
|
||||||
|
believes that all the attached hosts are down at the moment.
|
||||||
|
|
||||||
|
Host interface state is maintained in a set of position and state variables:
|
||||||
|
|
||||||
|
Host state is maintained in the following variables -
|
||||||
|
|
||||||
|
TBA TBA
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
IMPLEMENT THIS MODULE!!!
|
||||||
|
*/
|
||||||
|
#ifdef VM_IMPTIP
|
||||||
|
#include "h316_defs.h" // H316 emulator definitions
|
||||||
|
#include "h316_imp.h" // ARPAnet IMP/TIP definitions
|
||||||
|
|
||||||
|
// Externals from other parts of simh ...
|
||||||
|
extern uint16 dev_ext_int, dev_ext_enb; // current IRQ and IEN bit vectors
|
||||||
|
extern int32 PC; // current PC (for debug messages)
|
||||||
|
extern int32 stop_inst; // needed by IOBADFNC()
|
||||||
|
extern int32 sim_switches; // option bitmap for ATTACH/DETACH
|
||||||
|
extern uint16 M[]; // main memory (for DMC access)
|
||||||
|
|
||||||
|
// Forward declarations ...
|
||||||
|
int32 hi_io (uint16 line, int32 inst, int32 fnc, int32 dat, int32 dev);
|
||||||
|
int32 hi1_io (int32 inst, int32 fnc, int32 dat, int32 dev);
|
||||||
|
int32 hi2_io (int32 inst, int32 fnc, int32 dat, int32 dev);
|
||||||
|
int32 hi3_io (int32 inst, int32 fnc, int32 dat, int32 dev);
|
||||||
|
int32 hi4_io (int32 inst, int32 fnc, int32 dat, int32 dev);
|
||||||
|
t_stat hi_service (UNIT *uptr);
|
||||||
|
t_stat hi_reset (DEVICE *dptr);
|
||||||
|
t_stat hi_attach (UNIT *uptr, char *cptr);
|
||||||
|
t_stat hi_detach (UNIT *uptr);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
////////////////////// D A T A S T R U C T U R E S //////////////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Host interface data blocks ...
|
||||||
|
// The HIDB is our own internal data structure for each host. It keeps data
|
||||||
|
// about the TCP/IP connection, buffers, etc.
|
||||||
|
#define HI_HIDB(N) {0, 0, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE}
|
||||||
|
HIDB hi1_db = HI_HIDB(1), hi2_db = HI_HIDB(2);
|
||||||
|
HIDB hi3_db = HI_HIDB(3), hi4_db = HI_HIDB(4);
|
||||||
|
|
||||||
|
// Host Device Information Blocks ...
|
||||||
|
// The DIB is the structure simh uses to keep track of the device IO address
|
||||||
|
// and IO service routine. It can also hold the DMC channel, but we don't use
|
||||||
|
// that because it's unit specific.
|
||||||
|
#define HI_DIB(N) {HI##N, 1, HI##N##_RX_DMC, HI##N##_TX_DMC, \
|
||||||
|
INT_V_HI##N##RX, INT_V_HI##N##TX, &hi##N##_io, N}
|
||||||
|
DIB hi1_dib = HI_DIB(1), hi2_dib = HI_DIB(2);
|
||||||
|
DIB hi3_dib = HI_DIB(3), hi4_dib = HI_DIB(4);
|
||||||
|
|
||||||
|
// Host Device Unit data ...
|
||||||
|
// simh uses the unit data block primarily to schedule device service events.
|
||||||
|
// The UNIT data also contains four "user" fields which devices can reuse for
|
||||||
|
// any purpose and we take advantage of that to store the line number.
|
||||||
|
#define hline u3 // our host line number is stored in user data 3
|
||||||
|
#define HI_UNIT(N) {UDATA (&hi_service, UNIT_ATTABLE, 0), HI_POLL_DELAY, N, 0, 0, 0}
|
||||||
|
UNIT hi1_unit = HI_UNIT(1), hi2_unit = HI_UNIT(2);
|
||||||
|
UNIT hi3_unit = HI_UNIT(3), hi4_unit = HI_UNIT(4);
|
||||||
|
|
||||||
|
// Host Device Registers ...
|
||||||
|
// These are the simh device "registers" - they c can be viewed with the
|
||||||
|
// "EXAMINE HIxn STATE" command and modified by "DEPOSIT HIxn ..."
|
||||||
|
#define HI_REG(N) { \
|
||||||
|
{ DRDATA (POLL, hi##N##_unit.wait, 24), REG_NZ + PV_LEFT }, \
|
||||||
|
{ FLDATA (RXIRQ, dev_ext_int, INT_V_HI##N##RX-INT_V_EXTD) }, \
|
||||||
|
{ FLDATA (RXIEN, dev_ext_enb, INT_V_HI##N##RX-INT_V_EXTD) }, \
|
||||||
|
{ DRDATA (RXTOT, hi##N##_db.rxtotal,32), REG_RO + PV_LEFT }, \
|
||||||
|
{ FLDATA (TXIRQ, dev_ext_int, INT_V_HI##N##TX-INT_V_EXTD) }, \
|
||||||
|
{ FLDATA (TXIEN, dev_ext_enb, INT_V_HI##N##TX-INT_V_EXTD) }, \
|
||||||
|
{ DRDATA (TXTOT, hi##N##_db.txtotal,32), REG_RO + PV_LEFT }, \
|
||||||
|
{ FLDATA (LLOOP, hi##N##_db.lloop, 0), PV_RZRO }, \
|
||||||
|
{ FLDATA (ERROR, hi##N##_db.error, 0), PV_RZRO }, \
|
||||||
|
{ FLDATA (READY, hi##N##_db.ready, 0), PV_RZRO }, \
|
||||||
|
{ FLDATA (FULL, hi##N##_db.full , 0), PV_RZRO }, \
|
||||||
|
{ NULL } \
|
||||||
|
}
|
||||||
|
REG hi1_reg[] = HI_REG(1), hi2_reg[] = HI_REG(2);
|
||||||
|
REG hi3_reg[] = HI_REG(3), hi4_reg[] = HI_REG(4);
|
||||||
|
|
||||||
|
// Host Device Modifiers ...
|
||||||
|
// These are the modifiers simh uses for the "SET MIxn" and "SHOW MIx" commands.
|
||||||
|
#define HI_MOD(N) { \
|
||||||
|
{ 0 } \
|
||||||
|
}
|
||||||
|
MTAB hi1_mod[] = HI_MOD(1), hi2_mod[] = HI_MOD(2);
|
||||||
|
MTAB hi3_mod[] = HI_MOD(3), hi4_mod[] = HI_MOD(4);
|
||||||
|
|
||||||
|
// Debug modifiers for "SET HIn DEBUG = xxx" ...
|
||||||
|
DEBTAB hi_debug[] = {
|
||||||
|
{"WARN", IMP_DBG_WARN}, // print warnings that would otherwise be suppressed
|
||||||
|
{"UDP", IMP_DBG_UDP}, // print all UDP messages sent and received
|
||||||
|
{"IO", IMP_DBG_IOT}, // print all program I/O instructions
|
||||||
|
{0}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Host Device data ...
|
||||||
|
// This is the primary simh structure that defines each device - it gives the
|
||||||
|
// plain text name, the addresses of the unit, register and modifier tables, and
|
||||||
|
// the addresses of all action routines (e.g. attach, reset, etc).
|
||||||
|
#define HI_DEV(HI,N,F) { \
|
||||||
|
#HI, &hi##N##_unit, hi##N##_reg, hi##N##_mod, \
|
||||||
|
1, 10, 31, 1, 8, 8, \
|
||||||
|
NULL, NULL, &hi_reset, NULL, &hi_attach, &hi_detach, \
|
||||||
|
&hi##N##_dib, DEV_DISABLE|DEV_DEBUG|(F), 0, hi_debug, NULL, NULL \
|
||||||
|
}
|
||||||
|
DEVICE hi1_dev = HI_DEV(HI1,1,DEV_DIS), hi2_dev = HI_DEV(HI2,2,DEV_DIS);
|
||||||
|
DEVICE hi3_dev = HI_DEV(HI3,3,DEV_DIS), hi4_dev = HI_DEV(HI4,4,DEV_DIS);
|
||||||
|
|
||||||
|
// Host Tables ...
|
||||||
|
// These tables make it easy to locate the data associated with any host.
|
||||||
|
DEVICE *const hi_devices[HI_NUM] = {&hi1_dev, &hi2_dev, &hi3_dev, &hi4_dev };
|
||||||
|
UNIT *const hi_units [HI_NUM] = {&hi1_unit, &hi2_unit, &hi3_unit, &hi4_unit};
|
||||||
|
DIB *const hi_dibs [HI_NUM] = {&hi1_dib, &hi2_dib, &hi3_dib, &hi4_dib };
|
||||||
|
HIDB *const hi_hidbs [HI_NUM] = {&hi1_db, &hi2_db, &hi3_db, &hi4_db };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
////////////////// L O W L E V E L F U N C T I O N S ///////////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Find a pointer to the DEVICE, UNIT, DIB or HIDB given the host number ...
|
||||||
|
#define PDEVICE(h) hi_devices[(h)-1]
|
||||||
|
#define PUNIT(h) hi_units[(h)-1]
|
||||||
|
#define PDIB(h) hi_dibs[(h)-1]
|
||||||
|
#define PHIDB(h) hi_hidbs[(h)-1]
|
||||||
|
|
||||||
|
// These macros set and clear the interrupt request and enable flags ...
|
||||||
|
#define SET_RX_IRQ(h) SET_EXT_INT((1u << (PDIB(h)->rxint - INT_V_EXTD)))
|
||||||
|
#define SET_TX_IRQ(h) SET_EXT_INT((1u << (PDIB(h)->txint - INT_V_EXTD)))
|
||||||
|
#define CLR_RX_IRQ(h) CLR_EXT_INT((1u << (PDIB(h)->rxint - INT_V_EXTD)))
|
||||||
|
#define CLR_TX_IRQ(h) CLR_EXT_INT((1u << (PDIB(h)->txint - INT_V_EXTD)))
|
||||||
|
#define CLR_RX_IEN(h) CLR_EXT_ENB((1u << (PDIB(h)->rxint - INT_V_EXTD)))
|
||||||
|
#define CLR_TX_IEN(h) CLR_EXT_ENB((1u << (PDIB(h)->txint - INT_V_EXTD)))
|
||||||
|
|
||||||
|
// TRUE if the host has the specified debugging output enabled ...
|
||||||
|
#define ISHDBG(l,f) ((PDEVICE(l)->dctrl & (f)) != 0)
|
||||||
|
|
||||||
|
// Reset receiver (clear flags AND initialize all data) ...
|
||||||
|
void hi_reset_rx (uint16 host)
|
||||||
|
{
|
||||||
|
PHIDB(host)->lloop = PHIDB(host)->error = PHIDB(host)->enabled = FALSE;
|
||||||
|
PHIDB(host)->ready = PHIDB(host)->eom = FALSE;
|
||||||
|
PHIDB(host)->rxtotal = 0;
|
||||||
|
CLR_RX_IRQ(host); CLR_RX_IEN(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset transmitter (clear flags AND initialize all data) ...
|
||||||
|
void hi_reset_tx (uint16 host)
|
||||||
|
{
|
||||||
|
PHIDB(host)->lloop = PHIDB(host)->enabled = PHIDB(host)->full = FALSE;
|
||||||
|
PHIDB(host)->txtotal = 0;
|
||||||
|
CLR_TX_IRQ(host); CLR_TX_IEN(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//////////// I / O I N S T R U C T I O N E M U L A T I O N /////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Host specific I/O routines ...
|
||||||
|
int32 hi1_io(int32 inst, int32 fnc, int32 dat, int32 dev) {return hi_io(1, inst, fnc, dat, dev);}
|
||||||
|
int32 hi2_io(int32 inst, int32 fnc, int32 dat, int32 dev) {return hi_io(2, inst, fnc, dat, dev);}
|
||||||
|
int32 hi3_io(int32 inst, int32 fnc, int32 dat, int32 dev) {return hi_io(3, inst, fnc, dat, dev);}
|
||||||
|
int32 hi4_io(int32 inst, int32 fnc, int32 dat, int32 dev) {return hi_io(4, inst, fnc, dat, dev);}
|
||||||
|
|
||||||
|
// Common I/O simulation routine ...
|
||||||
|
int32 hi_io (uint16 host, int32 inst, int32 fnc, int32 dat, int32 dev)
|
||||||
|
{
|
||||||
|
// This routine is invoked by the CPU module whenever the code executes any
|
||||||
|
// I/O instruction (OCP, SKS, INA or OTA) with one of our modem's device
|
||||||
|
// address.
|
||||||
|
|
||||||
|
// OCP (output control pulse) initiates various modem operations ...
|
||||||
|
if (inst == ioOCP) {
|
||||||
|
switch (fnc) {
|
||||||
|
case 000:
|
||||||
|
// HnROUT - start regular host output ...
|
||||||
|
sim_debug(IMP_DBG_IOT, PDEVICE(host), "start regular output (PC=%06o)\n", PC-1);
|
||||||
|
return dat;
|
||||||
|
case 001:
|
||||||
|
// HnIN - start host input ...
|
||||||
|
sim_debug(IMP_DBG_IOT, PDEVICE(host), "start input (PC=%06o)\n", PC-1);
|
||||||
|
return dat;
|
||||||
|
case 002:
|
||||||
|
// HnFOUT - start final host output ...
|
||||||
|
sim_debug(IMP_DBG_IOT, PDEVICE(host), "start final output (PC=%06o)\n", PC-1);
|
||||||
|
return dat;
|
||||||
|
case 003:
|
||||||
|
// HnXP - cross patch ...
|
||||||
|
sim_debug(IMP_DBG_IOT, PDEVICE(host), "enable cross patch (PC=%06o)\n", PC-1);
|
||||||
|
return dat;
|
||||||
|
case 004:
|
||||||
|
// HnUNXP - un-cross patch ...
|
||||||
|
sim_debug(IMP_DBG_IOT, PDEVICE(host), "disable cross patch (PC=%06o)\n", PC-1);
|
||||||
|
return dat;
|
||||||
|
case 005:
|
||||||
|
// HnENAB - enable ...
|
||||||
|
sim_debug(IMP_DBG_IOT, PDEVICE(host), "enable host (PC=%06o)\n", PC-1);
|
||||||
|
return dat;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SKS (skip) tests various modem conditions ...
|
||||||
|
} else if (inst == ioSKS) {
|
||||||
|
switch (fnc) {
|
||||||
|
case 000:
|
||||||
|
// HnERR - skip on host error ...
|
||||||
|
sim_debug(IMP_DBG_IOT, PDEVICE(host), "skip on error (PC=%06o %s)\n", PC-1, "NOSKIP");
|
||||||
|
return dat;
|
||||||
|
case 001:
|
||||||
|
// HnRDY - skip on host ready ...
|
||||||
|
sim_debug(IMP_DBG_IOT, PDEVICE(host), "skip on ready (PC=%06o %s)\n", PC-1, "NOSKIP");
|
||||||
|
return dat;
|
||||||
|
case 002:
|
||||||
|
// HnEOM - skip on end of message ...
|
||||||
|
sim_debug(IMP_DBG_IOT, PDEVICE(host), "skip on end of message (PC=%06o %s)\n", PC-1, "NOSKIP");
|
||||||
|
return dat;
|
||||||
|
case 005:
|
||||||
|
// HnFULL - skip on host buffer full ...
|
||||||
|
sim_debug(IMP_DBG_IOT, PDEVICE(host), "skip on buffer full (PC=%06o %s)\n", PC-1, "NOSKIP");
|
||||||
|
return dat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anything else is an error...
|
||||||
|
sim_debug(IMP_DBG_WARN, PDEVICE(host), "UNIMPLEMENTED I/O (PC=%06o, instruction=%o, function=%02o)\n", PC-1, inst, fnc);
|
||||||
|
return IOBADFNC(dat);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/////////////////// H O S T E V E N T S E R V I C E ////////////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Unit service ...
|
||||||
|
t_stat hi_service (UNIT *uptr)
|
||||||
|
{
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/////////////// D E V I C E A C T I O N C O M M A N D S ////////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Reset routine ...
|
||||||
|
t_stat hi_reset (DEVICE *dptr)
|
||||||
|
{
|
||||||
|
// simh calls this routine for the RESET command ...
|
||||||
|
UNIT *uptr = dptr->units;
|
||||||
|
uint16 host= uptr->hline;
|
||||||
|
hi_reset_rx(host); hi_reset_tx(host);
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach (connect) ...
|
||||||
|
t_stat hi_attach (UNIT *uptr, char *cptr)
|
||||||
|
{
|
||||||
|
// simh calls this routine for (what else?) the ATTACH command.
|
||||||
|
uint16 host = uptr->hline;
|
||||||
|
fprintf(stderr,"HI%d - host interface not yet implemented\n", host);
|
||||||
|
return SCPE_IERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detach (connect) ...
|
||||||
|
t_stat hi_detach (UNIT *uptr)
|
||||||
|
{
|
||||||
|
// simh calls this routine for (you guessed it!) the DETACH command.
|
||||||
|
uint16 host = uptr->hline;
|
||||||
|
fprintf(stderr,"HI%d - host interface not yet implemented\n", host);
|
||||||
|
return SCPE_IERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif // #ifdef VM_IMPTIP from the very top
|
192
H316/h316_imp.c
Normal file
192
H316/h316_imp.c
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
/* h316_imp.c- BBN ARPAnet IMP/TIP Specific Hardware
|
||||||
|
Based on the SIMH simulator package written by Robert M Supnik.
|
||||||
|
|
||||||
|
Copyright (c) 2013 Robert Armstrong, bob@jfcl.com.
|
||||||
|
|
||||||
|
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
|
||||||
|
ROBERT ARMSTRONG 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 Robert Armstrong shall not be
|
||||||
|
used in advertising or otherwise to promote the sale, use or other dealings
|
||||||
|
in this Software without prior written authorization from Robert Armstrong.
|
||||||
|
|
||||||
|
tks task switch device
|
||||||
|
mlc multiline controller (aka TIP)
|
||||||
|
|
||||||
|
21-May-13 RLA New file
|
||||||
|
|
||||||
|
OVERVIEW
|
||||||
|
|
||||||
|
This module implements the IMP pseudo device - this hack takes care of two
|
||||||
|
custom devices in the IMP hardware - device 041, which implements task
|
||||||
|
switching and the RDIMPN instruction, and device 42, which implements the
|
||||||
|
AMIMLC ("am I a multiline controller") instruction. This module also contains
|
||||||
|
a few miscellaneous routines which are used by the IMP support in general.
|
||||||
|
|
||||||
|
IMP state is maintained in a set of state variables:
|
||||||
|
|
||||||
|
MLC always zero (TIP flag)
|
||||||
|
IEN task interrupt enabled
|
||||||
|
IRQ task interrupt pending
|
||||||
|
|
||||||
|
TODO
|
||||||
|
*/
|
||||||
|
#ifdef VM_IMPTIP
|
||||||
|
#include "h316_defs.h" // H316 emulator definitions
|
||||||
|
#include "h316_imp.h" // ARPAnet IMP/TIP definitions
|
||||||
|
|
||||||
|
// Locals ...
|
||||||
|
uint16 imp_station = IMP_STATION; // IMP number (or address)
|
||||||
|
uint16 imp_ismlc = 0; // 1 for MLC (not yet implemented!)
|
||||||
|
|
||||||
|
// Externals from other parts of simh ...
|
||||||
|
extern uint16 dev_ext_int, dev_ext_enb; // current IRQ and IEN bit vectors
|
||||||
|
extern int32 PC; // current PC (for debug messages)
|
||||||
|
extern int32 stop_inst; // needed by IOBADFNC()
|
||||||
|
|
||||||
|
// Forward declarations ...
|
||||||
|
int32 imp_io (int32 inst, int32 fnc, int32 dat, int32 dev);
|
||||||
|
t_stat imp_service (UNIT *uptr);
|
||||||
|
t_stat imp_reset (DEVICE *dptr);
|
||||||
|
t_stat imp_show_station (FILE *st, UNIT *uptr, int32 val, void *dp);
|
||||||
|
t_stat io_show_int (FILE *st, UNIT *uptr, int32 val, void *dp);
|
||||||
|
t_stat imp_set_station (UNIT *uptr, int32 val, char *cptr, void *dp);
|
||||||
|
t_stat io_set_int (UNIT *uptr, int32 val, char *cptr, void *dp);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
////////////////////// D A T A S T R U C T U R E S //////////////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// IMP device information block ...
|
||||||
|
DIB imp_dib = { IMP, 2, IOBUS, IOBUS, INT_V_TASK, INT_V_NONE, &imp_io, 0 };
|
||||||
|
|
||||||
|
// IMP unit data (we have only one!) ...
|
||||||
|
UNIT imp_unit = { UDATA (&imp_service, 0, 0) };
|
||||||
|
|
||||||
|
// IMP device registers (for "EXAMINE IMP STATE") ...
|
||||||
|
REG imp_reg[] = {
|
||||||
|
{ FLDATA (MLC, imp_ismlc, 0), REG_RO },
|
||||||
|
{ FLDATA (IEN, dev_ext_enb, INT_V_TASK-INT_V_EXTD) },
|
||||||
|
{ FLDATA (IRQ, dev_ext_int, INT_V_TASK-INT_V_EXTD) },
|
||||||
|
{ NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
// IMP device modifiers (for "SET/SHOW IMP xxx") ...
|
||||||
|
MTAB imp_mod[] = {
|
||||||
|
{ MTAB_XTD|MTAB_VDV, 0, "NUM", "NUM", &imp_set_station, &imp_show_station, NULL },
|
||||||
|
{ 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
// IMP debugging flags (for "SET IMP DEBUG=xxx") ...
|
||||||
|
DEBTAB imp_debug[] = {
|
||||||
|
{"WARN", IMP_DBG_WARN},
|
||||||
|
{"IO", IMP_DBG_IOT},
|
||||||
|
{0}
|
||||||
|
};
|
||||||
|
|
||||||
|
// And finally tie it all together ...
|
||||||
|
DEVICE imp_dev = {
|
||||||
|
"IMP", &imp_unit, imp_reg, imp_mod,
|
||||||
|
1, 0, 0, 0, 0, 0,
|
||||||
|
NULL, NULL, &imp_reset, NULL, NULL, NULL,
|
||||||
|
&imp_dib, DEV_DIS|DEV_DISABLE|DEV_DEBUG, 0, imp_debug, NULL, NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
////////// I M P I / O A N D S E R V I C E R O U T I N E S //////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Set and clear the TASK IRQ and IEN ...
|
||||||
|
#define SET_TASK_IRQ() SET_EXT_INT((1u << (imp_dib.inum - INT_V_EXTD)))
|
||||||
|
#define CLR_TASK_IRQ() CLR_EXT_INT((1u << (imp_dib.inum - INT_V_EXTD)))
|
||||||
|
#define CLR_TASK_IEN() CLR_EXT_ENB((1u << (imp_dib.inum - INT_V_EXTD)))
|
||||||
|
|
||||||
|
// IMP I/O routine ...
|
||||||
|
int32 imp_io (int32 inst, int32 fnc, int32 dat, int32 dev)
|
||||||
|
{
|
||||||
|
if (dev == IMP) {
|
||||||
|
if ((inst == ioOCP) && (fnc == 000)) {
|
||||||
|
// TASK - just set the task interrupt request bit ...
|
||||||
|
sim_debug(IMP_DBG_IOT, &imp_dev, "request task interrupt (PC=%06o)\n", PC-1);
|
||||||
|
SET_TASK_IRQ(); return dat;
|
||||||
|
} else if ((inst == ioINA) && ((fnc == 010) || (fnc == 000))) {
|
||||||
|
// RDIMPN - return the IMP address and always skip ...
|
||||||
|
sim_debug(IMP_DBG_IOT, &imp_dev, "read address (PC=%06o)\n", PC-1);
|
||||||
|
return IOSKIP(imp_station);
|
||||||
|
}
|
||||||
|
} else if (dev == IMP+1) {
|
||||||
|
if ((inst == ioSKS) && (fnc == 000)) {
|
||||||
|
// AMIMLC - skip if this machine is an MLC ...
|
||||||
|
sim_debug(IMP_DBG_IOT, &imp_dev, "skip on MLC (PC=%06o %s)\n", PC-1, imp_ismlc ? "SKIP" : "NOSKIP");
|
||||||
|
if (imp_ismlc != 0) return IOSKIP(dat); else return dat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anything else is an error...
|
||||||
|
sim_debug(IMP_DBG_WARN, &imp_dev, "UNIMPLEMENTED I/O (PC=%06o, instruction=%o, function=%02o)\n", PC-1, inst, fnc);
|
||||||
|
return IOBADFNC(dat);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unit service ...
|
||||||
|
t_stat imp_service (UNIT *uptr)
|
||||||
|
{
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/////////////// D E V I C E A C T I O N C O M M A N D S ////////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Reset routine ...
|
||||||
|
t_stat imp_reset (DEVICE *dptr)
|
||||||
|
{
|
||||||
|
// The simh RESET command clears both the interrupt request and enable...
|
||||||
|
CLR_TASK_IRQ(); CLR_TASK_IEN();
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
///////// D E V I C E S E T A N D S H O W C O M M A N D S //////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Show the station number ...
|
||||||
|
t_stat imp_show_station (FILE *st, UNIT *uptr, int32 val, void *desc)
|
||||||
|
{
|
||||||
|
fprintf(st,"station=%d", imp_station);
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the station number ...
|
||||||
|
t_stat imp_set_station (UNIT *uptr, int32 val, char *cptr, void *dp)
|
||||||
|
{
|
||||||
|
uint32 newnum; t_stat sts;
|
||||||
|
if (cptr == NULL) return SCPE_ARG;
|
||||||
|
newnum = get_uint (cptr, 10, 9999, &sts);
|
||||||
|
if (newnum == 0) return SCPE_ARG;
|
||||||
|
imp_station = newnum;
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // #ifdef VM_IMPTIP from the very top
|
199
H316/h316_imp.h
Normal file
199
H316/h316_imp.h
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
/* h316_imp.h- BBN ARPAnet IMP/TIP Definitions
|
||||||
|
|
||||||
|
Copyright (c) 2013, Robert Armstrong, bob@jfcl.com
|
||||||
|
|
||||||
|
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
|
||||||
|
ROBERT ARMSTRONG 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 Robert Armstrong shall not be
|
||||||
|
used in advertising or otherwise to promote the sale, use or other dealings
|
||||||
|
in this Software without prior written authorization from Robert Armstrong.
|
||||||
|
|
||||||
|
21-May-13 RLA New file.
|
||||||
|
*/
|
||||||
|
#ifdef VM_IMPTIP
|
||||||
|
#ifndef H316_IMP_H_
|
||||||
|
#define H316_IMP_H_ 0
|
||||||
|
#include "sim_defs.h"
|
||||||
|
|
||||||
|
// Common modem and host parameters ...
|
||||||
|
#define MI_NUM 5 // number of modem interfaces
|
||||||
|
#define HI_NUM 4 // number of host interfaces
|
||||||
|
#define MI_MAX_MSG 256 // longest possible modem message (words!)
|
||||||
|
#define HI_MAX_MSG 256 // longest possible host message (words!)
|
||||||
|
#define MI_RXPOLL 100 // RX polling delay for UDP messages
|
||||||
|
#define MI_TXBPS 56000UL // default TX speed (bits per second)
|
||||||
|
#define HI_POLL_DELAY 1000 // polling delay for messages
|
||||||
|
|
||||||
|
// Modem interface, line #1 ...
|
||||||
|
#define MI1 071 // IO address for modem interface #1
|
||||||
|
#define MI1_RX_DMC (DMC1-1+ 1) // DMC channel for modem 1 receive
|
||||||
|
#define MI1_TX_DMC (DMC1-1+ 6) // DMC channel for modem 1 transmit
|
||||||
|
#define INT_V_MI1RX (INT_V_EXTD+15) // modem 1 receive interrupt
|
||||||
|
#define INT_V_MI1TX (INT_V_EXTD+10) // modem 1 transmit interrupt
|
||||||
|
|
||||||
|
// Modem interface, line #2 ...
|
||||||
|
#define MI2 072 // IO address for modem interface #2
|
||||||
|
#define MI2_RX_DMC (DMC1-1+ 2) // DMC channel for modem 2 receive
|
||||||
|
#define MI2_TX_DMC (DMC1-1+ 7) // DMC channel for modem 2 transmit
|
||||||
|
#define INT_V_MI2RX (INT_V_EXTD+14) // modem 2 receive interrupt
|
||||||
|
#define INT_V_MI2TX (INT_V_EXTD+ 9) // modem 2 transmit interrupt
|
||||||
|
|
||||||
|
// Modem interface, line #3 ...
|
||||||
|
#define MI3 073 // IO address for modem interface #3
|
||||||
|
#define MI3_RX_DMC (DMC1-1+ 3) // DMC channel for modem 3 receive
|
||||||
|
#define MI3_TX_DMC (DMC1-1+ 8) // DMC channel for modem 3 transmit
|
||||||
|
#define INT_V_MI3RX (INT_V_EXTD+13) // modem 3 receive interrupt
|
||||||
|
#define INT_V_MI3TX (INT_V_EXTD+ 8) // modem 3 transmit interrupt
|
||||||
|
|
||||||
|
// Modem interface, line #4 ...
|
||||||
|
#define MI4 074 // IO address for modem interface #4
|
||||||
|
#define MI4_RX_DMC (DMC1-1+ 4) // DMC channel for modem 4 receive
|
||||||
|
#define MI4_TX_DMC (DMC1-1+ 9) // DMC channel for modem 4 transmit
|
||||||
|
#define INT_V_MI4RX (INT_V_EXTD+12) // modem 4 receive interrupt
|
||||||
|
#define INT_V_MI4TX (INT_V_EXTD+ 7) // modem 4 transmit interrupt
|
||||||
|
|
||||||
|
// Modem interface, line #5 ...
|
||||||
|
#define MI5 075 // IO address for modem interface #5
|
||||||
|
#define MI5_RX_DMC (DMC1-1+ 5) // DMC channel for modem 5 receive
|
||||||
|
#define MI5_TX_DMC (DMC1-1+10) // DMC channel for modem 5 transmit
|
||||||
|
#define INT_V_MI5RX (INT_V_EXTD+11) // modem 5 receive interrupt
|
||||||
|
#define INT_V_MI5TX (INT_V_EXTD+ 6) // modem 5 transmit interrupt
|
||||||
|
|
||||||
|
// Host interface, line #1 ...
|
||||||
|
#define HI1 070 // device address for host interface #1
|
||||||
|
#define HI1_RX_DMC (DMC1+13-1) // DMC channel for host 1 receive
|
||||||
|
#define HI1_TX_DMC (DMC1+11-1) // DMC channel for host 1 transmit
|
||||||
|
#define INT_V_HI1RX (INT_V_EXTD+ 3) // host 1 receive interrupt
|
||||||
|
#define INT_V_HI1TX (INT_V_EXTD+ 5) // host 1 transmit interrupt
|
||||||
|
|
||||||
|
// Host interface, line #2 ...
|
||||||
|
#define HI2 060 // device address for host interface #2
|
||||||
|
#define HI2_RX_DMC (DMC1-1+14) // DMC channel for host 2 receive
|
||||||
|
#define HI2_TX_DMC (DMC1-1+12) // DMC channel for host 2 transmit
|
||||||
|
#define INT_V_HI2RX (INT_V_EXTD+ 2) // host 2 receive interrupt
|
||||||
|
#define INT_V_HI2TX (INT_V_EXTD+ 4) // host 2 transmit interrupt
|
||||||
|
|
||||||
|
// Host interface, line #3 ...
|
||||||
|
#define HI3 051 // device address for host interface #3
|
||||||
|
#define HI3_RX_DMC (DMC1-1+16) // DMC channel for host 3 receive
|
||||||
|
#define HI3_TX_DMC (DMC1-1+15) // DMC channel for host 3 transmit
|
||||||
|
#define INT_V_HI3RX (INT_V_EXTD+ 6) // host 3 receive interrupt
|
||||||
|
#define INT_V_HI3TX (INT_V_EXTD+11) // host 3 transmit interrupt
|
||||||
|
|
||||||
|
// Host interface, line #4 ...
|
||||||
|
#define HI4 050 // device address for host interface #4
|
||||||
|
#define HI4_RX_DMC (DMC1-1+10) // DMC channel for host 4 receive
|
||||||
|
#define HI4_TX_DMC (DMC1-1+ 5) // DMC channel for host 4 transmit
|
||||||
|
#define INT_V_HI4RX (INT_V_EXTD+ 7) // host 4 receive interrupt
|
||||||
|
#define INT_V_HI4TX (INT_V_EXTD+12) // host 4 transmit interrupt
|
||||||
|
|
||||||
|
// IMP defaults ...
|
||||||
|
#define IMP 041 // IMP device IO address (41 & 42 actually!)
|
||||||
|
#define INT_V_TASK (INT_V_EXTD+ 0) // task switch interrupt number
|
||||||
|
#define IMP_STATION 1 // default station number
|
||||||
|
|
||||||
|
// RTC defaults ...
|
||||||
|
#define RTC 040 // real time clock IO address
|
||||||
|
#define INT_V_RTC (INT_V_EXTD+ 1) // RTC interrupt number
|
||||||
|
#define RTC_INTERVAL 20UL // default RTC interval (20us == 50kHz)
|
||||||
|
#define RTC_QUANTUM 32UL // default RTC quantum (32 ticks)
|
||||||
|
|
||||||
|
// WDT defaults ...
|
||||||
|
#define WDT 026 // watchdog timer IO address
|
||||||
|
#define WDT_VECTOR 000062 // WDT timeout vector
|
||||||
|
#define WDT_DELAY 0 // default WDT timeout (in milliseconds)
|
||||||
|
|
||||||
|
// Debugging flags ...
|
||||||
|
// In general, these bits are used as arguments for sim_debug(). Bits that
|
||||||
|
// begin with "IMP_DBG_xyz" are shared by more than one device (e.g. IMP_DBG_UDP)
|
||||||
|
// and must have unique bit assignments. Bits prefixed with a device name (e.g.
|
||||||
|
// "MI_DBG_xyz") apply to that device only.
|
||||||
|
#define IMP_DBG_WARN 0x0001 // all: print warnings
|
||||||
|
#define IMP_DBG_IOT 0x0002 // all: trace all program I/O instructions
|
||||||
|
#define IMP_DBG_UDP 0x0004 // all: trace UDP packets
|
||||||
|
#define MI_DBG_MSG 0x8000 // modem: decode and print all messages
|
||||||
|
#define WDT_DBG_LIGHTS 0x8000 // wdt: show status light changes
|
||||||
|
|
||||||
|
// Synonyms for DIB and UNIT fields ...
|
||||||
|
#define rxdmc chan // dib->rxdmc
|
||||||
|
#define txdmc chan2 // dib->txdmc
|
||||||
|
#define rxint inum // dib->rxint
|
||||||
|
#define txint inum2 // dib->txint
|
||||||
|
|
||||||
|
// Modem interface data block ....
|
||||||
|
// One of these is allocated to every modem interface to keep track of the
|
||||||
|
// current state, COM port, UDP connection , etc...
|
||||||
|
struct _MIDB {
|
||||||
|
// Receiver data ...
|
||||||
|
t_bool rxpending; // TRUE if a read is pending on this line
|
||||||
|
t_bool rxerror; // TRUE if any modem error detected
|
||||||
|
uint32 rxtotal; // total number of H316 words received
|
||||||
|
// Transmitter data ...
|
||||||
|
uint32 txtotal; // total number of H316 words transmitted
|
||||||
|
uint32 txdelay; // RTC ticks until TX done interrupt
|
||||||
|
// Other data ...
|
||||||
|
t_bool lloop; // line loop back enabled
|
||||||
|
t_bool iloop; // interface loop back enabled
|
||||||
|
int32 link; // h316_udp link number
|
||||||
|
uint32 bps; // simulated line speed or COM port baud rate
|
||||||
|
};
|
||||||
|
typedef struct _MIDB MIDB;
|
||||||
|
|
||||||
|
// Host interface data block ...
|
||||||
|
// One of these is allocated to every host interface ...
|
||||||
|
struct _HIDB {
|
||||||
|
// Receiver (HOST -> IMP) data ...
|
||||||
|
uint32 rxtotal; // total host messages received
|
||||||
|
// Transmitter (IMP -> HOST) data ...
|
||||||
|
uint32 txtotal; // total host messages sent
|
||||||
|
// Other data ...
|
||||||
|
t_bool lloop; // local loop back enabled
|
||||||
|
t_bool enabled; // TRUE if the host is enabled
|
||||||
|
t_bool error; // TRUE for any host error
|
||||||
|
t_bool ready; // TRUE if the host is ready
|
||||||
|
t_bool full; // TRUE if the host buffer is full
|
||||||
|
t_bool eom; // TRUE when end of message is reached
|
||||||
|
};
|
||||||
|
typedef struct _HIDB HIDB;
|
||||||
|
|
||||||
|
// I can't believe Bob managed to live without these, but I can't!
|
||||||
|
#ifndef LOBYTE // these are in winsock.h too!
|
||||||
|
#define LOBYTE(x) ((uint8) ( (x) & 0xFF))
|
||||||
|
#define HIBYTE(x) ((uint8) (((x) >> 8) & 0xFF))
|
||||||
|
#define MKWORD(h,l) ((uint16) ( (((h)&0xFF) << 8) | ((l)&0xFF) ))
|
||||||
|
#define LOWORD(x) ((uint16) ( (x) & 0xFFFF))
|
||||||
|
#define HIWORD(x) ((uint16) (((x) >> 16) & 0xFFFF))
|
||||||
|
#define MKLONG(h,l) ((uint32) ( (((h)&0xFFFF) << 16) | ((l)&0xFFFF) ))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Prototypes for the RTC module ...
|
||||||
|
// I really hate sharing things like this, but it's the only way to get the
|
||||||
|
// modem transmitter timing exactly right!
|
||||||
|
extern uint32 rtc_interval;
|
||||||
|
extern t_stat mi_tx_service (uint32 quantum);
|
||||||
|
|
||||||
|
// Prototypes for UDP modem/host interface emulation routines ...
|
||||||
|
#define NOLINK (-1)
|
||||||
|
t_stat udp_create (DEVICE *pdtr, char *premote, int32 *plink);
|
||||||
|
t_stat udp_release (DEVICE *dptr, int32 link);
|
||||||
|
t_stat udp_send (DEVICE *pdtr, int32 link, uint16 *pdata, uint16 count);
|
||||||
|
t_stat udp_send_self (DEVICE *dptr, int32 link, uint16 *pdata, uint16 count);
|
||||||
|
int32 udp_receive (DEVICE *dptr, int32 link, uint16 *pdata, uint16 maxbufg);
|
||||||
|
|
||||||
|
#endif // #ifndef _H316_IMP_H_
|
||||||
|
#endif // #ifdef VM_IMPTIP
|
|
@ -25,6 +25,7 @@
|
||||||
|
|
||||||
lpt line printer
|
lpt line printer
|
||||||
|
|
||||||
|
3-Jul-13 RLA compatibility changes for extended interrupts
|
||||||
09-Jun-07 RMS Fixed lost last print line (Theo Engel)
|
09-Jun-07 RMS Fixed lost last print line (Theo Engel)
|
||||||
19-Jan-06 RMS Added UNIT_TEXT flag
|
19-Jan-06 RMS Added UNIT_TEXT flag
|
||||||
03-Apr-06 RMS Fixed bug in blanks backscanning (Theo Engel)
|
03-Apr-06 RMS Fixed bug in blanks backscanning (Theo Engel)
|
||||||
|
@ -105,7 +106,7 @@ t_stat lpt_reset (DEVICE *dptr);
|
||||||
lpt_reg LPT register list
|
lpt_reg LPT register list
|
||||||
*/
|
*/
|
||||||
|
|
||||||
DIB lpt_dib = { LPT, IOBUS, 1, &lptio };
|
DIB lpt_dib = { LPT, 1, IOBUS, IOBUS, INT_V_LPT, INT_V_NONE, &lptio, 0 };
|
||||||
|
|
||||||
UNIT lpt_unit = { UDATA (&lpt_svc, UNIT_SEQ+UNIT_ATTABLE+UNIT_TEXT, 0) };
|
UNIT lpt_unit = { UDATA (&lpt_svc, UNIT_SEQ+UNIT_ATTABLE+UNIT_TEXT, 0) };
|
||||||
|
|
||||||
|
|
723
H316/h316_mi.c
Normal file
723
H316/h316_mi.c
Normal file
|
@ -0,0 +1,723 @@
|
||||||
|
/* h316_mi.c- BBN ARPAnet IMP/TIP Modem Interface
|
||||||
|
Based on the SIMH simulator package written by Robert M Supnik.
|
||||||
|
|
||||||
|
Copyright (c) 2013 Robert Armstrong, bob@jfcl.com.
|
||||||
|
|
||||||
|
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
|
||||||
|
ROBERT ARMSTRONG 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 Robert Armstrong shall not be
|
||||||
|
used in advertising or otherwise to promote the sale, use or other dealings
|
||||||
|
in this Software without prior written authorization from Robert Armstrong.
|
||||||
|
|
||||||
|
|
||||||
|
REVISION HISTORY
|
||||||
|
|
||||||
|
mi modem interface
|
||||||
|
|
||||||
|
21-May-13 RLA New file
|
||||||
|
|
||||||
|
|
||||||
|
OVERVIEW
|
||||||
|
|
||||||
|
The modem interface is one of the BBN engineered devices unique to the
|
||||||
|
ARPAnet IMP/TIP. The original hardware was a full duplex synchronous serial
|
||||||
|
line interface operating at 56k bps. The hardware was fairly smart and was
|
||||||
|
able to handle line synchronization (SYN), packet start (STX) and end (ETX),
|
||||||
|
and data escape (DLE) autonomously. Data is transferred directly to and
|
||||||
|
from H316 main memory using the DMC mechanism. The modem interface also
|
||||||
|
calculated a 24 bit "parity" (and by that I assume they meant some form of
|
||||||
|
CRC) value. This was automatically appended to the end of the transmitted
|
||||||
|
data and automatically verified by the receiving modem.
|
||||||
|
|
||||||
|
CONNECTIONS
|
||||||
|
|
||||||
|
This module provides two basic options for emulating the modem. Option 1
|
||||||
|
takes the data packets from H316 memory, wraps them in a UDP packet, and
|
||||||
|
sends them to another simh instance. The remote simh then unwraps the data
|
||||||
|
packet and loads it directly into H316 memory. In this instance,
|
||||||
|
synchronization, start of text/end of text, and data escapes are pointless
|
||||||
|
and are not used. The words are simply moved verbatim from one H316 to
|
||||||
|
another.
|
||||||
|
|
||||||
|
The other option is to logically connect the emulated modem to a physical
|
||||||
|
serial port on this PC. In that case we attempt to emulate the actions
|
||||||
|
of the original modem as closely as possible, including the synchronization,
|
||||||
|
start of text/end of text and data escape characters. Synchronization is
|
||||||
|
pointless on an asynchronous interface, of course, but we do it anyway in
|
||||||
|
the interest of compatability. We also attempt to calculate a 24 bit CRC
|
||||||
|
using (as best I can determine) the same algorithm as the original modems.
|
||||||
|
|
||||||
|
MULTIPLE INSTANCES
|
||||||
|
|
||||||
|
Each IMP can support up to five modem lines, and fitting this into simh
|
||||||
|
presents an architectural problem. The temptation is to treat all modems as
|
||||||
|
one device with multiple units (each unit corresponding to one line), but
|
||||||
|
that's a problem. The simh view of units is like a disk or tape - there's
|
||||||
|
a single controller that has one IO address, one interrupt and one DMA/DMC
|
||||||
|
channel. That controller then has multiple units attached to it that are
|
||||||
|
selected by bits in a controller register and all units share the same
|
||||||
|
IO, interrupt and DMA/DMC assignments.
|
||||||
|
|
||||||
|
The modems aren't like that at all - each of the five cards is completely
|
||||||
|
independent with its own distinct IO address, interrupt and DMC assignments.
|
||||||
|
It's analagous to five instances of the same controller installed in the
|
||||||
|
machine, but simh unfortunately has limited support for multiple instances
|
||||||
|
of the same controller. The few instances of prior art in simh that I can
|
||||||
|
find (e.g. xq, xqb on the PDP11/VAX) have just been done ad hoc by
|
||||||
|
duplicating all the device data. Rather than rewrite simh, that's the
|
||||||
|
approach I took here, even though with five instances it gets awkward.
|
||||||
|
|
||||||
|
POLLING AND SERVICE
|
||||||
|
|
||||||
|
The IMP software turns out to be extraordinarily sensitive to modem timing.
|
||||||
|
It actually goes to the trouble of measuring the effective line speed by
|
||||||
|
using the RTC to time how long it takes to send a message, and one thing that
|
||||||
|
especially annoys the IMP are variations in the effective line speed. They
|
||||||
|
had a lot of trouble with AT&T Long Lines back in the Old Days, and the IMP
|
||||||
|
has quite a bit of code to monitor line quality. Even fairly minor variations
|
||||||
|
in speed will cause it to mark the line as "down" and sent a trouble report
|
||||||
|
back to BBN.
|
||||||
|
|
||||||
|
To combat this, we actually let the RTC code time the transmitter interrupts.
|
||||||
|
When the IMP software does a "start modem output" OCP the entire packet will
|
||||||
|
be extracted from H316 memory and transmitted via UDP at that instant, BUT
|
||||||
|
the transmitter done interrupt will be deferred. The delay is computed from
|
||||||
|
the length of the packet and the simulated line speed, and then the RTC is
|
||||||
|
used to count down the interval. When the time expires, the interrupt request
|
||||||
|
is generated. It's unfortunate to have to couple the RTC and the modem in
|
||||||
|
this way, but since the IMP code is using the RTC to measure the line speed
|
||||||
|
AND since the RTC determines when the transmit done occurs, it guarantees
|
||||||
|
that the IMP always sees exactly the same delay.
|
||||||
|
|
||||||
|
The modem receiver is completely independent of the transmitter and is polled
|
||||||
|
by the usual simh event queue mechanism and mi_service() routine. When the
|
||||||
|
IMP code executes a "start modem input" OCP a read pending flag is set in the
|
||||||
|
modem status but nothing else occurs. Each poll checks the UDP socket for
|
||||||
|
incoming data and, if a packet was received AND a read operation is pending,
|
||||||
|
then the read completes that moment and the interrupt request is asserted.
|
||||||
|
The UDP socket is polled regardless of whether a read is pending and if data
|
||||||
|
arrives without a read then it's discarded. That's exactly what a real modem
|
||||||
|
would do.
|
||||||
|
|
||||||
|
ERROR HANDLING
|
||||||
|
|
||||||
|
Transmitter error handling is easy - fatal errors print a message and abort
|
||||||
|
the simulation, but any other errors are simply ignored. The IMP modems had
|
||||||
|
no kind of error dection on the transmitter side and no way to report them
|
||||||
|
anyway so we do the same. Any data packet associated with the error is just
|
||||||
|
discarded. In particular with both UDP and COM ports there's no way to tell
|
||||||
|
whether anybody is on the other end listening, so even packets that are
|
||||||
|
successfully transmitted may disappear into the ether. This isn't a problem
|
||||||
|
for the IMP - the real world was like that too and the IMP is able to handle
|
||||||
|
retransmitting packets without our help.
|
||||||
|
|
||||||
|
Receiver errors set the error flag in the modem status; this flag can be
|
||||||
|
tested and cleared by the "skip on modem error" SKS instruction. The only
|
||||||
|
receiver error that can be detected is buffer overrun (i.e. the sender's
|
||||||
|
message was longer than the receiver's buffer). With a serial connection
|
||||||
|
checksum errors are also possible, but those never occur with UDP.
|
||||||
|
|
||||||
|
Transmitting or receiving on a modem that's not attached isn't an error - it
|
||||||
|
simply does nothing. It's analogous to a modem with the phone line unplugged.
|
||||||
|
Hard I/O errors for UDP or COM ports print an error message and then detach
|
||||||
|
the modem connection. It's up to the user to interrupt the simulation and
|
||||||
|
reattach if he wants to try again.
|
||||||
|
|
||||||
|
STATE
|
||||||
|
|
||||||
|
Modem state is maintained in the following variables -
|
||||||
|
|
||||||
|
RXPOLL 24 receiver polling interval
|
||||||
|
RXPEND 1 an input operation is pending
|
||||||
|
RXERR 1 receiver error flag
|
||||||
|
RXIEN 1 receiver interrupt enable
|
||||||
|
RXIRQ 1 receiver interrupt request
|
||||||
|
RXTOT 32 count of total messages received
|
||||||
|
TXDLY 32 RTC ticks until TX done interrupt
|
||||||
|
TXIEN 1 transmitter interrupt enable
|
||||||
|
TXIRQ 1 transmitter interrupt request
|
||||||
|
TXTOT 32 count of total messages transmitted
|
||||||
|
LINKNO 32 link number for h316_udp module
|
||||||
|
BPS 32 simulated bps for UDP delay calculations
|
||||||
|
actual baud rate for physical COM ports
|
||||||
|
LLOOP 1 local loopback enabled
|
||||||
|
RLOOP 1 remote loopback enabled
|
||||||
|
|
||||||
|
Most of these values will be found in the Modem Information Data Block (aka
|
||||||
|
"MIDB") but a few are stored elsewhere (e.g. IRQ/IEN are in the CPU's dev_int
|
||||||
|
and dev_enb vectors).
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
Implement checksum handling
|
||||||
|
Implement local/remote loopback
|
||||||
|
*/
|
||||||
|
#ifdef VM_IMPTIP
|
||||||
|
#include "h316_defs.h" // H316 emulator definitions
|
||||||
|
#include "h316_imp.h" // ARPAnet IMP/TIP definitions
|
||||||
|
|
||||||
|
// Externals from other parts of simh ...
|
||||||
|
extern uint16 dev_ext_int, dev_ext_enb; // current IRQ and IEN bit vectors
|
||||||
|
extern int32 PC; // current PC (for debug messages)
|
||||||
|
extern int32 stop_inst; // needed by IOBADFNC()
|
||||||
|
extern int32 sim_switches; // option bitmap for ATTACH/DETACH
|
||||||
|
extern uint16 M[]; // main memory (for DMC access)
|
||||||
|
|
||||||
|
// Forward declarations ...
|
||||||
|
int32 mi_io (uint16 line, int32 inst, int32 fnc, int32 dat, int32 dev);
|
||||||
|
int32 mi1_io (int32 inst, int32 fnc, int32 dat, int32 dev);
|
||||||
|
int32 mi2_io (int32 inst, int32 fnc, int32 dat, int32 dev);
|
||||||
|
int32 mi3_io (int32 inst, int32 fnc, int32 dat, int32 dev);
|
||||||
|
int32 mi4_io (int32 inst, int32 fnc, int32 dat, int32 dev);
|
||||||
|
int32 mi5_io (int32 inst, int32 fnc, int32 dat, int32 dev);
|
||||||
|
t_stat mi_rx_service (UNIT *uptr);
|
||||||
|
void mi_rx_local (uint16 line, uint16 txnext, uint16 txcount);
|
||||||
|
t_stat mi_reset (DEVICE *dptr);
|
||||||
|
t_stat mi_attach (UNIT *uptr, char *cptr);
|
||||||
|
t_stat mi_detach (UNIT *uptr);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
////////////////////// D A T A S T R U C T U R E S //////////////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// simh requires several data structures for every device - a DIB, one or more
|
||||||
|
// UNITS, a modifier table, a register list, and a device definition. The sit-
|
||||||
|
// uation here is even more complicated because we have five identical modems to
|
||||||
|
// define, and so lots of clever macros are used to handle the repetition and
|
||||||
|
// save some typing.
|
||||||
|
|
||||||
|
// Modem Information Data Blocks ...
|
||||||
|
// The MIDB is our own internal data structure for each modem. It keeps data
|
||||||
|
// about the current state, COM port, UDP connection, etc.
|
||||||
|
#define MI_MIDB(N) {FALSE, FALSE, 0, 0, 0, FALSE, FALSE, NOLINK, MI_TXBPS}
|
||||||
|
MIDB mi1_db = MI_MIDB(1), mi2_db = MI_MIDB(2);
|
||||||
|
MIDB mi3_db = MI_MIDB(3), mi4_db = MI_MIDB(4);
|
||||||
|
MIDB mi5_db = MI_MIDB(5);
|
||||||
|
|
||||||
|
// Modem Device Information Blocks ...
|
||||||
|
// The DIB is the structure simh uses to keep track of the device IO address
|
||||||
|
// and IO service routine. It can also hold the DMC channel, but we don't use
|
||||||
|
// that because it's unit specific.
|
||||||
|
#define MI_DIB(N) {MI##N, 1, MI##N##_RX_DMC, MI##N##_TX_DMC, \
|
||||||
|
INT_V_MI##N##RX, INT_V_MI##N##TX, &mi##N##_io, N}
|
||||||
|
DIB mi1_dib = MI_DIB(1), mi2_dib = MI_DIB(2);
|
||||||
|
DIB mi3_dib = MI_DIB(3), mi4_dib = MI_DIB(4);
|
||||||
|
DIB mi5_dib = MI_DIB(5);
|
||||||
|
|
||||||
|
// Modem Device Unit data ...
|
||||||
|
// simh uses the unit data block primarily to schedule device service events.
|
||||||
|
#define mline u3 // our modem line number is stored in user data 3
|
||||||
|
#define MI_UNIT(N) {UDATA (&mi_rx_service, UNIT_ATTABLE, 0), MI_RXPOLL, N, 0, 0, 0}
|
||||||
|
UNIT mi1_unit = MI_UNIT(1), mi2_unit = MI_UNIT(2);
|
||||||
|
UNIT mi3_unit = MI_UNIT(3), mi4_unit = MI_UNIT(4);
|
||||||
|
UNIT mi5_unit = MI_UNIT(5);
|
||||||
|
|
||||||
|
// Modem Device Registers ...
|
||||||
|
// These are the simh device "registers" - they c can be viewed with the
|
||||||
|
// "EXAMINE MIn STATE" command and modified by "DEPOSIT MIn ..."
|
||||||
|
#define MI_REG(N) { \
|
||||||
|
{ DRDATA (RXPOLL, mi##N##_unit.wait, 24), REG_NZ + PV_LEFT }, \
|
||||||
|
{ FLDATA (RXPEND, mi##N##_db.rxpending, 0), REG_RO + PV_RZRO }, \
|
||||||
|
{ FLDATA (RXERR, mi##N##_db.rxerror, 0), PV_RZRO }, \
|
||||||
|
{ FLDATA (RXIEN, dev_ext_enb, INT_V_MI##N##RX-INT_V_EXTD) }, \
|
||||||
|
{ FLDATA (RXIRQ, dev_ext_int, INT_V_MI##N##RX-INT_V_EXTD) }, \
|
||||||
|
{ DRDATA (RXTOT, mi##N##_db.rxtotal, 32), REG_RO + PV_LEFT }, \
|
||||||
|
{ DRDATA (TXDLY, mi##N##_db.txdelay, 32), PV_LEFT }, \
|
||||||
|
{ FLDATA (TXIEN, dev_ext_enb, INT_V_MI##N##TX-INT_V_EXTD) }, \
|
||||||
|
{ FLDATA (TXIRQ, dev_ext_int, INT_V_MI##N##TX-INT_V_EXTD) }, \
|
||||||
|
{ DRDATA (TXTOT, mi##N##_db.txtotal, 32), REG_RO + PV_LEFT }, \
|
||||||
|
{ DRDATA (LINK, mi##N##_db.link, 32), REG_RO + PV_LEFT }, \
|
||||||
|
{ DRDATA (BPS, mi##N##_db.bps, 32), REG_NZ + PV_LEFT }, \
|
||||||
|
{ FLDATA (LLOOP, mi##N##_db.lloop, 0), PV_RZRO }, \
|
||||||
|
{ FLDATA (ILOOP, mi##N##_db.iloop, 0), PV_RZRO }, \
|
||||||
|
{ NULL } \
|
||||||
|
}
|
||||||
|
REG mi1_reg[] = MI_REG(1), mi2_reg[] = MI_REG(2);
|
||||||
|
REG mi3_reg[] = MI_REG(3), mi4_reg[] = MI_REG(4);
|
||||||
|
REG mi5_reg[] = MI_REG(5);
|
||||||
|
|
||||||
|
// Modem Device Modifiers ...
|
||||||
|
// These are the modifiers simh uses for the "SET MIn" and "SHOW MIn" commands.
|
||||||
|
#define MI_MOD(N) { \
|
||||||
|
{ 0 } \
|
||||||
|
}
|
||||||
|
MTAB mi1_mod[] = MI_MOD(1), mi2_mod[] = MI_MOD(2);
|
||||||
|
MTAB mi3_mod[] = MI_MOD(3), mi4_mod[] = MI_MOD(4);
|
||||||
|
MTAB mi5_mod[] = MI_MOD(5);
|
||||||
|
|
||||||
|
// Debug modifiers for "SET MIn DEBUG = xxx" ...
|
||||||
|
DEBTAB mi_debug[] = {
|
||||||
|
{"WARN", IMP_DBG_WARN}, // print warnings that would otherwise be suppressed
|
||||||
|
{"UDP", IMP_DBG_UDP}, // print all UDP messages sent and received
|
||||||
|
{"IO", IMP_DBG_IOT}, // print all program I/O instructions
|
||||||
|
{"MSG", MI_DBG_MSG}, // decode and print all messages
|
||||||
|
{0}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Modem Device data ...
|
||||||
|
// This is the primary simh structure that defines each device - it gives the
|
||||||
|
// plain text name, the addresses of the unit, register and modifier tables, and
|
||||||
|
// the addresses of all action routines (e.g. attach, reset, etc).
|
||||||
|
#define MI_DEV(MI,N,F) { \
|
||||||
|
#MI, &mi##N##_unit, mi##N##_reg, mi##N##_mod, \
|
||||||
|
1, 10, 31, 1, 8, 8, \
|
||||||
|
NULL, NULL, &mi_reset, NULL, &mi_attach, &mi_detach, \
|
||||||
|
&mi##N##_dib, DEV_DISABLE|DEV_DEBUG|(F), 0, mi_debug, NULL, NULL \
|
||||||
|
}
|
||||||
|
DEVICE mi1_dev = MI_DEV(MI1,1,DEV_DIS), mi2_dev = MI_DEV(MI2,2,DEV_DIS);
|
||||||
|
DEVICE mi3_dev = MI_DEV(MI3,3,DEV_DIS), mi4_dev = MI_DEV(MI4,4,DEV_DIS);
|
||||||
|
DEVICE mi5_dev = MI_DEV(MI5,5,DEV_DIS);
|
||||||
|
|
||||||
|
// Modem Tables ...
|
||||||
|
// These tables make it easy to locate the data associated with any line.
|
||||||
|
DEVICE *const mi_devices[MI_NUM] = {&mi1_dev, &mi2_dev, &mi3_dev, &mi4_dev, &mi5_dev };
|
||||||
|
UNIT *const mi_units [MI_NUM] = {&mi1_unit, &mi2_unit, &mi3_unit, &mi4_unit, &mi5_unit};
|
||||||
|
DIB *const mi_dibs [MI_NUM] = {&mi1_dib, &mi2_dib, &mi3_dib, &mi4_dib, &mi5_dib };
|
||||||
|
MIDB *const mi_midbs [MI_NUM] = {&mi1_db, &mi2_db, &mi3_db, &mi4_db, &mi5_db };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
////////////////// L O W L E V E L F U N C T I O N S ///////////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Find a pointer to the DEVICE, UNIT, DIB or MIDB given the line number ...
|
||||||
|
#define PDEVICE(l) mi_devices[(l)-1]
|
||||||
|
#define PUNIT(l) mi_units[(l)-1]
|
||||||
|
#define PDIB(l) mi_dibs[(l)-1]
|
||||||
|
#define PMIDB(l) mi_midbs[(l)-1]
|
||||||
|
|
||||||
|
// These macros set and clear the interrupt request and enable flags ...
|
||||||
|
#define SET_RX_IRQ(l) SET_EXT_INT((1u << (PDIB(l)->rxint - INT_V_EXTD)))
|
||||||
|
#define SET_TX_IRQ(l) SET_EXT_INT((1u << (PDIB(l)->txint - INT_V_EXTD)))
|
||||||
|
#define CLR_RX_IRQ(l) CLR_EXT_INT((1u << (PDIB(l)->rxint - INT_V_EXTD)))
|
||||||
|
#define CLR_TX_IRQ(l) CLR_EXT_INT((1u << (PDIB(l)->txint - INT_V_EXTD)))
|
||||||
|
#define CLR_RX_IEN(l) CLR_EXT_ENB((1u << (PDIB(l)->rxint - INT_V_EXTD)))
|
||||||
|
#define CLR_TX_IEN(l) CLR_EXT_ENB((1u << (PDIB(l)->txint - INT_V_EXTD)))
|
||||||
|
|
||||||
|
// TRUE if the line has the specified debugging output enabled ...
|
||||||
|
#define ISLDBG(l,f) ((PDEVICE(l)->dctrl & (f)) != 0)
|
||||||
|
|
||||||
|
// Reset receiver (clear flags AND initialize all data) ...
|
||||||
|
void mi_reset_rx (uint16 line)
|
||||||
|
{
|
||||||
|
PMIDB(line)->iloop = PMIDB(line)->lloop = FALSE;
|
||||||
|
PMIDB(line)->rxerror = PMIDB(line)->rxpending = FALSE;
|
||||||
|
PMIDB(line)->rxtotal = 0;
|
||||||
|
CLR_RX_IRQ(line); CLR_RX_IEN(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset transmitter (clear flags AND initialize all data) ...
|
||||||
|
void mi_reset_tx (uint16 line)
|
||||||
|
{
|
||||||
|
PMIDB(line)->iloop = PMIDB(line)->lloop = FALSE;
|
||||||
|
PMIDB(line)->txtotal = PMIDB(line)->txdelay = 0;
|
||||||
|
CLR_TX_IRQ(line); CLR_TX_IEN(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the DMC control words (starting address, end and length) for the channel.
|
||||||
|
void mi_get_dmc (uint16 dmc, uint16 *pnext, uint16 *plast, uint16 *pcount)
|
||||||
|
{
|
||||||
|
uint16 dmcad;
|
||||||
|
if ((dmc<DMC1) || (dmc>(DMC1+DMC_MAX-1))) {
|
||||||
|
*pnext = *plast = *pcount = 0; return;
|
||||||
|
}
|
||||||
|
dmcad = DMC_BASE + (dmc-DMC1)*2;
|
||||||
|
*pnext = M[dmcad] & X_AMASK; *plast = M[dmcad+1] & X_AMASK;
|
||||||
|
*pcount = (*plast - *pnext + 1) & DMASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the DMC words to show "count" words transferred.
|
||||||
|
void mi_update_dmc (uint32 dmc, uint32 count)
|
||||||
|
{
|
||||||
|
uint16 dmcad, next;
|
||||||
|
if ((dmc<DMC1) || (dmc>(DMC1+DMC_MAX-1))) return;
|
||||||
|
dmcad = DMC_BASE + (dmc-DMC1)*2;
|
||||||
|
next = M[dmcad];
|
||||||
|
M[dmcad] = (next & DMA_IN) | ((next+count) & X_AMASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link error recovery ...
|
||||||
|
void mi_link_error (uint16 line)
|
||||||
|
{
|
||||||
|
// Any physical I/O error, either for the UDP link or a COM port, prints a
|
||||||
|
// message and detaches the modem. It's up to the user to decide what to do
|
||||||
|
// after that...
|
||||||
|
fprintf(stderr,"MI%d - UNRECOVERABLE I/O ERROR!\n", line);
|
||||||
|
mi_reset_rx(line); mi_reset_tx(line);
|
||||||
|
sim_cancel(PUNIT(line)); mi_detach(PUNIT(line));
|
||||||
|
PMIDB(line)->link = NOLINK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/////////////////// D E B U G G I N G R O U T I N E S ////////////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Log a modem input or output including DMC words ...
|
||||||
|
void mi_debug_mio (uint16 line, uint32 dmc, const char *ptext)
|
||||||
|
{
|
||||||
|
uint16 next, last, count;
|
||||||
|
if (!ISLDBG(line, IMP_DBG_IOT)) return;
|
||||||
|
mi_get_dmc(dmc, &next, &last, &count);
|
||||||
|
sim_debug(IMP_DBG_IOT, PDEVICE(line),
|
||||||
|
"start %s (PC=%06o, next=%06o, last=%06o, count=%d)\n",
|
||||||
|
ptext, PC-1, next, last, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the contents of a message sent or received ...
|
||||||
|
void mi_debug_msg (uint16 line, uint16 next, uint16 count, const char *ptext)
|
||||||
|
{
|
||||||
|
uint16 i; char buf[CBUFSIZE]; int len = 0;
|
||||||
|
if (!ISLDBG(line, MI_DBG_MSG)) return;
|
||||||
|
sim_debug(MI_DBG_MSG, PDEVICE(line), "message %s (length=%d)\n", ptext, count);
|
||||||
|
for (i = 1, len = 0; i <= count; ++i) {
|
||||||
|
len += sprintf(buf+len, "%06o ", M[next+i-1]);
|
||||||
|
if (((i & 7) == 0) || (i == count)) {
|
||||||
|
sim_debug(MI_DBG_MSG, PDEVICE(line), "- %s\n", buf); len = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
///////////////// T R A N S M I T A N D R E C E I V E //////////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Start the transmitter ...
|
||||||
|
void mi_start_tx (uint16 line)
|
||||||
|
{
|
||||||
|
// This handles all the work of the "start modem output" OCP, including
|
||||||
|
// extracting the packet from H316 memory, EXCEPT for actually setting the
|
||||||
|
// transmit done interrupt. That's handled by the RTC polling routine after
|
||||||
|
// a delay that we calculate..
|
||||||
|
uint16 next, last, count; uint32 nbits; t_stat ret;
|
||||||
|
|
||||||
|
// Get the DMC words for this channel and update the next pointer as if the
|
||||||
|
// transfer actually occurred.
|
||||||
|
mi_get_dmc(PDIB(line)->txdmc, &next, &last, &count);
|
||||||
|
mi_update_dmc(PDIB(line)->txdmc, count);
|
||||||
|
mi_debug_msg (line, next, count, "sent");
|
||||||
|
|
||||||
|
// Transmit the data, handling both the interface loopback AND the line loop
|
||||||
|
// back flags in the process. Note that in particular the interface loop back
|
||||||
|
// does NOT require that the modem be attached!
|
||||||
|
if (PMIDB(line)->iloop) {
|
||||||
|
mi_rx_local(line, next, count);
|
||||||
|
} else if (PMIDB(line)->link != NOLINK) {
|
||||||
|
if (PMIDB(line)->lloop)
|
||||||
|
ret = udp_send_self(PDEVICE(line), PMIDB(line)->link, &M[next], count);
|
||||||
|
else
|
||||||
|
ret = udp_send(PDEVICE(line), PMIDB(line)->link, &M[next], count);
|
||||||
|
if (ret != SCPE_OK) mi_link_error(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do some fancy math to figure out how long, in RTC ticks, it would actually
|
||||||
|
// take to transmit a packet of this length with a real modem and phone line.
|
||||||
|
// Note that the "+12" is an approximation for the modem overhead, including
|
||||||
|
// DLE, STX, ETX and checksum bytes, that would be added to the packet.
|
||||||
|
nbits = (((uint32) count)*2UL + 12UL) * 8UL;
|
||||||
|
PMIDB(line)->txdelay = (nbits * 1000000UL) / (PMIDB(line)->bps * rtc_interval);
|
||||||
|
//fprintf(stderr,"MI%d - transmit packet, length=%d, bits=%ld, interval=%ld, delay=%ld\n", line, count, nbits, rtc_interval, PMIDB(line)->txdelay);
|
||||||
|
|
||||||
|
// That's it - we're done until it's time for the TX done interrupt ...
|
||||||
|
CLR_TX_IRQ(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll for transmitter done interrupts ...
|
||||||
|
void mi_poll_tx (uint16 line, uint32 quantum)
|
||||||
|
{
|
||||||
|
// This routine is called, via the RTC service, to count down the interval
|
||||||
|
// until the transmitter finishes. When it hits zero, an interrupt occurs.
|
||||||
|
if (PMIDB(line)->txdelay == 0) return;
|
||||||
|
if (PMIDB(line)->txdelay <= quantum) {
|
||||||
|
SET_TX_IRQ(line); PMIDB(line)->txdelay = 0; PMIDB(line)->txtotal++;
|
||||||
|
sim_debug(IMP_DBG_IOT, PDEVICE(line), "transmit done (message #%d, intreq=%06o)\n", PMIDB(line)->txtotal, dev_ext_int);
|
||||||
|
} else
|
||||||
|
PMIDB(line)->txdelay -= quantum;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the receiver ...
|
||||||
|
void mi_start_rx (uint16 line)
|
||||||
|
{
|
||||||
|
// "Starting" the receiver simply sets the RX pending flag. Nothing else
|
||||||
|
// needs to be done (nothing else _can_ be done!) until we actually receive
|
||||||
|
// a real packet.
|
||||||
|
|
||||||
|
// We check for the case of another receive already pending, but I don't
|
||||||
|
// think the real hardware detected this or considered it an error condition.
|
||||||
|
if (PMIDB(line)->rxpending) {
|
||||||
|
sim_debug(IMP_DBG_WARN,PDEVICE(line),"start input while input already pending\n");
|
||||||
|
}
|
||||||
|
PMIDB(line)->rxpending = TRUE; PMIDB(line)->rxerror = FALSE;
|
||||||
|
CLR_RX_IRQ(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll for receiver data ...
|
||||||
|
void mi_poll_rx (uint16 line)
|
||||||
|
{
|
||||||
|
// This routine is called by mi_service to poll for any packets received.
|
||||||
|
// This is done regardless of whether a receive is pending on the line. If
|
||||||
|
// a packet is waiting AND a receive is pending then we'll store it and finish
|
||||||
|
// the receive operation. If a packet is waiting but no receive is pending
|
||||||
|
// then the packet is discarded...
|
||||||
|
uint16 next, last, maxbuf; uint16 *pdata; int16 count;
|
||||||
|
|
||||||
|
// If the modem isn't attached, then the read never completes!
|
||||||
|
if (PMIDB(line)->link == NOLINK) return;
|
||||||
|
|
||||||
|
// Get the DMC words for this channel, or zeros if no read is pending ...
|
||||||
|
if (PMIDB(line)->rxpending) {
|
||||||
|
mi_get_dmc(PDIB(line)->rxdmc, &next, &last, &maxbuf);
|
||||||
|
pdata = &M[next];
|
||||||
|
} else {
|
||||||
|
next = last = maxbuf = 0; pdata = NULL;
|
||||||
|
}
|
||||||
|
// Try to read a packet. If we get nothing then just return.
|
||||||
|
count = udp_receive(PDEVICE(line), PMIDB(line)->link, pdata, maxbuf);
|
||||||
|
if (count == 0) return;
|
||||||
|
if (count < 0) {mi_link_error(line); return;}
|
||||||
|
|
||||||
|
// Now would be a good time to worry about whether a receive is pending!
|
||||||
|
if (!PMIDB(line)->rxpending) {
|
||||||
|
sim_debug(IMP_DBG_WARN, PDEVICE(line), "data received with no input pending\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We really got a packet! Update the DMC pointers to reflect the actual
|
||||||
|
// size of the packet received. If the packet length would have exceeded the
|
||||||
|
// receiver buffer, then that sets the error flag too.
|
||||||
|
if (count > maxbuf) {
|
||||||
|
sim_debug(IMP_DBG_WARN, PDEVICE(line), "receiver overrun (length=%d maxbuf=%d)\n", count, maxbuf);
|
||||||
|
PMIDB(line)->rxerror = TRUE; count = maxbuf;
|
||||||
|
}
|
||||||
|
mi_update_dmc(PDIB(line)->rxdmc, count);
|
||||||
|
mi_debug_msg (line, next, count, "received");
|
||||||
|
|
||||||
|
// Assert the interrupt request and we're done!
|
||||||
|
SET_RX_IRQ(line); PMIDB(line)->rxpending = FALSE; PMIDB(line)->rxtotal++;
|
||||||
|
sim_debug(IMP_DBG_IOT, PDEVICE(line), "receive done (message #%d, intreq=%06o)\n", PMIDB(line)->rxtotal, dev_ext_int);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive cross patched data ...
|
||||||
|
void mi_rx_local (uint16 line, uint16 txnext, uint16 txcount)
|
||||||
|
{
|
||||||
|
// This routine is invoked by the mi_start_tx() function when this modem has
|
||||||
|
// the "interface cross patch" bit set. This flag causes the modem to talk to
|
||||||
|
// to itself, and data sent by the transmitter goes directly to the receiver.
|
||||||
|
// The modem is bypassed completely and in fact need not even be connected.
|
||||||
|
// This is essentially a special case of the mi_poll_rx() routine and it's a
|
||||||
|
// shame they don't share more code, but that's the way it is.
|
||||||
|
// Get the DMC words for this channel, or zeros if no read is pending ...
|
||||||
|
uint16 rxnext, rxlast, maxbuf;
|
||||||
|
|
||||||
|
// If no read is pending, then just throw away the data ...
|
||||||
|
if (!PMIDB(line)->rxpending) return;
|
||||||
|
|
||||||
|
// Get the DMC words for the receiver and copy data from one buffer to the other.
|
||||||
|
mi_get_dmc(PDIB(line)->rxdmc, &rxnext, &rxlast, &maxbuf);
|
||||||
|
if (txcount > maxbuf) {txcount = maxbuf; PMIDB(line)->rxerror = TRUE;}
|
||||||
|
memmove(&M[rxnext], &M[txnext], txcount * sizeof(uint16));
|
||||||
|
|
||||||
|
// Update the receiver DMC pointers, assert IRQ and we're done!
|
||||||
|
mi_update_dmc(PDIB(line)->rxdmc, txcount);
|
||||||
|
mi_debug_msg (line, rxnext, txcount, "received");
|
||||||
|
SET_RX_IRQ(line); PMIDB(line)->rxpending = FALSE; PMIDB(line)->rxtotal++;
|
||||||
|
sim_debug(IMP_DBG_IOT, PDEVICE(line), "receive done (message #%d, intreq=%06o)\n", PMIDB(line)->rxtotal, dev_ext_int);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//////////// I / O I N S T R U C T I O N E M U L A T I O N /////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Line specific I/O routines ...
|
||||||
|
// Unfortunately simh doesn't pass the I/O emulation routine any data about the
|
||||||
|
// device except for its device address. In particular, it doesn't pass a pointer
|
||||||
|
// to the device or unit data blocks, so we're on our own to find those. Rather
|
||||||
|
// than a search on the device address, we just provide a separate I/O routine
|
||||||
|
// for each modem line. All they do is call the common I/O routine with an extra
|
||||||
|
// parameter - problem solved!
|
||||||
|
int32 mi1_io(int32 inst, int32 fnc, int32 dat, int32 dev) {return mi_io(1, inst, fnc, dat, dev);}
|
||||||
|
int32 mi2_io(int32 inst, int32 fnc, int32 dat, int32 dev) {return mi_io(2, inst, fnc, dat, dev);}
|
||||||
|
int32 mi3_io(int32 inst, int32 fnc, int32 dat, int32 dev) {return mi_io(3, inst, fnc, dat, dev);}
|
||||||
|
int32 mi4_io(int32 inst, int32 fnc, int32 dat, int32 dev) {return mi_io(4, inst, fnc, dat, dev);}
|
||||||
|
int32 mi5_io(int32 inst, int32 fnc, int32 dat, int32 dev) {return mi_io(5, inst, fnc, dat, dev);}
|
||||||
|
|
||||||
|
// Common I/O simulation routine ...
|
||||||
|
int32 mi_io (uint16 line, int32 inst, int32 fnc, int32 dat, int32 dev)
|
||||||
|
{
|
||||||
|
// This routine is invoked by the CPU module whenever the code executes any
|
||||||
|
// I/O instruction (OCP, SKS, INA or OTA) with one of our modem's device
|
||||||
|
// address.
|
||||||
|
|
||||||
|
// OCP (output control pulse) initiates various modem operations ...
|
||||||
|
if (inst == ioOCP) {
|
||||||
|
switch (fnc) {
|
||||||
|
case 000:
|
||||||
|
// MnOUT - start modem output ...
|
||||||
|
mi_debug_mio(line, PDIB(line)->txdmc, "output");
|
||||||
|
mi_start_tx(line); return dat;
|
||||||
|
case 001:
|
||||||
|
// MnUNXP - un-cross patch modem ...
|
||||||
|
sim_debug(IMP_DBG_IOT,PDEVICE(line),"un-cross patch modem (PC=%06o)\n", PC-1);
|
||||||
|
PMIDB(line)->iloop = PMIDB(line)->lloop = FALSE; return dat;
|
||||||
|
case 002:
|
||||||
|
// MnLXP - enable line cross patch ...
|
||||||
|
sim_debug(IMP_DBG_IOT,PDEVICE(line),"enable line cross patch (PC=%06o)\n", PC-1);
|
||||||
|
PMIDB(line)->lloop = TRUE; PMIDB(line)->iloop = FALSE; return dat;
|
||||||
|
case 003:
|
||||||
|
// MnIXP - enable interface cross patch ...
|
||||||
|
sim_debug(IMP_DBG_IOT,PDEVICE(line),"enable interface cross patch (PC=%06o)\n", PC-1);
|
||||||
|
PMIDB(line)->iloop = TRUE; PMIDB(line)->lloop = FALSE; return dat;
|
||||||
|
case 004:
|
||||||
|
// MnIN - start modem input ...
|
||||||
|
mi_debug_mio(line, PDIB(line)->rxdmc, "input");
|
||||||
|
mi_start_rx(line); return dat;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SKS (skip) tests various modem conditions ...
|
||||||
|
} else if (inst == ioSKS) {
|
||||||
|
switch (fnc) {
|
||||||
|
case 004:
|
||||||
|
// MnERR - skip on modem error ...
|
||||||
|
sim_debug(IMP_DBG_IOT,PDEVICE(line),"skip on error (PC=%06o, %s)\n",
|
||||||
|
PC-1, PMIDB(line)->rxerror ? "SKIP" : "NOSKIP");
|
||||||
|
return PMIDB(line)->rxerror ? IOSKIP(dat) : dat;
|
||||||
|
// NOTE - the following skip, MnRXDONE, isn't actually part of the
|
||||||
|
// original IMP design. As far as I can tell the IMP had no way to
|
||||||
|
// explicitly poll this flags; the only way to tell when a modem finished
|
||||||
|
// was to catch the associated interrupt. I've added one for testing
|
||||||
|
// purposes, using an unimplemented SKS instruction.
|
||||||
|
case 002:
|
||||||
|
// MnRXDONE - skip on receive done ...
|
||||||
|
return PMIDB(line)->rxpending ? dat : IOSKIP(dat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anything else is an error...
|
||||||
|
sim_debug(IMP_DBG_WARN,PDEVICE(line),"UNIMPLEMENTED I/O (PC=%06o, instruction=%o, function=%02o)\n", PC-1, inst, fnc);
|
||||||
|
return IOBADFNC(dat);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receiver service ...
|
||||||
|
t_stat mi_rx_service (UNIT *uptr)
|
||||||
|
{
|
||||||
|
// This is the standard simh "service" routine that's called when an event
|
||||||
|
// queue entry expires. It just polls the receiver and reschedules itself.
|
||||||
|
// That's it!
|
||||||
|
uint16 line = uptr->mline;
|
||||||
|
mi_poll_rx(line);
|
||||||
|
sim_activate(uptr, uptr->wait);
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transmitter service ...
|
||||||
|
t_stat mi_tx_service (uint32 quantum)
|
||||||
|
{
|
||||||
|
// This is the special transmitter service routine that's called by the RTC
|
||||||
|
// service every time the RTC is updated. This routine polls ALL the modem
|
||||||
|
// transmitters (or at least any which are active) and figures out whether it
|
||||||
|
// is time for an interrupt.
|
||||||
|
uint32 i;
|
||||||
|
for (i = 1; i <= MI_NUM; ++i) mi_poll_tx(i, quantum);
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/////////////// D E V I C E A C T I O N C O M M A N D S ////////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Reset device ...
|
||||||
|
t_stat mi_reset (DEVICE *dptr)
|
||||||
|
{
|
||||||
|
// simh calls this routine for the RESET command ...
|
||||||
|
UNIT *uptr = dptr->units;
|
||||||
|
uint16 line = uptr->mline;
|
||||||
|
|
||||||
|
// Reset the devices AND clear the interrupt enable bits ...
|
||||||
|
mi_reset_rx(line); mi_reset_tx(line);
|
||||||
|
|
||||||
|
// If the unit is attached, then make sure we restart polling because some
|
||||||
|
// simh commands (e.g. boot) dump the pending event queue!
|
||||||
|
sim_cancel(uptr);
|
||||||
|
if ((uptr->flags & UNIT_ATT) != 0) sim_activate(uptr, uptr->wait);
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach device ...
|
||||||
|
t_stat mi_attach (UNIT *uptr, char *cptr)
|
||||||
|
{
|
||||||
|
// simh calls this routine for (what else?) the ATTACH command. There are
|
||||||
|
// three distinct formats for ATTACH -
|
||||||
|
//
|
||||||
|
// ATTACH -p MIn COMnn - attach MIn to a physical COM port
|
||||||
|
// ATTACH MIn llll:w.x.y.z:rrrr - connect via UDP to a remote simh host
|
||||||
|
//
|
||||||
|
t_stat ret; char *pfn; uint16 line = uptr->mline;
|
||||||
|
t_bool fport = sim_switches & SWMASK('P');
|
||||||
|
|
||||||
|
// If we're already attached, then detach ...
|
||||||
|
if ((uptr->flags & UNIT_ATT) != 0) detach_unit(uptr);
|
||||||
|
|
||||||
|
// The physical (COM port) attach isn't implemented yet ...
|
||||||
|
if (fport) {
|
||||||
|
fprintf(stderr,"MI%d - physical COM support is not yet implemented\n", line);
|
||||||
|
return SCPE_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a copy of the "file name" argument. udp_create() actually modifies
|
||||||
|
// the string buffer we give it, so we make a copy now so we'll have something
|
||||||
|
// to display in the "SHOW MIn ..." command.
|
||||||
|
pfn = (char *) calloc (CBUFSIZE, sizeof (char));
|
||||||
|
if (pfn == NULL) return SCPE_MEM;
|
||||||
|
strncpy (pfn, cptr, CBUFSIZE);
|
||||||
|
|
||||||
|
// Create the UDP connection.
|
||||||
|
ret = udp_create(PDEVICE(line), cptr, &(PMIDB(line)->link));
|
||||||
|
if (ret != SCPE_OK) {free(pfn); return ret;};
|
||||||
|
|
||||||
|
// Reset the flags and start polling ...
|
||||||
|
uptr->flags |= UNIT_ATT; uptr->filename = pfn;
|
||||||
|
return mi_reset(find_dev_from_unit(uptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detach device ...
|
||||||
|
t_stat mi_detach (UNIT *uptr)
|
||||||
|
{
|
||||||
|
// simh calls this routine for (you guessed it!) the DETACH command. This
|
||||||
|
// disconnects the modem from any UDP connection or COM port and effectively
|
||||||
|
// makes the modem "off line". A disconnected modem acts like a real modem
|
||||||
|
// with its phone line unplugged.
|
||||||
|
t_stat ret; uint16 line = uptr->mline;
|
||||||
|
if ((uptr->flags & UNIT_ATT) == 0) return SCPE_OK;
|
||||||
|
ret = udp_release(PDEVICE(line), PMIDB(line)->link);
|
||||||
|
if (ret != SCPE_OK) return ret;
|
||||||
|
PMIDB(line)->link = NOLINK; uptr->flags &= ~UNIT_ATT;
|
||||||
|
free (uptr->filename); uptr->filename = NULL;
|
||||||
|
return mi_reset(PDEVICE(line));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // #ifdef VM_IMPTIP from the very top
|
|
@ -25,6 +25,7 @@
|
||||||
|
|
||||||
mt 516-4100 seven track magnetic tape
|
mt 516-4100 seven track magnetic tape
|
||||||
|
|
||||||
|
3-Jul-13 RLA compatibility changes for extended interrupts
|
||||||
19-Mar-12 RMS Fixed declaration of chan_req (Mark Pizzolato)
|
19-Mar-12 RMS Fixed declaration of chan_req (Mark Pizzolato)
|
||||||
09-Jun-07 RMS Fixed bug in write without stop (Theo Engel)
|
09-Jun-07 RMS Fixed bug in write without stop (Theo Engel)
|
||||||
16-Feb-06 RMS Added tape capacity checking
|
16-Feb-06 RMS Added tape capacity checking
|
||||||
|
@ -119,7 +120,7 @@ void mt_wrwd (UNIT *uptr, uint32 dat);
|
||||||
mt_mod MT modifier list
|
mt_mod MT modifier list
|
||||||
*/
|
*/
|
||||||
|
|
||||||
DIB mt_dib = { MT, IOBUS, MT_NUMDR, &mtio };
|
DIB mt_dib = { MT, MT_NUMDR, IOBUS, IOBUS, INT_V_MT, INT_V_NONE, &mtio, 0 };
|
||||||
|
|
||||||
UNIT mt_unit[] = {
|
UNIT mt_unit[] = {
|
||||||
{ UDATA (&mt_svc, UNIT_ATTABLE + UNIT_ROABLE + UNIT_DISABLE, 0) },
|
{ UDATA (&mt_svc, UNIT_ATTABLE + UNIT_ROABLE + UNIT_DISABLE, 0) },
|
||||||
|
|
384
H316/h316_rtc.c
Normal file
384
H316/h316_rtc.c
Normal file
|
@ -0,0 +1,384 @@
|
||||||
|
/* h316_rtc.c- BBN ARPAnet IMP/TIP Real Time Clock and Watch Dog Timer
|
||||||
|
Based on the SIMH simulator package written by Robert M Supnik.
|
||||||
|
|
||||||
|
Copyright (c) 2013 Robert Armstrong, bob@jfcl.com.
|
||||||
|
|
||||||
|
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
|
||||||
|
ROBERT ARMSTRONG 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 Robert Armstrong shall not be
|
||||||
|
used in advertising or otherwise to promote the sale, use or other dealings
|
||||||
|
in this Software without prior written authorization from Robert Armstrong.
|
||||||
|
|
||||||
|
rtc real time clock
|
||||||
|
wdt watch dog timer
|
||||||
|
|
||||||
|
21-May-13 RLA New file
|
||||||
|
|
||||||
|
OVERVIEW
|
||||||
|
|
||||||
|
The IMP and TIP used a custom real time clock that was apparently created
|
||||||
|
by BBN just for those devices. The IMP/TIP RTC is NOT the same as the
|
||||||
|
official Honeywell real time clock option, H316-12. When emulating an IMP
|
||||||
|
or TIP, this RTC device must be enabled and the standard simh H316 CLK
|
||||||
|
device must be disabled.
|
||||||
|
|
||||||
|
The IMP and TIP also had a watch dog timer which, if ever allowed to time
|
||||||
|
out, would cause a non-maskable interrupt via location 62(8) - this is the
|
||||||
|
same trap location used by the memory lockout option, which the IMP/TIP
|
||||||
|
lacked. Not much is known about the WDT, and the current implementation is
|
||||||
|
simply a place holder that doesn't do much.
|
||||||
|
|
||||||
|
RTC state is maintained in a set of state variables:
|
||||||
|
|
||||||
|
ENA RTC is enabled
|
||||||
|
COUNT current count
|
||||||
|
IEN RTC interrupt enabled
|
||||||
|
IRQ RTC interrupt pending
|
||||||
|
TPS effective ticks per second
|
||||||
|
WAIT simulator time until the next tick
|
||||||
|
|
||||||
|
WDT state is maintained in a set of state variables:
|
||||||
|
|
||||||
|
COUNT current countdown
|
||||||
|
TMO WDT timed out
|
||||||
|
LIGHTS last "set status lights"
|
||||||
|
WAIT simulator time until the next tick
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
Implement the WDT!!
|
||||||
|
*/
|
||||||
|
#ifdef VM_IMPTIP
|
||||||
|
#include "h316_defs.h" // H316 emulator definitions
|
||||||
|
#include "h316_imp.h" // ARPAnet IMP/TIP definitions
|
||||||
|
|
||||||
|
// Locals ...
|
||||||
|
uint32 rtc_interval = RTC_INTERVAL; // RTC tick interval (in microseconds)
|
||||||
|
uint32 rtc_quantum = RTC_QUANTUM; // RTC update interval (in ticks)
|
||||||
|
uint32 rtc_tps = 1000000UL / (RTC_INTERVAL * RTC_QUANTUM);
|
||||||
|
uint16 rtc_enabled = 1; // RTC enabled
|
||||||
|
uint32 rtc_count = 0; // current RTC count
|
||||||
|
uint32 wdt_delay = WDT_DELAY; // WDT timeout (in milliseconds, 0=none)
|
||||||
|
uint32 wdt_count = 0; // current WDT countdown
|
||||||
|
uint16 wdt_lights = 0; // last "set status lights" output
|
||||||
|
|
||||||
|
// Externals from other parts of simh ...
|
||||||
|
extern uint16 dev_ext_int, dev_ext_enb; // current IRQ and IEN bit vectors
|
||||||
|
extern int32 PC; // current PC (for debug messages)
|
||||||
|
extern int32 stop_inst; // needed by IOBADFNC()
|
||||||
|
|
||||||
|
// Forward declarations ...
|
||||||
|
int32 rtc_io (int32 inst, int32 fnc, int32 dat, int32 dev);
|
||||||
|
t_stat rtc_service (UNIT *uptr);
|
||||||
|
t_stat rtc_reset (DEVICE *dptr);
|
||||||
|
int32 wdt_io (int32 inst, int32 fnc, int32 dat, int32 dev);
|
||||||
|
t_stat wdt_service (UNIT *uptr);
|
||||||
|
t_stat wdt_reset (DEVICE *dptr);
|
||||||
|
t_stat rtc_set_interval (UNIT *uptr, int32 val, char *cptr, void *desc);
|
||||||
|
t_stat rtc_show_interval (FILE *st, UNIT *uptr, int32 val, void *desc);
|
||||||
|
t_stat rtc_set_quantum(UNIT *uptr, int32 val, char *cptr, void *desc);
|
||||||
|
t_stat rtc_show_quantum (FILE *st, UNIT *uptr, int32 val, void *desc);
|
||||||
|
t_stat wdt_set_delay (UNIT *uptr, int32 val, char *cptr, void *desc);
|
||||||
|
t_stat wdt_show_delay (FILE *st, UNIT *uptr, int32 val, void *desc);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
////////////////// R T C D A T A S T R U C T U R E S //////////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// RTC device information block ...
|
||||||
|
DIB rtc_dib = { RTC, 1, IOBUS, IOBUS, INT_V_RTC, INT_V_NONE, &rtc_io, 0 };
|
||||||
|
|
||||||
|
// RTC unit data block (we have only one unit!) ...
|
||||||
|
UNIT rtc_unit = { UDATA (&rtc_service, 0, 0), (RTC_INTERVAL*RTC_QUANTUM) };
|
||||||
|
|
||||||
|
// RTC device registers (for "EXAMINE RTC STATE") ...
|
||||||
|
REG rtc_reg[] = {
|
||||||
|
{ FLDATA (ENA, rtc_enabled, 0) },
|
||||||
|
{ DRDATA (COUNT, rtc_count, 16), PV_LEFT },
|
||||||
|
{ FLDATA (IEN, dev_ext_enb, INT_V_RTC-INT_V_EXTD) },
|
||||||
|
{ FLDATA (IRQ, dev_ext_int, INT_V_RTC-INT_V_EXTD) },
|
||||||
|
{ DRDATA (TPS, rtc_tps, 32), PV_LEFT },
|
||||||
|
{ DRDATA (WAIT, rtc_unit.wait, 24), REG_NZ + PV_LEFT },
|
||||||
|
{ NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
// RTC device modifiers (for "SET/SHOW RTC xxx") ...
|
||||||
|
MTAB rtc_mod[] = {
|
||||||
|
{ MTAB_XTD|MTAB_VDV, 0, "INTERVAL", "INTERVAL", &rtc_set_interval, &rtc_show_interval, NULL },
|
||||||
|
{ MTAB_XTD|MTAB_VDV, 0, "QUANTUM", "QUANTUM", &rtc_set_quantum, &rtc_show_quantum, NULL },
|
||||||
|
{ 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
// RTC debugging flags (for "SET RTC DEBUG=xxx") ...
|
||||||
|
DEBTAB rtc_debug[] = {
|
||||||
|
{"WARN", IMP_DBG_WARN},
|
||||||
|
{"IO", IMP_DBG_IOT},
|
||||||
|
{0}
|
||||||
|
};
|
||||||
|
|
||||||
|
// And finally tie it all together ...
|
||||||
|
DEVICE rtc_dev = {
|
||||||
|
"RTC", &rtc_unit, rtc_reg, rtc_mod,
|
||||||
|
1, 0, 0, 0, 0, 0,
|
||||||
|
NULL, NULL, &rtc_reset, NULL, NULL, NULL,
|
||||||
|
&rtc_dib, DEV_DIS|DEV_DISABLE|DEV_DEBUG, 0, rtc_debug, NULL, NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
////////////////// W D T D A T A S T R U C T U R E S //////////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// WDT device information block ...
|
||||||
|
DIB wdt_dib = { WDT, 1, IOBUS, IOBUS, INT_V_NONE, INT_V_NONE, &wdt_io, 0 };
|
||||||
|
|
||||||
|
// WDT unit data block (it has only one) ...
|
||||||
|
UNIT wdt_unit = { UDATA (&wdt_service, 0, 0), 1000 };
|
||||||
|
|
||||||
|
// WDT device registers (for "EXAMINE WDT STATE") ...
|
||||||
|
REG wdt_reg[] = {
|
||||||
|
{ DRDATA (COUNT, wdt_count, 16), PV_LEFT },
|
||||||
|
{ DRDATA (WAIT, wdt_unit.wait, 24), REG_NZ | PV_LEFT },
|
||||||
|
{ ORDATA (LIGHTS, wdt_lights, 16), REG_RO | PV_LEFT },
|
||||||
|
{ NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
// WDT device modifiers (for "SET/SHOW WDT xxx") ...
|
||||||
|
MTAB wdt_mod[] = {
|
||||||
|
{ MTAB_XTD | MTAB_VDV, 0, "DELAY", "DELAY", &wdt_set_delay, &wdt_show_delay, NULL },
|
||||||
|
{ 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
// WDT debugging flags (for "SET WDT DEBUG=xxx") ...
|
||||||
|
DEBTAB wdt_debug[] = {
|
||||||
|
{"WARN", IMP_DBG_WARN},
|
||||||
|
{"IO", IMP_DBG_IOT},
|
||||||
|
{"LIGHTS", WDT_DBG_LIGHTS},
|
||||||
|
{0}
|
||||||
|
};
|
||||||
|
|
||||||
|
// And summarize ...
|
||||||
|
DEVICE wdt_dev = {
|
||||||
|
"WDT", &wdt_unit, wdt_reg, wdt_mod,
|
||||||
|
1, 0, 0, 0, 0, 0,
|
||||||
|
NULL, NULL, &wdt_reset, NULL, NULL, NULL,
|
||||||
|
&wdt_dib, DEV_DIS|DEV_DISABLE|DEV_DEBUG, 0, wdt_debug, NULL, NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
////////// R T C I / O A N D S E R V I C E R O U T I N E S //////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Set and clear the RTC IRQ and IEN ...
|
||||||
|
#define SET_RTC_IRQ() SET_EXT_INT((1u << (rtc_dib.inum - INT_V_EXTD)))
|
||||||
|
#define CLR_RTC_IRQ() CLR_EXT_INT((1u << (rtc_dib.inum - INT_V_EXTD)))
|
||||||
|
#define CLR_RTC_IEN() CLR_EXT_ENB((1u << (rtc_dib.inum - INT_V_EXTD)))
|
||||||
|
|
||||||
|
// RTC IO routine ...
|
||||||
|
int32 rtc_io (int32 inst, int32 fnc, int32 dat, int32 dev)
|
||||||
|
{
|
||||||
|
switch (inst) {
|
||||||
|
case ioOCP:
|
||||||
|
if (fnc == 010) {
|
||||||
|
// CLKOFF - turn the RTC off ...
|
||||||
|
sim_cancel(&rtc_unit); rtc_enabled = 0; CLR_RTC_IRQ();
|
||||||
|
sim_debug(IMP_DBG_IOT, &rtc_dev, "disabled (PC=%06o)\n", PC-1);
|
||||||
|
return dat;
|
||||||
|
} else if (fnc == 000) {
|
||||||
|
// CLKON - turn the RTC on ...
|
||||||
|
rtc_enabled = 1; CLR_RTC_IRQ();
|
||||||
|
if (sim_is_active(&rtc_unit) == 0)
|
||||||
|
sim_activate (&rtc_unit, sim_rtc_init (rtc_unit.wait));
|
||||||
|
sim_debug(IMP_DBG_IOT, &rtc_dev, "enabled (PC=%06o)\n", PC-1);
|
||||||
|
return dat;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ioINA:
|
||||||
|
if ((fnc == 010) || (fnc == 000)) {
|
||||||
|
// RDCLOK - return the current count
|
||||||
|
sim_debug(IMP_DBG_IOT, &rtc_dev, "read clock (PC=%06o, RTC=%06o)\n", PC-1, (rtc_count & DMASK));
|
||||||
|
return IOSKIP((rtc_count & DMASK));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sim_debug(IMP_DBG_WARN, &rtc_dev, "UNIMPLEMENTED I/O (PC=%06o, instruction=%o, function=%02o)\n", PC-1, inst, fnc);
|
||||||
|
return IOBADFNC(dat);
|
||||||
|
}
|
||||||
|
|
||||||
|
// RTC unit service ...
|
||||||
|
t_stat rtc_service (UNIT *uptr)
|
||||||
|
{
|
||||||
|
// Add the current quantum to the clock register and, if the clock register
|
||||||
|
// has overflowed, request an interrupt. The real hardware interrupts when
|
||||||
|
// there is a carry out of the low byte (in other words, every 256 clocks).
|
||||||
|
// Note that we can't simply check the low byte for zero to detect overflows
|
||||||
|
// because of the quantum. Since we aren't necessarily incrementing by 1, we
|
||||||
|
// may never see a value of exactly zero. We'll have to be more clever.
|
||||||
|
uint8 rtc_high = HIBYTE(rtc_count);
|
||||||
|
rtc_count = (rtc_count + rtc_quantum) & DMASK;
|
||||||
|
if (HIBYTE(rtc_count) != rtc_high) {
|
||||||
|
sim_debug(IMP_DBG_IOT, &rtc_dev, "interrupt request\n");
|
||||||
|
SET_RTC_IRQ();
|
||||||
|
}
|
||||||
|
mi_tx_service(rtc_quantum);
|
||||||
|
uptr->wait = sim_rtc_calb (rtc_tps); /* recalibrate */
|
||||||
|
sim_activate_after (uptr, 1000000/rtc_tps); /* reactivate unit */
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
////////// W D T I / O A N D S E R V I C E R O U T I N E S //////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// WDT IO routine ...
|
||||||
|
int32 wdt_io (int32 inst, int32 fnc, int32 dat, int32 dev)
|
||||||
|
{
|
||||||
|
if ((inst == ioOCP) && (fnc == 0)) {
|
||||||
|
// Reset WDT ...
|
||||||
|
sim_debug(IMP_DBG_IOT, &wdt_dev, "reset (PC=%06o)\n", PC-1);
|
||||||
|
return dat;
|
||||||
|
} else if ((inst == ioOTA) && (fnc == 0)) {
|
||||||
|
// Set status lights ...
|
||||||
|
if (wdt_lights != dat) {
|
||||||
|
sim_debug(WDT_DBG_LIGHTS, &wdt_dev, "changed to %06o\n", dat);
|
||||||
|
}
|
||||||
|
sim_debug(IMP_DBG_IOT, &wdt_dev, "set status lights (PC=%06o, LIGHTS=%06o)\n", PC-1, dat);
|
||||||
|
wdt_lights = dat; return dat;
|
||||||
|
}
|
||||||
|
|
||||||
|
sim_debug(IMP_DBG_WARN, &wdt_dev, "UNIMPLEMENTED I/O (PC=%06o, instruction=%o, function=%02o)\n", PC-1, inst, fnc);
|
||||||
|
return IOBADFNC(dat);
|
||||||
|
}
|
||||||
|
|
||||||
|
// WDT unit service ...
|
||||||
|
t_stat wdt_service (UNIT *uptr)
|
||||||
|
{
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/////////////// D E V I C E A C T I O N C O M M A N D S ////////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// RTC reset routine ...
|
||||||
|
t_stat rtc_reset (DEVICE *dptr)
|
||||||
|
{
|
||||||
|
// Clear the interrupt enable and any pending interrupts, reset the count
|
||||||
|
// and enable the clock. At least I assume that's what a reset does - the
|
||||||
|
// docs aren't too specific on this point...
|
||||||
|
rtc_enabled = 1; rtc_count = 0;
|
||||||
|
CLR_RTC_IRQ(); CLR_RTC_IEN();
|
||||||
|
sim_cancel (&rtc_unit);
|
||||||
|
sim_register_clock_unit ((dptr->flags & DEV_DIS) ? NULL : &rtc_unit);
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// WDT reset routine ...
|
||||||
|
t_stat wdt_reset (DEVICE *dptr)
|
||||||
|
{
|
||||||
|
// Clear the WDT countdown and turn off all the lights ...
|
||||||
|
wdt_count = 0; wdt_lights = 0;
|
||||||
|
sim_cancel (&wdt_unit);
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
///////// D E V I C E S E T A N D S H O W C O M M A N D S //////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Set/Show RTC interval ...
|
||||||
|
t_stat rtc_set_interval (UNIT *uptr, int32 val, char *cptr, void *desc)
|
||||||
|
{
|
||||||
|
uint32 newint, newtps; t_stat ret;
|
||||||
|
if (cptr == NULL) return SCPE_ARG;
|
||||||
|
newint = get_uint (cptr, 10, 1000000, &ret);
|
||||||
|
if (ret != SCPE_OK) return ret;
|
||||||
|
if (newint == 0) return SCPE_ARG;
|
||||||
|
newtps = 1000000UL / (newint * rtc_quantum);
|
||||||
|
if ((newtps == 0) || (newtps >= 100000)) return SCPE_ARG;
|
||||||
|
rtc_interval = newint; rtc_tps = newtps;
|
||||||
|
uptr->wait = sim_rtc_calb (rtc_tps);
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
t_stat rtc_show_interval (FILE *st, UNIT *uptr, int32 val, void *desc)
|
||||||
|
{
|
||||||
|
fprintf(st,"interval=%d (us)", rtc_interval);
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set/Show RTC quantum ...
|
||||||
|
t_stat rtc_set_quantum (UNIT *uptr, int32 val, char *cptr, void *desc)
|
||||||
|
{
|
||||||
|
uint32 newquant, newtps; t_stat ret;
|
||||||
|
if (cptr == NULL) return SCPE_ARG;
|
||||||
|
newquant = get_uint (cptr, 10, 1000000, &ret);
|
||||||
|
if (ret != SCPE_OK) return ret;
|
||||||
|
if (newquant == 0) return SCPE_ARG;
|
||||||
|
newtps = 1000000UL / (rtc_interval * newquant);
|
||||||
|
if ((newtps == 0) || (newtps >= 100000)) return SCPE_ARG;
|
||||||
|
rtc_quantum = newquant; rtc_tps = newtps;
|
||||||
|
uptr->wait = sim_rtc_calb (rtc_tps);
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
t_stat rtc_show_quantum (FILE *st, UNIT *uptr, int32 val, void *desc)
|
||||||
|
{
|
||||||
|
fprintf(st,"quantum=%d (ticks)", rtc_quantum);
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set/Show WDT delay ...
|
||||||
|
t_stat wdt_set_delay (UNIT *uptr, int32 val, char *cptr, void *desc)
|
||||||
|
{
|
||||||
|
uint32 newint; t_stat ret;
|
||||||
|
if (cptr == NULL) return SCPE_ARG;
|
||||||
|
newint = get_uint (cptr, 10, 65535, &ret);
|
||||||
|
if (ret != SCPE_OK) return ret;
|
||||||
|
if (newint != 0) {
|
||||||
|
fprintf(stderr,"WDT - timeout not yet implemented\n");
|
||||||
|
return SCPE_IERR;
|
||||||
|
}
|
||||||
|
wdt_delay = newint;
|
||||||
|
// TBA add calculations here???
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
t_stat wdt_show_delay (FILE *st, UNIT *uptr, int32 val, void *desc)
|
||||||
|
{
|
||||||
|
if (wdt_delay > 0)
|
||||||
|
fprintf(st,"delay=%d (ms)", wdt_delay);
|
||||||
|
else
|
||||||
|
fprintf(st,"no timeout");
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // #ifdef VM_IMPTIP from the very top
|
|
@ -30,6 +30,7 @@
|
||||||
|
|
||||||
10-Sep-13 RMS Fixed several bugs in the TTY logic
|
10-Sep-13 RMS Fixed several bugs in the TTY logic
|
||||||
Added SET file type commands to PTR/PTP
|
Added SET file type commands to PTR/PTP
|
||||||
|
3-Jul-13 RLA compatibility changes for extended interrupts
|
||||||
09-Jun-07 RMS Fixed bug in clock increment (Theo Engel)
|
09-Jun-07 RMS Fixed bug in clock increment (Theo Engel)
|
||||||
30-Sep-06 RMS Fixed handling of non-printable characters in KSR mode
|
30-Sep-06 RMS Fixed handling of non-printable characters in KSR mode
|
||||||
03-Apr-06 RMS Fixed bugs in punch state handling (Theo Engel)
|
03-Apr-06 RMS Fixed bugs in punch state handling (Theo Engel)
|
||||||
|
@ -158,7 +159,7 @@ t_stat ttp_write (int32 c);
|
||||||
ptr_reg PTR register list
|
ptr_reg PTR register list
|
||||||
*/
|
*/
|
||||||
|
|
||||||
DIB ptr_dib = { PTR, IOBUS, 1, &ptrio };
|
DIB ptr_dib = { PTR, 1, IOBUS, IOBUS, INT_V_PTR, INT_V_NONE, &ptrio, 0 };
|
||||||
|
|
||||||
UNIT ptr_unit = {
|
UNIT ptr_unit = {
|
||||||
UDATA (&ptr_svc, UNIT_SEQ+UNIT_ATTABLE+UNIT_ROABLE, 0),
|
UDATA (&ptr_svc, UNIT_SEQ+UNIT_ATTABLE+UNIT_ROABLE, 0),
|
||||||
|
@ -203,7 +204,7 @@ DEVICE ptr_dev = {
|
||||||
ptp_reg PTP register list
|
ptp_reg PTP register list
|
||||||
*/
|
*/
|
||||||
|
|
||||||
DIB ptp_dib = { PTP, IOBUS, 1, &ptpio };
|
DIB ptp_dib = { PTP, 1, IOBUS, IOBUS, INT_V_PTP, INT_V_NONE, &ptpio, 0 };
|
||||||
|
|
||||||
UNIT ptp_unit = {
|
UNIT ptp_unit = {
|
||||||
UDATA (&ptp_svc, UNIT_SEQ+UNIT_ATTABLE, 0), SERIAL_OUT_WAIT
|
UDATA (&ptp_svc, UNIT_SEQ+UNIT_ATTABLE, 0), SERIAL_OUT_WAIT
|
||||||
|
@ -243,7 +244,7 @@ DEVICE ptp_dev = {
|
||||||
#define TTR 2
|
#define TTR 2
|
||||||
#define TTP 3
|
#define TTP 3
|
||||||
|
|
||||||
DIB tty_dib = { TTY, IOBUS, 1, &ttyio };
|
DIB tty_dib = { TTY, 1, IOBUS, IOBUS, INT_V_TTY, INT_V_NONE, &ttyio, 0 };
|
||||||
|
|
||||||
UNIT tty_unit[] = {
|
UNIT tty_unit[] = {
|
||||||
{ UDATA (&tti_svc, TT_MODE_KSR, 0), KBD_POLL_WAIT },
|
{ UDATA (&tti_svc, TT_MODE_KSR, 0), KBD_POLL_WAIT },
|
||||||
|
@ -308,7 +309,7 @@ DEVICE tty_dev = {
|
||||||
clk_reg CLK register list
|
clk_reg CLK register list
|
||||||
*/
|
*/
|
||||||
|
|
||||||
DIB clk_dib = { CLK_KEYS, IOBUS, 1, &clkio };
|
DIB clk_dib = { CLK_KEYS, 1, IOBUS, IOBUS, INT_V_CLK, INT_V_NONE, &clkio, 0 };
|
||||||
|
|
||||||
UNIT clk_unit = { UDATA (&clk_svc, 0, 0), 16000 };
|
UNIT clk_unit = { UDATA (&clk_svc, 0, 0), 16000 };
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
used in advertising or otherwise to promote the sale, use or other dealings
|
used in advertising or otherwise to promote the sale, use or other dealings
|
||||||
in this Software without prior written authorization from Robert M Supnik.
|
in this Software without prior written authorization from Robert M Supnik.
|
||||||
|
|
||||||
|
21-May-13 RLA Add IMP/TIP devices
|
||||||
01-Dec-04 RMS Fixed fprint_opr calling sequence
|
01-Dec-04 RMS Fixed fprint_opr calling sequence
|
||||||
24-Oct-03 RMS Added DMA/DMC support
|
24-Oct-03 RMS Added DMA/DMC support
|
||||||
17-Sep-01 RMS Removed multiconsole support
|
17-Sep-01 RMS Removed multiconsole support
|
||||||
|
@ -41,6 +42,11 @@ extern DEVICE clk_dev;
|
||||||
extern DEVICE dp_dev;
|
extern DEVICE dp_dev;
|
||||||
extern DEVICE fhd_dev;
|
extern DEVICE fhd_dev;
|
||||||
extern DEVICE mt_dev;
|
extern DEVICE mt_dev;
|
||||||
|
#ifdef VM_IMPTIP
|
||||||
|
extern DEVICE rtc_dev, wdt_dev, imp_dev;
|
||||||
|
extern DEVICE mi1_dev, mi2_dev, mi3_dev, mi4_dev, mi5_dev;
|
||||||
|
extern DEVICE hi1_dev, hi2_dev, hi3_dev, hi4_dev;
|
||||||
|
#endif
|
||||||
extern REG cpu_reg[];
|
extern REG cpu_reg[];
|
||||||
extern uint16 M[];
|
extern uint16 M[];
|
||||||
|
|
||||||
|
@ -64,12 +70,19 @@ DEVICE *sim_devices[] = {
|
||||||
&cpu_dev,
|
&cpu_dev,
|
||||||
&ptr_dev,
|
&ptr_dev,
|
||||||
&ptp_dev,
|
&ptp_dev,
|
||||||
&tty_dev,
|
|
||||||
&lpt_dev,
|
&lpt_dev,
|
||||||
&clk_dev,
|
&tty_dev,
|
||||||
&dp_dev,
|
|
||||||
&fhd_dev,
|
|
||||||
&mt_dev,
|
&mt_dev,
|
||||||
|
&clk_dev,
|
||||||
|
&fhd_dev,
|
||||||
|
&dp_dev,
|
||||||
|
#ifdef VM_IMPTIP
|
||||||
|
&wdt_dev,
|
||||||
|
&rtc_dev,
|
||||||
|
&imp_dev,
|
||||||
|
&mi1_dev, &mi2_dev, &mi3_dev, &mi4_dev, &mi5_dev,
|
||||||
|
&hi1_dev, &hi2_dev, &hi3_dev, &hi4_dev,
|
||||||
|
#endif
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
667
H316/h316_udp.c
Normal file
667
H316/h316_udp.c
Normal file
|
@ -0,0 +1,667 @@
|
||||||
|
/* h316_udp.c: IMP/TIP Modem and Host Interface socket routines using UDP
|
||||||
|
|
||||||
|
Copyright (c) 2013 Robert Armstrong, bob@jfcl.com
|
||||||
|
|
||||||
|
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
|
||||||
|
ROBERT ARMSTRONG 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 Robert Armstrong shall not be
|
||||||
|
used in advertising or otherwise to promote the sale, use or other dealings
|
||||||
|
in this Software without prior written authorization from Robert Armstrong.
|
||||||
|
|
||||||
|
|
||||||
|
REVISION HISTORY
|
||||||
|
|
||||||
|
udp socket routines
|
||||||
|
|
||||||
|
26-Jun-13 RLA Rewritten from TCP version
|
||||||
|
|
||||||
|
|
||||||
|
OVERVIEW
|
||||||
|
|
||||||
|
This module emulates low level communications between two virtual modems
|
||||||
|
using UDP packets over the modern network connections. It's used by both
|
||||||
|
the IMP modem interface and the host interface modules to implement IMP to
|
||||||
|
IMP and IMP to HOST connections.
|
||||||
|
|
||||||
|
TCP vs UDP
|
||||||
|
|
||||||
|
Why UDP and not TCP? TCP has a couple of advantages after all - it's
|
||||||
|
stream oriented, which is intrinsically like a modem, and it handles all
|
||||||
|
the network "funny stuff" for us. TCP has a couple of problems too - first,
|
||||||
|
it's inherently asymmetrical. There's a "server" end which opens a master
|
||||||
|
socket and passively listens for connections, and a "client" end which
|
||||||
|
actively attempts to connect. That's annoying, but it can be worked around.
|
||||||
|
|
||||||
|
The big problem with TCP is that even though it treats the data like a stream
|
||||||
|
it's internally buffering it, and you really have absolutely no control over
|
||||||
|
when TCP will decide to send its buffer. Google "nagle algorithm" to get an
|
||||||
|
idea. Yes, you can set TCP_NODELAY to disable Nagle, but the data's still
|
||||||
|
buffered and it doesn't fix the problem. What's the issue with buffering?
|
||||||
|
It introduces completely unpredictable delays into the message traffic. A
|
||||||
|
transmitting IMP could send two or three (or ten or twenty!) messages before
|
||||||
|
TCP actually decides to try to deliver them to the destination.
|
||||||
|
|
||||||
|
And it turns out that IMPs are extraordinarily sensitive to line speed. The
|
||||||
|
IMP firmware actually goes to the trouble of measuring the effective line
|
||||||
|
speed by using the RTC to figure out how long it takes to send a message.
|
||||||
|
One thing that screws up the IMP to no end is variation in the effective
|
||||||
|
line speed. I guess they had a lot of trouble with AT&T Long Lines back in
|
||||||
|
the Old Days, and the IMP has quite a bit of code to monitor line quality.
|
||||||
|
Even fairly minor variations in speed will cause it to mark the line as
|
||||||
|
"down" and sent a trouble report back to BBN. And no, I'm not making this up!
|
||||||
|
|
||||||
|
UDP gives us a few advantages. First, it's inherently packet oriented so
|
||||||
|
we can simply grab the entire packet from the transmitting IMP's memory, wrap
|
||||||
|
a little extra information around it, and ship it off in one datagram. The
|
||||||
|
receiving IMP gets the whole packet at once and it can simply BLT it into
|
||||||
|
the receiving IMP's memory. No fuss, no muss, no need convert the packet
|
||||||
|
into a stream, add word counts, wait for complete packets, etc. And UDP is
|
||||||
|
symmetrical - both ends listen and send in the same way. There's no need for
|
||||||
|
master sockets, passive (server) and active (client) ends, or any of that.
|
||||||
|
|
||||||
|
Also UDP has no buffering - the packet goes out on the wire when we send it.
|
||||||
|
The data doesn't wait around in some buffer for TCP to decide when it wants
|
||||||
|
to let it go. The latency and delay for UDP is much more predictable and
|
||||||
|
consistent, at least for local networks. If you're actually sending the
|
||||||
|
packets out on the big, wide, Internet then all bets are off on that.
|
||||||
|
|
||||||
|
UDP has a few problems that we have to worry about. First, it's not
|
||||||
|
guaranteed delivery so just because one IMP sends a packet doesn't mean that
|
||||||
|
the other end will ever see it. Surprisingly that's not a problem for us.
|
||||||
|
Phone lines have noise and dropouts, and real modems lose packets too. The
|
||||||
|
IMP code is completely happy and able to deal with that, and generally we
|
||||||
|
don't worry about dropped packets at all.
|
||||||
|
|
||||||
|
There are other issues with UDP - it doesn't guarantee packet order, so the
|
||||||
|
sending IMP might transmit packets 1, 2 and 3 and the receiving IMP will get
|
||||||
|
1, 3 then 2. THAT would never happen with a real modem and we have to shield
|
||||||
|
the IMP code from any such eventuality. Also, with UDP packets can be
|
||||||
|
duplicated so the receiving IMP might see 1, 2, 2, 3 (or even 1, 3, 2, 1!).
|
||||||
|
Again, a modem would never do that and we have to prevent it from happening.
|
||||||
|
Both cases are easily dealt with by adding a sequence number to the header
|
||||||
|
we wrap around the IMP's packet. Out of sequence or duplicate packets can
|
||||||
|
be detected and are simply dropped. If necessary, the IMP will deal with
|
||||||
|
retransmitting them in its own time.
|
||||||
|
|
||||||
|
One more thing about UDP - there is no way to tell whether a connection is
|
||||||
|
established or not and for that matter there is no "connection" at all
|
||||||
|
(that's why it's a "connectionless" protocol, after all!). We simply send
|
||||||
|
packets out and there's no way to know whether anybody is hearing them. The
|
||||||
|
real IMP modem hardware had no carrier detect or other dataset control
|
||||||
|
functions, so it was identical in that respect. An IMP sent messages out the
|
||||||
|
modem and, unless it received a message back, it had no way to know whether
|
||||||
|
the IMP on the other end was hearing them.
|
||||||
|
|
||||||
|
|
||||||
|
INTERFACE
|
||||||
|
|
||||||
|
This module provides a simplified UDP socket interface. These functions are
|
||||||
|
implemented -
|
||||||
|
|
||||||
|
udp_create define a connection to the remote IMP
|
||||||
|
udp_release release a connection
|
||||||
|
udp_send send an IMP message to the other end
|
||||||
|
udp_receive receive (w/o blocking!) a message if available
|
||||||
|
|
||||||
|
Note that each connection is assigned a unique "handle", a small integer,
|
||||||
|
which is used as an index into our internal connection data table. There
|
||||||
|
is a limit on the maximum number of connections available, as set my the
|
||||||
|
MAXLINKS parameter. Also, notice that all links are intrinsically full
|
||||||
|
duplex and bidirectional - data can be sent and received in both directions
|
||||||
|
independently. Real modems and host cards were exactly the same.
|
||||||
|
|
||||||
|
One last comment - there's a nice sim_sock module which provides platform
|
||||||
|
independent TCP functions. Unfortunately there is no UDP equivalent, and this
|
||||||
|
module doesn't use sim_sock. Sorry. Even more unfortunate is that the
|
||||||
|
current implementation is WIN32/WINSOCK specific. Sorry again. There's no
|
||||||
|
reason why it couldn't be ported to other platforms, but somebody will have
|
||||||
|
to write the missing code.
|
||||||
|
*/
|
||||||
|
#ifdef VM_IMPTIP
|
||||||
|
#include "sim_defs.h" // simh machine independent definitions
|
||||||
|
#ifdef _WIN32 // WINSOCK definitions
|
||||||
|
#include <winsock2.h> // at least Windows puts it all in one file!
|
||||||
|
#elif defined(__linux__) // Linux definitions
|
||||||
|
#include <sys/socket.h> // struct socketaddr_in, et al
|
||||||
|
#include <netinet/in.h> // INADDR_NONE, et al
|
||||||
|
#include <netdb.h> // gethostbyname()
|
||||||
|
#include <fcntl.h> // fcntl() (what else??)
|
||||||
|
#include <unistd.h> // getpid(), more?
|
||||||
|
#endif
|
||||||
|
#include "h316_defs.h" // H316 emulator definitions
|
||||||
|
#include "h316_imp.h" // ARPAnet IMP/TIP definitions
|
||||||
|
|
||||||
|
// Local constants ...
|
||||||
|
#define MAXLINKS 10 // maximum number of simultaneous connections
|
||||||
|
// This constant determines the longest possible IMP data payload that can be
|
||||||
|
// sent. Most IMP messages are trivially small - 68 words or so - but, when one
|
||||||
|
// IMP asks for a reload the neighbor IMP sends the entire memory image in a
|
||||||
|
// single message! That message is about 14K words long.
|
||||||
|
// The next thing you should worry about is whether the underlying IP network
|
||||||
|
// can actually send a UDP packet of this size. It turns out that there's no
|
||||||
|
// simple answer to that - it'll be fragmented for sure, but as long as all
|
||||||
|
// the fragments arrive intact then the destination should reassemble them.
|
||||||
|
#define MAXDATA 16384 // longest possible IMP packet (in H316 words)
|
||||||
|
|
||||||
|
// Compatibility hacks for WINSOCK vs Linux ...
|
||||||
|
#ifdef __linux__
|
||||||
|
#define WSAGetLastError() errno
|
||||||
|
#define closesocket close
|
||||||
|
#define SOCKET int32
|
||||||
|
#define SOCKADDR struct sockaddr
|
||||||
|
#define WSAEWOULDBLOCK EWOULDBLOCK
|
||||||
|
#define INVALID_SOCKET ((SOCKET) (-1))
|
||||||
|
#define SOCKET_ERROR (-1)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// UDP connection data structure ...
|
||||||
|
// One of these blocks is allocated for every simulated modem link.
|
||||||
|
struct _UDP_LINK {
|
||||||
|
t_bool used; // TRUE if this UDP_LINK is in use
|
||||||
|
uint32 ipremote; // IP address of the remote system
|
||||||
|
uint16 rxport; // OUR receiving port number
|
||||||
|
uint16 txport; // HIS receiving port number (on ipremote)
|
||||||
|
struct sockaddr_in rxaddr; // OUR receiving address (goes with rxsock!)
|
||||||
|
struct sockaddr_in txaddr; // HIS transmitting address (pairs with txsock!)
|
||||||
|
SOCKET rxsock; // socket for receiving incoming packets
|
||||||
|
SOCKET txsock; // socket for sending outgoing packets
|
||||||
|
uint32 rxsequence; // next message sequence number for receive
|
||||||
|
uint32 txsequence; // next message sequence number for transmit
|
||||||
|
};
|
||||||
|
typedef struct _UDP_LINK UDP_LINK;
|
||||||
|
|
||||||
|
// This magic number is stored at the beginning of every UDP message and is
|
||||||
|
// checked on receive. It's hardly foolproof, but its a simple attempt to
|
||||||
|
// guard against other applications dumping unsolicited UDP messages into our
|
||||||
|
// receiver socket...
|
||||||
|
#define MAGIC ((uint32) (((((('H' << 8) | '3') << 8) | '1') << 8) | '6'))
|
||||||
|
|
||||||
|
// UDP wrapper data structure ...
|
||||||
|
// This is the UDP packet which is actually transmitted or received. It
|
||||||
|
// contains the actual IMP packet, plus whatever additional information we
|
||||||
|
// need to keep track of things. NOTE THAT ALL DATA IN THIS PACKET, INCLUDING
|
||||||
|
// THE H316 MEMORY WORDS, ARE SENT AND RECEIVED WITH NETWORK BYTE ORDER!
|
||||||
|
struct _UDP_PACKET {
|
||||||
|
uint32 magic; // UDP "magic number" (see above)
|
||||||
|
uint32 sequence; // UDP packet sequence number
|
||||||
|
uint16 count; // number of H316 words to follow
|
||||||
|
uint16 data[MAXDATA]; // and the actual H316 data words/IMP packet
|
||||||
|
};
|
||||||
|
typedef struct _UDP_PACKET UDP_PACKET;
|
||||||
|
#define UDP_HEADER_LEN (2*sizeof(uint32) + sizeof(uint16))
|
||||||
|
|
||||||
|
// Locals ...
|
||||||
|
t_bool udp_wsa_started = FALSE; // TRUE if WSAStartup() has been called
|
||||||
|
UDP_LINK udp_links[MAXLINKS] = {0}; // data for every active connection
|
||||||
|
|
||||||
|
|
||||||
|
t_stat udp_startup (DEVICE *dptr)
|
||||||
|
{
|
||||||
|
// WINSOCK requires that WSAStartup be called exactly once before any other
|
||||||
|
// network calls are made. That's a bit inconvenient, but this routine deals
|
||||||
|
// with it by using a static variable to call WSAStartup the first time thru
|
||||||
|
// and then never again.
|
||||||
|
#ifdef _WIN32
|
||||||
|
WORD wVersionRequested = MAKEWORD(2,2);
|
||||||
|
WSADATA wsaData; int32 ret;
|
||||||
|
if (!udp_wsa_started) {
|
||||||
|
ret = WSAStartup (wVersionRequested, &wsaData);
|
||||||
|
if (ret != 0) {
|
||||||
|
fprintf(stderr,"UDP - WINSOCK startup error %d\n", ret);
|
||||||
|
return SCPE_IERR;
|
||||||
|
} else
|
||||||
|
sim_debug(IMP_DBG_UDP, dptr, "WSAStartup() called\n");
|
||||||
|
udp_wsa_started = TRUE;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
t_stat udp_shutdown (DEVICE *dptr)
|
||||||
|
{
|
||||||
|
// This routine calls WSACleanup() after the last socket has been closed.
|
||||||
|
// It's essentially the opposite of udp_startup() ...
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (udp_wsa_started) {
|
||||||
|
WSACleanup(); udp_wsa_started = FALSE;
|
||||||
|
sim_debug(IMP_DBG_UDP, dptr, "WSACleanup() called\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 udp_find_free_link (void)
|
||||||
|
{
|
||||||
|
// Find a free UDP_LINK block, initialize it and return its index. If none
|
||||||
|
// are free, then return -1 ...
|
||||||
|
int32 i;
|
||||||
|
for (i = 0; i < MAXLINKS; ++i) {
|
||||||
|
if (udp_links[i].used == 0) {
|
||||||
|
memset(&udp_links[i], 0, sizeof(UDP_LINK));
|
||||||
|
// Just in case these values aren't zero!
|
||||||
|
udp_links[i].rxsock = udp_links[i].txsock = INVALID_SOCKET;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NOLINK;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *udp_format_remote (int32 link)
|
||||||
|
{
|
||||||
|
// Format the remote address and port in the format "w.x.y.z:pppp" . It's
|
||||||
|
// a bit ugly (OK, it's a lot ugly!) but it's just for error messages...
|
||||||
|
static char buf[64];
|
||||||
|
sprintf(buf, "%d.%d.%d.%d:%d",
|
||||||
|
(udp_links[link].ipremote >> 24) & 0xFF,
|
||||||
|
(udp_links[link].ipremote >> 16) & 0xFF,
|
||||||
|
(udp_links[link].ipremote >> 8) & 0xFF,
|
||||||
|
udp_links[link].ipremote & 0xFF,
|
||||||
|
udp_links[link].txport);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* get_ipaddr IP address:port
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
cptr = pointer to input string
|
||||||
|
Outputs:
|
||||||
|
ipa = pointer to IP address (may be NULL), 0 = none
|
||||||
|
ipp = pointer to IP port (may be NULL), 0 = none
|
||||||
|
result = status
|
||||||
|
*/
|
||||||
|
|
||||||
|
static t_stat get_ipaddr (char *cptr, uint32 *ipa, uint32 *ipp)
|
||||||
|
{
|
||||||
|
char gbuf[CBUFSIZE];
|
||||||
|
char *addrp, *portp, *octetp;
|
||||||
|
uint32 i, addr, port, octet;
|
||||||
|
t_stat r;
|
||||||
|
|
||||||
|
if ((cptr == NULL) || (*cptr == 0))
|
||||||
|
return SCPE_ARG;
|
||||||
|
strncpy (gbuf, cptr, CBUFSIZE);
|
||||||
|
addrp = gbuf; /* default addr */
|
||||||
|
if ((portp = strchr (gbuf, ':'))) /* x:y? split */
|
||||||
|
*portp++ = 0;
|
||||||
|
else if (strchr (gbuf, '.')) /* x.y...? */
|
||||||
|
portp = NULL;
|
||||||
|
else {
|
||||||
|
portp = gbuf; /* port only */
|
||||||
|
addrp = NULL; /* no addr */
|
||||||
|
}
|
||||||
|
if (portp) { /* port string? */
|
||||||
|
if (ipp == NULL) /* not wanted? */
|
||||||
|
return SCPE_ARG;
|
||||||
|
port = (int32) get_uint (portp, 10, 65535, &r);
|
||||||
|
if ((r != SCPE_OK) || (port == 0))
|
||||||
|
return SCPE_ARG;
|
||||||
|
}
|
||||||
|
else port = 0;
|
||||||
|
if (addrp) { /* addr string? */
|
||||||
|
if (ipa == NULL) /* not wanted? */
|
||||||
|
return SCPE_ARG;
|
||||||
|
for (i = addr = 0; i < 4; i++) { /* four octets */
|
||||||
|
octetp = strchr (addrp, '.'); /* find octet end */
|
||||||
|
if (octetp != NULL) /* split string */
|
||||||
|
*octetp++ = 0;
|
||||||
|
else if (i < 3) /* except last */
|
||||||
|
return SCPE_ARG;
|
||||||
|
octet = (int32) get_uint (addrp, 10, 255, &r);
|
||||||
|
if (r != SCPE_OK)
|
||||||
|
return SCPE_ARG;
|
||||||
|
addr = (addr << 8) | octet;
|
||||||
|
addrp = octetp;
|
||||||
|
}
|
||||||
|
if (((addr & 0377) == 0) || ((addr & 0377) == 255))
|
||||||
|
return SCPE_ARG;
|
||||||
|
}
|
||||||
|
else addr = 0;
|
||||||
|
if (ipp) /* return req values */
|
||||||
|
*ipp = port;
|
||||||
|
if (ipa)
|
||||||
|
*ipa = addr;
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
t_stat udp_parse_remote (int32 link, char *premote)
|
||||||
|
{
|
||||||
|
// This routine will parse a remote address string in any of these forms -
|
||||||
|
//
|
||||||
|
// llll:w.x.y.z:rrrr
|
||||||
|
// llll:name.domain.com:rrrr
|
||||||
|
// llll::rrrr
|
||||||
|
// w.x.y.z:rrrr
|
||||||
|
// name.domain.com:rrrr
|
||||||
|
//
|
||||||
|
// In all examples, "llll" is the local port number that we use for listening,
|
||||||
|
// and "rrrr" is the remote port number that we use for transmitting. The
|
||||||
|
// local port is optional and may be omitted, in which case it defaults to the
|
||||||
|
// same as the remote port. This works fine if the other IMP is actually on
|
||||||
|
// a different host, but don't try that with localhost - you'll be talking to
|
||||||
|
// yourself!! In both cases, "w.x.y.z" is a dotted IP for the remote machine
|
||||||
|
// and "name.domain.com" is its name (which will be looked up to get the IP).
|
||||||
|
// If the host name/IP is omitted then it defaults to "localhost".
|
||||||
|
char *end, *colon; int32 port; struct hostent *he;
|
||||||
|
if (*premote == '\0') return SCPE_2FARG;
|
||||||
|
// Look for the local port number. If it's not there, set rxport to zero for now.
|
||||||
|
port = strtoul(premote, &end, 10); udp_links[link].rxport = 0;
|
||||||
|
if ((*end == ':') && (port > 0)) {
|
||||||
|
udp_links[link].rxport = port; premote = end+1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for "name:port" and extract the remote port...
|
||||||
|
if ((colon = strchr(premote, ':')) == NULL) return SCPE_ARG;
|
||||||
|
*colon++ = '\0'; port = strtoul(colon, &end, 10);
|
||||||
|
if ((*end != '\0') || (port == 0)) return SCPE_ARG;
|
||||||
|
udp_links[link].txport = port;
|
||||||
|
if (udp_links[link].rxport == 0) udp_links[link].rxport = port;
|
||||||
|
|
||||||
|
// Now try to parse the host as a dotted IP address ...
|
||||||
|
if (get_ipaddr(premote, &udp_links[link].ipremote, NULL) == SCPE_OK) return SCPE_OK;
|
||||||
|
|
||||||
|
// Special kludge - allow just ":port" to mean "localhost:port" ...
|
||||||
|
if(*premote == '\0') {
|
||||||
|
if (udp_links[link].rxport == udp_links[link].txport)
|
||||||
|
fprintf(stderr,"WARNING - use different transmit and receive ports!\n");
|
||||||
|
premote = "localhost";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not a dotted IP - try to lookup a host name ...
|
||||||
|
if ((he = gethostbyname(premote)) == NULL) return SCPE_OPENERR;
|
||||||
|
udp_links[link].ipremote = * (unsigned long *) he->h_addr_list[0];
|
||||||
|
if (udp_links[link].ipremote == INADDR_NONE) {
|
||||||
|
fprintf(stderr,"WARNING - unable to resolve \"%s\"\n", premote);
|
||||||
|
return SCPE_OPENERR;
|
||||||
|
}
|
||||||
|
udp_links[link].ipremote = ntohl(udp_links[link].ipremote);
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
t_stat udp_socket_error (int32 link, const char *msg)
|
||||||
|
{
|
||||||
|
// This routine is called whenever a SOCKET_ERROR is returned for any I/O.
|
||||||
|
fprintf(stderr,"UDP%d - %s failed with error %d\n", link, msg, WSAGetLastError());
|
||||||
|
return SCPE_IOERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
t_stat udp_create_rx_socket (int32 link)
|
||||||
|
{
|
||||||
|
// This routine will create the receiver socket for the virtual modem.
|
||||||
|
// Sockets are always UDP and, in the case of the receiver, bound to the port
|
||||||
|
// specified. Receiving sockets are also always set to NON BLOCKING mode.
|
||||||
|
int32 iret; uint32 flags = 1;
|
||||||
|
|
||||||
|
// Creating the socket works on both Windows and Linux ...
|
||||||
|
udp_links[link].rxsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||||
|
if (udp_links[link].rxsock == INVALID_SOCKET)
|
||||||
|
return udp_socket_error(link, "RX socket()");
|
||||||
|
udp_links[link].rxaddr.sin_family = AF_INET;
|
||||||
|
udp_links[link].rxaddr.sin_port = htons(udp_links[link].rxport);
|
||||||
|
udp_links[link].rxaddr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
|
iret = bind(udp_links[link].rxsock, (SOCKADDR *) &udp_links[link].rxaddr, sizeof(struct sockaddr_in));
|
||||||
|
if (iret != 0)
|
||||||
|
return udp_socket_error(link, "bind()");
|
||||||
|
|
||||||
|
// But making it non-blocking is a problem ...
|
||||||
|
#ifdef _WIN32
|
||||||
|
iret = ioctlsocket(udp_links[link].rxsock, FIONBIO, (u_long *) &flags);
|
||||||
|
if (iret != 0)
|
||||||
|
return udp_socket_error(link, "ioctlsocket()");
|
||||||
|
#elif defined(__linux__)
|
||||||
|
flags = fcntl(udp_links[link].rxsock, F_GETFL, 0);
|
||||||
|
if (flags == -1) return udp_socket_error(link, "fcntl(F_GETFL)");
|
||||||
|
iret = fcntl(udp_links[link].rxsock, F_SETFL, flags | O_NONBLOCK);
|
||||||
|
if (iret == -1) return udp_socket_error(link, "fcntl(F_SETFL)");
|
||||||
|
iret = fcntl(udp_links[link].rxsock, F_SETOWN, getpid());
|
||||||
|
if (iret == -1) return udp_socket_error(link, "fcntl(F_SETOWN)");
|
||||||
|
#endif
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
t_stat udp_create_tx_socket (int32 link)
|
||||||
|
{
|
||||||
|
// This routine will create the transmitter socket for the virtual modem.
|
||||||
|
// In the case of the transmitter, we don't bind the socket at this time -
|
||||||
|
// WINSOCK will automatically bind it for us to a free port on the first IO.
|
||||||
|
// Also note that transmitting sockets are blocking; we don't have code (yet!)
|
||||||
|
// to allow them to be nonblocking.
|
||||||
|
udp_links[link].txsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||||
|
if (udp_links[link].txsock == INVALID_SOCKET)
|
||||||
|
return udp_socket_error(link, "TX socket()");
|
||||||
|
|
||||||
|
// Initialize the txaddr structure too - note that this isn't used now; it's
|
||||||
|
// the sockaddr we will use when we later do a sendto() the remote host!
|
||||||
|
udp_links[link].txaddr.sin_family = AF_INET;
|
||||||
|
udp_links[link].txaddr.sin_port = htons(udp_links[link].txport);
|
||||||
|
udp_links[link].txaddr.sin_addr.s_addr = htonl(udp_links[link].ipremote);
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
t_stat udp_create (DEVICE *dptr, char *premote, int32 *pln)
|
||||||
|
{
|
||||||
|
// Create a logical UDP link to the specified remote system. The "remote"
|
||||||
|
// string specifies both the remote host name or IP and a port number. The
|
||||||
|
// port number is both the port we send datagrams to, and also the port we
|
||||||
|
// listen on for incoming datagrams. UDP doesn't have any real concept of a
|
||||||
|
// "connection" of course, and this routine simply creates the necessary
|
||||||
|
// sockets in this host. We have no way of knowing whether the remote host is
|
||||||
|
// listening or even if it exists.
|
||||||
|
//
|
||||||
|
// We return SCPE_OK if we're successful and an error code if we aren't. If
|
||||||
|
// we are successful, then the ln parameter is assigned the link number,
|
||||||
|
// which is a handle used to identify this connection to all future udp_xyz()
|
||||||
|
// calls.
|
||||||
|
t_stat ret;
|
||||||
|
int32 link = udp_find_free_link();
|
||||||
|
if (link < 0) return SCPE_MEM;
|
||||||
|
|
||||||
|
// Make sure WINSOCK is initialized ...
|
||||||
|
if ((ret = udp_startup(dptr)) != SCPE_OK) return ret;
|
||||||
|
|
||||||
|
// Parse the remote name and set up the ipaddr and port ...
|
||||||
|
if ((ret = udp_parse_remote(link, premote)) != SCPE_OK) return ret;
|
||||||
|
|
||||||
|
// Create the sockets for the transmitter and receiver ...
|
||||||
|
if ((ret = udp_create_rx_socket(link)) != SCPE_OK) return ret;
|
||||||
|
if ((ret = udp_create_tx_socket(link)) != SCPE_OK) return ret;
|
||||||
|
|
||||||
|
// All done - mark the TCP_LINK data as "used" and return the index.
|
||||||
|
udp_links[link].used = TRUE; *pln = link;
|
||||||
|
sim_debug(IMP_DBG_UDP, dptr, "link %d - listening on port %d and sending to %s\n", link, udp_links[link].rxport, udp_format_remote(link));
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
t_stat udp_release (DEVICE *dptr, int32 link)
|
||||||
|
{
|
||||||
|
// Close a link that was created by udp_create() and release any resources
|
||||||
|
// allocated to it. We always return SCPE_OK unless the link specified is
|
||||||
|
// already unused.
|
||||||
|
int32 iret, i;
|
||||||
|
if ((link < 0) || (link >= MAXLINKS)) return SCPE_IERR;
|
||||||
|
if (!udp_links[link].used) return SCPE_IERR;
|
||||||
|
|
||||||
|
// Close the sockets associated with this connection - that's easy ...
|
||||||
|
iret = closesocket(udp_links[link].rxsock);
|
||||||
|
if (iret != 0) udp_socket_error(link, "closesocket()");
|
||||||
|
iret = closesocket(udp_links[link].txsock);
|
||||||
|
if (iret != 0) udp_socket_error(link, "closesocket()");
|
||||||
|
udp_links[link].used = FALSE;
|
||||||
|
sim_debug(IMP_DBG_UDP, dptr, "link %d - closed\n", link);
|
||||||
|
|
||||||
|
// If we just closed the last link, then call udp_shutdown() ...
|
||||||
|
for (i = 0; i < MAXLINKS; ++i) {
|
||||||
|
if (udp_links[i].used) return SCPE_OK;
|
||||||
|
}
|
||||||
|
return udp_shutdown(dptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
t_stat udp_send_to (DEVICE *dptr, int32 link, uint16 *pdata, uint16 count, SOCKADDR *pdest)
|
||||||
|
{
|
||||||
|
// This routine does all the work of sending an IMP data packet. pdata
|
||||||
|
// is a pointer (usually into H316 simulated memory) to the IMP packet data,
|
||||||
|
// count is the length of the data (in H316 words, not bytes!), and pdest is
|
||||||
|
// the destination socket. There are two things worthy of note here - first,
|
||||||
|
// notice that the H316 words are sent in network order, so the remote simh
|
||||||
|
// doesn't necessarily need to have the same endian-ness as this machine.
|
||||||
|
// Second, notice that transmitting sockets are NOT set to non blocking so
|
||||||
|
// this routine might wait, but we assume the wait will never be too long.
|
||||||
|
UDP_PACKET pkt; int pktlen; uint16 i; int32 iret;
|
||||||
|
if ((link < 0) || (link >= MAXLINKS)) return SCPE_IERR;
|
||||||
|
if (!udp_links[link].used) return SCPE_IERR;
|
||||||
|
if ((pdata == NULL) || (count == 0) || (count > MAXDATA)) return SCPE_IERR;
|
||||||
|
|
||||||
|
// Build the UDP packet, filling in our own header information and copying
|
||||||
|
// the H316 words from memory. REMEMBER THAT EVERYTHING IS IN NETWORK ORDER!
|
||||||
|
pkt.magic = htonl(MAGIC);
|
||||||
|
pkt.sequence = htonl(udp_links[link].txsequence++);
|
||||||
|
pkt.count = htons(count);
|
||||||
|
for (i = 0; i < count; ++i) pkt.data[i] = htons(*pdata++);
|
||||||
|
pktlen = UDP_HEADER_LEN + count*sizeof(uint16);
|
||||||
|
|
||||||
|
// Send it and we're outta here ...
|
||||||
|
iret = sendto(udp_links[link].txsock, (const char *) &pkt, pktlen, 0, pdest, sizeof (struct sockaddr_in));
|
||||||
|
if (iret == SOCKET_ERROR) return udp_socket_error(link, "sendto()");
|
||||||
|
sim_debug(IMP_DBG_UDP, dptr, "link %d - packet sent (sequence=%d, length=%d)\n", link, ntohl(pkt.sequence), ntohs(pkt.count));
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
t_stat udp_send (DEVICE *dptr, int32 link, uint16 *pdata, uint16 count)
|
||||||
|
{
|
||||||
|
// Send an IMP packet to the remote simh. This is the usual case - the only
|
||||||
|
// reason there's any other options at all is so we can emulate loopback.
|
||||||
|
return udp_send_to (dptr, link, pdata, count, (SOCKADDR *) &(udp_links[link].txaddr));
|
||||||
|
}
|
||||||
|
|
||||||
|
t_stat udp_send_self (DEVICE *dptr, int32 link, uint16 *pdata, uint16 count)
|
||||||
|
{
|
||||||
|
// Send an IMP packet to our own receiving socket. This might seem silly,
|
||||||
|
// but it's used to emulate the line loopback function...
|
||||||
|
struct sockaddr_in self;
|
||||||
|
self.sin_family = AF_INET;
|
||||||
|
self.sin_port = htons(udp_links[link].rxport);
|
||||||
|
self.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||||
|
return udp_send_to (dptr, link, pdata, count, (SOCKADDR *) &self);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 udp_receive_packet (int32 link, UDP_PACKET *ppkt)
|
||||||
|
{
|
||||||
|
// This routine will do the hard part of receiving a UDP packet. If it's
|
||||||
|
// successful the packet length, in bytes, is returned. The receiver socket
|
||||||
|
// is non-blocking, so if no packet is available then zero will be returned
|
||||||
|
// instead. Lastly, if a fatal socket I/O error occurs, -1 is returned.
|
||||||
|
//
|
||||||
|
// Note that this routine only receives the packet - it doesn't handle any
|
||||||
|
// of the checking for valid packets, unexpected packets, duplicate or out of
|
||||||
|
// sequence packets. That's strictly the caller's problem!
|
||||||
|
int32 pktsiz;
|
||||||
|
struct sockaddr_in sender;
|
||||||
|
#if defined (macintosh) || defined (__linux) || defined (__linux__) || \
|
||||||
|
defined (__APPLE__) || defined (__OpenBSD__) || \
|
||||||
|
defined(__NetBSD__) || defined(__FreeBSD__) || \
|
||||||
|
(defined(__hpux) && defined(_XOPEN_SOURCE_EXTENDED))
|
||||||
|
socklen_t sndsiz = (socklen_t)sizeof(sender);
|
||||||
|
#elif defined (_WIN32) || defined (__EMX__) || \
|
||||||
|
(defined (__ALPHA) && defined (__unix__)) || \
|
||||||
|
defined (__hpux)
|
||||||
|
int sndsiz = (int)sizeof(sender);
|
||||||
|
#else
|
||||||
|
size_t sndsiz = sizeof(sender);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
pktsiz = recvfrom(udp_links[link].rxsock, (char *) ppkt, sizeof(UDP_PACKET),
|
||||||
|
0, (SOCKADDR *) &sender, &sndsiz);
|
||||||
|
if (pktsiz >= 0) return pktsiz;
|
||||||
|
if (WSAGetLastError() == WSAEWOULDBLOCK) return 0;
|
||||||
|
udp_socket_error(link, "recvfrom()");
|
||||||
|
return NOLINK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 udp_receive (DEVICE *dptr, int32 link, uint16 *pdata, uint16 maxbuf)
|
||||||
|
{
|
||||||
|
// Receive an IMP packet from the virtual modem. pdata is a pointer usually
|
||||||
|
// directly into H316 simulated memory) to where the IMP packet data should
|
||||||
|
// be stored, and maxbuf is the maximum length of that buffer in H316 words
|
||||||
|
// (not bytes!). If a message is successfully received then this routine
|
||||||
|
// returns the length, again in H316 words, of the IMP packet. The caller
|
||||||
|
// can detect buffer overflows by comparing this result to maxbuf. If no
|
||||||
|
// packets are waiting right now then zero is returned, and -1 is returned
|
||||||
|
// in the event of any fatal socket I/O error.
|
||||||
|
//
|
||||||
|
// This routine also handles checking for unsolicited messages and duplicate
|
||||||
|
// or out of sequence messages. All of these are unceremoniously discarded.
|
||||||
|
//
|
||||||
|
// One final note - it's explicitly allowed for pdata to be null and/or
|
||||||
|
// maxbuf to be zero. In either case the received package is discarded, but
|
||||||
|
// the actual length of the discarded package is still returned.
|
||||||
|
UDP_PACKET pkt; int32 pktlen, explen, implen, i; uint32 magic, pktseq;
|
||||||
|
if ((link < 0) || (link >= MAXLINKS)) return SCPE_IERR;
|
||||||
|
if (!udp_links[link].used) return SCPE_IERR;
|
||||||
|
|
||||||
|
while ((pktlen = udp_receive_packet(link, &pkt)) > 0) {
|
||||||
|
// First do some header checks for a valid UDP packet ...
|
||||||
|
if (pktlen < UDP_HEADER_LEN) {
|
||||||
|
sim_debug(IMP_DBG_UDP, dptr, "link %d - received packet w/o header (length=%d)\n", link, pktlen);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
magic = ntohl(pkt.magic);
|
||||||
|
if (magic != MAGIC) {
|
||||||
|
sim_debug(IMP_DBG_UDP, dptr, "link %d - received packet w/bad magic number (magic=%08x)\n", link, magic);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
implen = ntohs(pkt.count);
|
||||||
|
explen = UDP_HEADER_LEN + implen*sizeof(uint16);
|
||||||
|
if (explen != pktlen) {
|
||||||
|
sim_debug(IMP_DBG_UDP, dptr, "link %d - received packet length wrong (expected=%d received=%d)\n", link, explen, pktlen);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now the hard part = check the sequence number. The rxsequence value is
|
||||||
|
// the number of the next packet we expect to receive - that's the number
|
||||||
|
// this packet should have. If this packet's sequence is less than that,
|
||||||
|
// then this packet is out of order or a duplicate and we discard it. If
|
||||||
|
// this packet is greater than that, then we must have missed one or two
|
||||||
|
// packets. In that case we MUST update rxsequence to match this one;
|
||||||
|
// otherwise the two ends could never resynchronize after a lost packet.
|
||||||
|
pktseq = ntohl(pkt.sequence);
|
||||||
|
if (pktseq < udp_links[link].rxsequence) {
|
||||||
|
sim_debug(IMP_DBG_UDP, dptr, "link %d - received packet out of sequence 1 (expected=%d received=%d\n", link, udp_links[link].rxsequence, pktseq);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (pktseq != udp_links[link].rxsequence) {
|
||||||
|
sim_debug(IMP_DBG_UDP, dptr, "link %d - received packet out of sequence 2 (expected=%d received=%d\n", link, udp_links[link].rxsequence, pktseq);
|
||||||
|
}
|
||||||
|
udp_links[link].rxsequence = pktseq+1;
|
||||||
|
|
||||||
|
// It's a valid packet - if there's no buffer then just discard it.
|
||||||
|
if ((pdata == NULL) || (maxbuf == 0)) {
|
||||||
|
sim_debug(IMP_DBG_UDP, dptr, "link %d - received packet discarded (no buffer available)\n", link);
|
||||||
|
return implen;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the data to the H316 memory and we're done!
|
||||||
|
sim_debug(IMP_DBG_UDP, dptr, "link %d - packet received (sequence=%d, length=%d)\n", link, pktseq, pktlen);
|
||||||
|
for (i = 0; i < (implen < maxbuf ? implen : maxbuf); ++i)
|
||||||
|
*pdata++ = ntohs(pkt.data[i]);
|
||||||
|
return implen;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here if pktlen <= 0 ...
|
||||||
|
return pktlen;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // ifdef VM_IMPTIP
|
19
H316/tests/imp2.cmd
Normal file
19
H316/tests/imp2.cmd
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
;; *** IMP NODE #2 SETUP ***
|
||||||
|
|
||||||
|
; IMP #2 connects to IMP #2 via modem line 1 on both ends ...
|
||||||
|
|
||||||
|
; Set the simulator configuration ...
|
||||||
|
echo Creating standard configuration for IMP #2 ...
|
||||||
|
do impconfig.cmd
|
||||||
|
SET IMP NUM=2
|
||||||
|
|
||||||
|
; Load the IMP code ...
|
||||||
|
echo Loading IMP code ...
|
||||||
|
do impcode.cmd
|
||||||
|
|
||||||
|
; Start up the modem links!
|
||||||
|
echo Attaching modem links ...
|
||||||
|
ATTACH MI1 4421::4431
|
||||||
|
|
||||||
|
; And we're done ..
|
||||||
|
echo Type GO to start ...
|
22
H316/tests/imp3.cmd
Normal file
22
H316/tests/imp3.cmd
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
;; *** IMP NODE #3 SETUP ***
|
||||||
|
|
||||||
|
; IMP #3 connects to IMP #2 and #4 both. Modem line 1 on this end connects
|
||||||
|
; to line 1 on the IMP 2 end, and line 2 on this end connects to IMP 3 line 1.
|
||||||
|
|
||||||
|
; Set the simulator configuration ...
|
||||||
|
echo Creating standard configuration for IMP #3 ...
|
||||||
|
do impconfig.cmd
|
||||||
|
SET IMP NUM=3
|
||||||
|
|
||||||
|
; Load the IMP code ...
|
||||||
|
echo Loading IMP code ...
|
||||||
|
do impcode.cmd
|
||||||
|
|
||||||
|
; Start up the modem links!
|
||||||
|
echo Attaching modem links ...
|
||||||
|
SET MI2 ENABLED
|
||||||
|
ATTACH MI1 4431::4421
|
||||||
|
ATTACH MI2 4432::4441
|
||||||
|
|
||||||
|
; And we're done ..
|
||||||
|
echo Type GO to start ...
|
20
H316/tests/imp4.cmd
Normal file
20
H316/tests/imp4.cmd
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
;; *** IMP NODE #4 SETUP ***
|
||||||
|
|
||||||
|
; IMP #4 coneects to IMP #3 via modem line 1 on this end and modem line
|
||||||
|
; 2 on the IMP 3 end ...
|
||||||
|
|
||||||
|
; Set the simulator configuration ...
|
||||||
|
echo Creating standard configuration for IMP #4 ...
|
||||||
|
do impconfig.cmd
|
||||||
|
SET IMP NUM=4
|
||||||
|
|
||||||
|
; Load the IMP code ...
|
||||||
|
echo Loading IMP code ...
|
||||||
|
do impcode.cmd
|
||||||
|
|
||||||
|
; Start up the modem links!
|
||||||
|
echo Attaching modem links ...
|
||||||
|
ATTACH MI1 4441::4432
|
||||||
|
|
||||||
|
; And we're done ..
|
||||||
|
echo Type GO to start ...
|
8237
H316/tests/impcode.cmd
Normal file
8237
H316/tests/impcode.cmd
Normal file
File diff suppressed because it is too large
Load diff
65
H316/tests/impconfig.cmd
Normal file
65
H316/tests/impconfig.cmd
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
;; ***** GENERIC IMP CONFIGURATION *****
|
||||||
|
|
||||||
|
; This simh command file sets up the H316 simulator configuration for a generic
|
||||||
|
; IMP node. Note that it doesn't load any IMP code (the caller is expected to
|
||||||
|
; do that) and it doesn't define any IMP node specific settings (e.g. modem
|
||||||
|
; links, IMP address, etc).
|
||||||
|
;
|
||||||
|
; RLA [4-Jun-13]
|
||||||
|
RESET ALL
|
||||||
|
|
||||||
|
; Define the CPU configuration ...
|
||||||
|
; NOTE - real IMPs only had 16K of memory!
|
||||||
|
SET CPU 16K NOHSA DMA=0 DMC EXTINT=16
|
||||||
|
|
||||||
|
; Disable all the devices an IMP doesn't have ...
|
||||||
|
SET LPT DISABLED
|
||||||
|
SET MT DISABLED
|
||||||
|
SET CLK DISABLED
|
||||||
|
SET FHD DISABLED
|
||||||
|
SET DP DISABLED
|
||||||
|
|
||||||
|
; Enable the IMP device but leave the station address undefined ...
|
||||||
|
SET IMP ENABLED
|
||||||
|
;;SET IMP NUM=1
|
||||||
|
|
||||||
|
; Enable the RTC to count at 50kHz (20us intervals) ...
|
||||||
|
SET RTC ENABLED
|
||||||
|
SET RTC INTERVAL=20
|
||||||
|
SET RTC QUANTUM=32
|
||||||
|
|
||||||
|
; Enable the WDT but don't ever time out (we have enough problems!)...
|
||||||
|
SET WDT ENABLED
|
||||||
|
SET WDT DELAY=0
|
||||||
|
|
||||||
|
; Enable only modem line 1 and disable all the rest ...
|
||||||
|
SET MI1 ENABLED
|
||||||
|
SET MI2 DISABLED
|
||||||
|
SET MI3 DISABLED
|
||||||
|
SET MI4 DISABLED
|
||||||
|
SET MI5 DISABLED
|
||||||
|
|
||||||
|
; Enable only one host interface and disable all the rest ...
|
||||||
|
SET HI1 ENABLED
|
||||||
|
SET HI2 DISABLED
|
||||||
|
SET HI3 DISABLED
|
||||||
|
SET HI4 DISABLED
|
||||||
|
|
||||||
|
; Just ignore I/Os to disconnected devices ...
|
||||||
|
DEPOSIT CPU STOP_DEV 0
|
||||||
|
|
||||||
|
; SS4 ON is required to run DDT!
|
||||||
|
DEPOSIT CPU SS4 1
|
||||||
|
|
||||||
|
; Set the TTY speed to realistic values (about 9600BPS in this case) ...
|
||||||
|
DEPOSIT TTY KTIME 1000
|
||||||
|
DEPOSIT TTY TTIME 1000
|
||||||
|
|
||||||
|
; Don't know for sure what SS2 does, but it appears to have something to do
|
||||||
|
; with the IMP startup. Leave it ON for now...
|
||||||
|
DEPOSIT CPU SS2 1
|
||||||
|
|
||||||
|
; All done ....
|
||||||
|
SET CPU HISTORY=65000
|
||||||
|
SET CONSOLE DEBUG=STDERR
|
||||||
|
SET WDT DEBUG=LIGHTS
|
25
H316/tests/imploop.cmd
Normal file
25
H316/tests/imploop.cmd
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
;; *** IMP FIVE MODEM LINE LOOPBACK TEST ***
|
||||||
|
|
||||||
|
; Set the simulator configuration ...
|
||||||
|
echo IMP five modem line interface loopback test...
|
||||||
|
do impconfig.cmd
|
||||||
|
SET IMP NUM=2
|
||||||
|
|
||||||
|
; Load the IMP code ...
|
||||||
|
echo Loading IMP code ...
|
||||||
|
do impcode.cmd
|
||||||
|
|
||||||
|
; Start up the modem links!
|
||||||
|
SET MI1 ENABLED
|
||||||
|
DEPOSIT MI1 ILOOP 1
|
||||||
|
SET MI2 ENABLED
|
||||||
|
DEPOSIT MI2 ILOOP 1
|
||||||
|
SET MI3 ENABLED
|
||||||
|
DEPOSIT MI3 ILOOP 1
|
||||||
|
SET MI4 ENABLED
|
||||||
|
DEPOSIT MI4 ILOOP 1
|
||||||
|
SET MI5 ENABLED
|
||||||
|
DEPOSIT MI5 ILOOP 1
|
||||||
|
|
||||||
|
; And we're done ..
|
||||||
|
echo Type GO to start ...
|
21
H316/tests/imploop4.cmd
Normal file
21
H316/tests/imploop4.cmd
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
;; *** IMP LINE FOUR (ONLY!) LOOPBACK TEST ***
|
||||||
|
|
||||||
|
; Set the simulator configuration ...
|
||||||
|
echo IMP line four loopback test...
|
||||||
|
do impconfig.cmd
|
||||||
|
SET IMP NUM=2
|
||||||
|
|
||||||
|
; Load the IMP code ...
|
||||||
|
echo Loading IMP code ...
|
||||||
|
do impcode.cmd
|
||||||
|
|
||||||
|
; Start up the modem links!
|
||||||
|
SET MI1 DISABLED
|
||||||
|
SET MI2 DISABLED
|
||||||
|
SET MI3 DISABLED
|
||||||
|
SET MI4 ENABLED
|
||||||
|
DEPOSIT MI4 ILOOP 1
|
||||||
|
SET MI5 DISABLED
|
||||||
|
|
||||||
|
; And we're done ..
|
||||||
|
echo Type GO to start ...
|
61
H316/tests/mdmtest1.cmd
Normal file
61
H316/tests/mdmtest1.cmd
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
; TEST1 - send a test modem message
|
||||||
|
|
||||||
|
; Set up the configuration ...
|
||||||
|
RESET ALL
|
||||||
|
SET CPU 32K NOHSA DMA=0 DMC EXTINT=16
|
||||||
|
SET LPT DISABLED
|
||||||
|
SET MT DISABLED
|
||||||
|
SET CLK DISABLED
|
||||||
|
SET FHD DISABLED
|
||||||
|
SET DP DISABLED
|
||||||
|
SET IMP DISABLED
|
||||||
|
SET RTC DISABLED
|
||||||
|
SET WDT DISABLED
|
||||||
|
SET MI1 ENABLED
|
||||||
|
SET MI2 DISABLED
|
||||||
|
SET MI3 DISABLED
|
||||||
|
SET MI4 DISABLED
|
||||||
|
SET MI5 DISABLED
|
||||||
|
SET HI1 DISABLED
|
||||||
|
SET HI2 DISABLED
|
||||||
|
SET HI3 DISABLED
|
||||||
|
SET HI4 DISABLED
|
||||||
|
|
||||||
|
; Deposit the test message in memory at 000100..000107 ...
|
||||||
|
DEPOSIT ALL 0
|
||||||
|
DEPOSIT 100 100000
|
||||||
|
DEPOSIT 101 011111
|
||||||
|
DEPOSIT 102 122222
|
||||||
|
DEPOSIT 103 033333
|
||||||
|
DEPOSIT 104 144444
|
||||||
|
DEPOSIT 105 055555
|
||||||
|
DEPOSIT 106 166666
|
||||||
|
DEPOSIT 107 077777
|
||||||
|
|
||||||
|
; Store a little program to set up the DMC and do start modem output ..
|
||||||
|
DEPOSIT 32 100
|
||||||
|
DEPOSIT 33 107
|
||||||
|
DEPOSIT -m 10 OCP 0071
|
||||||
|
DEPOSIT -m 11 HLT
|
||||||
|
DEPOSIT P 10
|
||||||
|
|
||||||
|
; Tell the world ...
|
||||||
|
echo
|
||||||
|
echo Here are the DMC pointers before sending -
|
||||||
|
ex 32:33
|
||||||
|
echo
|
||||||
|
echo And here is the data we're sending -
|
||||||
|
ex 100:107
|
||||||
|
|
||||||
|
; Away we go!
|
||||||
|
echo
|
||||||
|
echo Starting simulation ...
|
||||||
|
ATTACH MI1 4431::4432
|
||||||
|
go
|
||||||
|
|
||||||
|
; All done...
|
||||||
|
echo
|
||||||
|
echo Here are the DMC pointers after sending ...
|
||||||
|
ex 32:33
|
||||||
|
|
||||||
|
|
54
H316/tests/mdmtest2.cmd
Normal file
54
H316/tests/mdmtest2.cmd
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
; TEST2 - receive a test modem message
|
||||||
|
|
||||||
|
; Set up the configuration ...
|
||||||
|
RESET ALL
|
||||||
|
SET CPU 32K NOHSA DMA=0 DMC EXTINT=16
|
||||||
|
SET LPT DISABLED
|
||||||
|
SET MT DISABLED
|
||||||
|
SET CLK DISABLED
|
||||||
|
SET FHD DISABLED
|
||||||
|
SET DP DISABLED
|
||||||
|
SET IMP DISABLED
|
||||||
|
SET RTC DISABLED
|
||||||
|
SET WDT DISABLED
|
||||||
|
SET MI1 ENABLED
|
||||||
|
SET MI2 DISABLED
|
||||||
|
SET MI3 DISABLED
|
||||||
|
SET MI4 DISABLED
|
||||||
|
SET MI5 DISABLED
|
||||||
|
SET HI1 DISABLED
|
||||||
|
SET HI2 DISABLED
|
||||||
|
SET HI3 DISABLED
|
||||||
|
SET HI4 DISABLED
|
||||||
|
|
||||||
|
; Clear the receiver buffer at 000100 ...
|
||||||
|
DEPOSIT ALL 0
|
||||||
|
|
||||||
|
; Store a little program to receive the message ...
|
||||||
|
DEPOSIT 20 100
|
||||||
|
DEPOSIT 21 177
|
||||||
|
DEPOSIT -m 10 OCP 0471
|
||||||
|
DEPOSIT -m 11 SKS 0271
|
||||||
|
DEPOSIT -m 12 JMP 11
|
||||||
|
DEPOSIT -m 13 HLT
|
||||||
|
DEPOSIT P 10
|
||||||
|
|
||||||
|
; Tell the world ...
|
||||||
|
echo
|
||||||
|
echo Here are the DMC pointers before receiving -
|
||||||
|
ex 20:21
|
||||||
|
|
||||||
|
; and wait for "GO" ...
|
||||||
|
echo
|
||||||
|
echo Starting simulation ...
|
||||||
|
ATTACH MI1 4432::4431
|
||||||
|
go
|
||||||
|
|
||||||
|
; All done ...
|
||||||
|
echo
|
||||||
|
echo Here is the data we received -
|
||||||
|
ex 100:107
|
||||||
|
echo
|
||||||
|
echo And here are the DMC pointers after receiving -
|
||||||
|
ex 20:21
|
||||||
|
echo
|
42
H316/tests/testrtc.cmd
Normal file
42
H316/tests/testrtc.cmd
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
; This is a super simple simh script to test the IMP RTC and verify that it is
|
||||||
|
; incrementing at the correct 100us interval. It simply waits for the clock
|
||||||
|
; count to overflow (which takes 65535 * 100us or about 6.5 seconds) and then
|
||||||
|
; repeats for a total of 10 iterations. If all is well, this loop should take
|
||||||
|
; pretty close to 65 seconds to complete.
|
||||||
|
;
|
||||||
|
; RLA [15-Jun-13]
|
||||||
|
echo
|
||||||
|
echo SIMH IMP RTC INTERVAL CALIBRATION TEST
|
||||||
|
|
||||||
|
; Turn on the RTC (this requires extended interrupt support)
|
||||||
|
set cpu extint=16
|
||||||
|
set rtc enabled
|
||||||
|
|
||||||
|
; Turn the clock on (OCP 40 ==> CLKON) ...
|
||||||
|
d 1000 030040
|
||||||
|
|
||||||
|
; Loop reading the clock register until it becomes negative ...
|
||||||
|
d 1001 131040
|
||||||
|
d -m 1002 HLT
|
||||||
|
d -m 1003 SMI
|
||||||
|
d -m 1004 JMP 1001
|
||||||
|
|
||||||
|
; Loop reading the clock register until it becomes positive again ...
|
||||||
|
d 1005 131040
|
||||||
|
d -m 1006 HLT
|
||||||
|
d -m 1007 SPL
|
||||||
|
d -m 1010 JMP 1005
|
||||||
|
|
||||||
|
; And repeat the above for ten iterations ...
|
||||||
|
d -m 1011 IRS 1015
|
||||||
|
d -m 1012 JMP 1001
|
||||||
|
d -m 1013 HLT
|
||||||
|
d -m 1014 0
|
||||||
|
d -m 1015 177766
|
||||||
|
|
||||||
|
; That's it...
|
||||||
|
d p 1000
|
||||||
|
echo Start your stopwatch and at the same moment type "GO".
|
||||||
|
echo The program should halt in exactly 65 seconds ...
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
Name="VCCLCompilerTool"
|
Name="VCCLCompilerTool"
|
||||||
Optimization="0"
|
Optimization="0"
|
||||||
AdditionalIncludeDirectories="./;../"
|
AdditionalIncludeDirectories="./;../"
|
||||||
PreprocessorDefinitions="_CRT_NONSTDC_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;SIM_NEED_GIT_COMMIT_ID"
|
PreprocessorDefinitions="_CRT_NONSTDC_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;SIM_NEED_GIT_COMMIT_ID;VM_IMPTIP"
|
||||||
MinimalRebuild="true"
|
MinimalRebuild="true"
|
||||||
BasicRuntimeChecks="3"
|
BasicRuntimeChecks="3"
|
||||||
RuntimeLibrary="1"
|
RuntimeLibrary="1"
|
||||||
|
@ -128,7 +128,7 @@
|
||||||
InlineFunctionExpansion="1"
|
InlineFunctionExpansion="1"
|
||||||
OmitFramePointers="true"
|
OmitFramePointers="true"
|
||||||
AdditionalIncludeDirectories="./;../"
|
AdditionalIncludeDirectories="./;../"
|
||||||
PreprocessorDefinitions="_CRT_NONSTDC_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;SIM_NEED_GIT_COMMIT_ID"
|
PreprocessorDefinitions="_CRT_NONSTDC_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;SIM_NEED_GIT_COMMIT_ID;VM_IMPTIP"
|
||||||
StringPooling="true"
|
StringPooling="true"
|
||||||
RuntimeLibrary="0"
|
RuntimeLibrary="0"
|
||||||
EnableFunctionLevelLinking="true"
|
EnableFunctionLevelLinking="true"
|
||||||
|
@ -201,14 +201,30 @@
|
||||||
RelativePath="..\H316\h316_fhd.c"
|
RelativePath="..\H316\h316_fhd.c"
|
||||||
>
|
>
|
||||||
</File>
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\H316\h316_hi.c"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\H316\h316_imp.c"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
<File
|
<File
|
||||||
RelativePath="..\H316\h316_lp.c"
|
RelativePath="..\H316\h316_lp.c"
|
||||||
>
|
>
|
||||||
</File>
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\H316\h316_mi.c"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
<File
|
<File
|
||||||
RelativePath="..\H316\h316_mt.c"
|
RelativePath="..\H316\h316_mt.c"
|
||||||
>
|
>
|
||||||
</File>
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\H316\h316_rtc.c"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
<File
|
<File
|
||||||
RelativePath="..\H316\h316_stddev.c"
|
RelativePath="..\H316\h316_stddev.c"
|
||||||
>
|
>
|
||||||
|
@ -217,6 +233,10 @@
|
||||||
RelativePath="..\H316\h316_sys.c"
|
RelativePath="..\H316\h316_sys.c"
|
||||||
>
|
>
|
||||||
</File>
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\H316\h316_udp.c"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
<File
|
<File
|
||||||
RelativePath="..\scp.c"
|
RelativePath="..\scp.c"
|
||||||
>
|
>
|
||||||
|
@ -266,6 +286,10 @@
|
||||||
RelativePath="..\H316\h316_defs.h"
|
RelativePath="..\H316\h316_defs.h"
|
||||||
>
|
>
|
||||||
</File>
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\H316\h316_imp.h"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
<File
|
<File
|
||||||
RelativePath="..\scp.h"
|
RelativePath="..\scp.h"
|
||||||
>
|
>
|
||||||
|
|
|
@ -383,8 +383,10 @@ H316_LIB = $(LIB_DIR)H316-$(ARCH).OLB
|
||||||
H316_SOURCE = $(H316_DIR)H316_STDDEV.C,$(H316_DIR)H316_LP.C,\
|
H316_SOURCE = $(H316_DIR)H316_STDDEV.C,$(H316_DIR)H316_LP.C,\
|
||||||
$(H316_DIR)H316_CPU.C,$(H316_DIR)H316_SYS.C,\
|
$(H316_DIR)H316_CPU.C,$(H316_DIR)H316_SYS.C,\
|
||||||
$(H316_DIR)H316_FHD.C,$(H316_DIR)H316_MT.C,\
|
$(H316_DIR)H316_FHD.C,$(H316_DIR)H316_MT.C,\
|
||||||
$(H316_DIR)H316_DP.C
|
$(H316_DIR)H316_DP.C,$(H316_DIR)H316_RTC.C,\
|
||||||
H316_OPTIONS = /INCL=($(SIMH_DIR),$(H316_DIR))/DEF=($(CC_DEFS))
|
$(H316_DIR)H316_IMP.C,$(H316_DIR)H316_HI.C,\
|
||||||
|
$(H316_DIR)H316_MI.C,$(H316_DIR)H316_UDP.C
|
||||||
|
H316_OPTIONS = /INCL=($(SIMH_DIR),$(H316_DIR))/DEF=($(CC_DEFS),"VM_IMPTIP=1")
|
||||||
|
|
||||||
#
|
#
|
||||||
# Hewlett-Packard HP-2100 Simulator Definitions.
|
# Hewlett-Packard HP-2100 Simulator Definitions.
|
||||||
|
|
BIN
doc/Summary of IMP IO Device Codes.doc
Normal file
BIN
doc/Summary of IMP IO Device Codes.doc
Normal file
Binary file not shown.
BIN
doc/h316_imp.doc
Normal file
BIN
doc/h316_imp.doc
Normal file
Binary file not shown.
5
makefile
5
makefile
|
@ -868,8 +868,9 @@ PDP8_OPT = -I ${PDP8D}
|
||||||
H316D = H316
|
H316D = H316
|
||||||
H316 = ${H316D}/h316_stddev.c ${H316D}/h316_lp.c ${H316D}/h316_cpu.c \
|
H316 = ${H316D}/h316_stddev.c ${H316D}/h316_lp.c ${H316D}/h316_cpu.c \
|
||||||
${H316D}/h316_sys.c ${H316D}/h316_mt.c ${H316D}/h316_fhd.c \
|
${H316D}/h316_sys.c ${H316D}/h316_mt.c ${H316D}/h316_fhd.c \
|
||||||
${H316D}/h316_dp.c
|
${H316D}/h316_dp.c ${H316D}/h316_rtc.c ${H316D}/h316_imp.c \
|
||||||
H316_OPT = -I ${H316D}
|
${H316D}/h316_hi.c ${H316D}/h316_mi.c ${H316D}/h316_udp.c
|
||||||
|
H316_OPT = -I ${H316D} -D VM_IMPTIP
|
||||||
|
|
||||||
|
|
||||||
HP2100D = HP2100
|
HP2100D = HP2100
|
||||||
|
|
Loading…
Add table
Reference in a new issue