Merge branch 'master' of github.com:simh/simh
This commit is contained in:
commit
a7c2d7bf35
35 changed files with 24965 additions and 147 deletions
314
H316/h316_cpu.c
314
H316/h316_cpu.c
|
@ -25,6 +25,12 @@
|
|||
|
||||
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 bugs in double precision, normalization, SC (Adrian Wise)
|
||||
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
|
||||
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_enb device interrupt enable flags
|
||||
dev_int[2] device interrupt 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
|
||||
at least one interrupt request is pending, then an interrupt occurs.
|
||||
The order of flags in these variables corresponds to the order
|
||||
|
@ -212,6 +225,9 @@
|
|||
*/
|
||||
|
||||
#include "h316_defs.h"
|
||||
#ifdef VM_IMPTIP
|
||||
#include "h316_imp.h"
|
||||
#endif
|
||||
|
||||
#define PCQ_SIZE 64 /* must be 2**n */
|
||||
#define PCQ_MASK (PCQ_SIZE - 1)
|
||||
|
@ -242,6 +258,7 @@ typedef struct {
|
|||
int32 xr;
|
||||
int32 ea;
|
||||
int32 opnd;
|
||||
t_bool iack; // [RLA] TRUE if an interrupt occurred
|
||||
} InstHistory;
|
||||
|
||||
uint16 M[MAXMEMSIZE] = { 0 }; /* memory */
|
||||
|
@ -259,6 +276,9 @@ int32 sc = 0; /* shift count */
|
|||
int32 ss[4]; /* sense switches */
|
||||
int32 dev_int = 0; /* dev ready */
|
||||
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 stop_inst = 1; /* stop on ill inst */
|
||||
int32 stop_dev = 2; /* stop on ill dev */
|
||||
|
@ -276,6 +296,9 @@ int32 hst_p = 0; /* history pointer */
|
|||
int32 hst_lnt = 0; /* history length */
|
||||
InstHistory *hst = NULL; /* instruction history */
|
||||
|
||||
extern int32 sim_int_char;
|
||||
extern DEVICE *sim_devices[];
|
||||
|
||||
t_bool devtab_init (void);
|
||||
int32 dmaio (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_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_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
|
||||
|
||||
|
@ -298,7 +326,7 @@ t_stat cpu_show_nchan (FILE *st, UNIT *uptr, int32 val, void *desc);
|
|||
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 = {
|
||||
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) },
|
||||
{ ORDATA (DEVINT, dev_int, 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) },
|
||||
{ BRDATA (DMAAD, dma_ad, 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 },
|
||||
{ MTAB_XTD | MTAB_VDV, 0, "channels", "CHANNELS",
|
||||
&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, UNIT_DMC, "DMC", "DMC", NULL },
|
||||
{ MTAB_XTD|MTAB_VDV|MTAB_NMO|MTAB_SHP, 0, "HISTORY", "HISTORY",
|
||||
&cpu_set_hist, &cpu_show_hist },
|
||||
{ MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "DMA1", NULL,
|
||||
NULL, &cpu_show_dma, 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 },
|
||||
{ MTAB_XTD | MTAB_VDV, 0, "extended interrupts", "EXTINT",
|
||||
&cpu_set_interrupts, &cpu_show_interrupts, NULL },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
|
@ -380,9 +406,10 @@ t_stat sim_instr (void)
|
|||
{
|
||||
int32 AR, BR, MB, Y, t1, t2, t3, skip, dev;
|
||||
uint32 ut;
|
||||
t_bool iack; // [RLA] TRUE if an interrupt was taken this cycle
|
||||
t_stat reason;
|
||||
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 Add31 (int32 val1, int32 val2);
|
||||
int32 Operate (int32 MB, int32 AR);
|
||||
|
@ -443,6 +470,7 @@ if (chan_req) { /* channel request? */
|
|||
return STOP_DMAER;
|
||||
if ((r = t >> IOT_V_REASON) != 0)
|
||||
return r;
|
||||
// [RLA] Note that we intentionally ignore address breaks here!
|
||||
Write (ad, t & DMASK); /* write to mem */
|
||||
}
|
||||
else { /* no, output */
|
||||
|
@ -464,6 +492,7 @@ if (chan_req) { /* channel request? */
|
|||
}
|
||||
else { /* DMC */
|
||||
st = (st & DMA_IN) | ((ad + 1) & X_AMASK);
|
||||
// [RLA] Note that we intentionally ignore address breaks here!
|
||||
Write (dmcad, st); /* update start */
|
||||
end = Read (dmcad + 1); /* get end */
|
||||
if (((ad ^ end) & X_AMASK) == 0) { /* start == end? */
|
||||
|
@ -479,13 +508,15 @@ if (chan_req) { /* channel request? */
|
|||
|
||||
/* Interrupts */
|
||||
|
||||
if ((dev_int & (INT_PEND|INT_NMI|dev_enb)) > INT_PEND) {/* int req? */
|
||||
pme = ext; /* save extend */
|
||||
if (cpu_unit.flags & UNIT_EXT) /* ext opt? extend on */
|
||||
ext = 1;
|
||||
dev_int = dev_int & ~INT_ON; /* intr off */
|
||||
MB = 0120000 | M_INT; /* inst = JST* 63 */
|
||||
}
|
||||
//[RLA] Todo - add WDT interrupts ????
|
||||
iack = FALSE;
|
||||
if ((dev_int & (INT_PEND|INT_NMI|dev_enb)) > INT_PEND) { // [RLA] check for standard interrupt
|
||||
MB = cpu_interrupt(M_INT); iack = TRUE;
|
||||
}
|
||||
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 */
|
||||
|
||||
|
@ -512,6 +543,7 @@ if (hst_lnt) { /* instr hist? */
|
|||
hst[hst_p].ar = AR;
|
||||
hst[hst_p].br = BR;
|
||||
hst[hst_p].xr = XR;
|
||||
hst[hst_p].iack = iack; // [RLA] record if interrupt taken
|
||||
}
|
||||
|
||||
/* Memory reference instructions */
|
||||
|
@ -547,9 +579,9 @@ switch (I_GETOP (MB)) { /* case on <1:6> */
|
|||
case 004: case 024: case 044: case 064: /* STA */
|
||||
if ((reason = Ea (MB, &Y))) /* eff addr */
|
||||
break;
|
||||
Write (Y, AR); /* store A */
|
||||
if ((reason = Write(Y, AR))) break; /* [RLA] store A */
|
||||
if (dp) { /* double prec? */
|
||||
Write (Y | 1, BR); /* store B */
|
||||
if ((reason = Write(Y | 1, BR))) break; /* [RLA] store B */
|
||||
sc = 0;
|
||||
}
|
||||
break;
|
||||
|
@ -590,7 +622,7 @@ switch (I_GETOP (MB)) { /* case on <1:6> */
|
|||
if ((reason = Ea (MB, &Y))) /* eff addr */
|
||||
break;
|
||||
MB = NEWA (Read (Y), PC); /* merge old PC */
|
||||
Write (Y, MB);
|
||||
if ((reason = Write(Y, MB))) break; // [RLA]
|
||||
PCQ_ENTRY;
|
||||
PC = NEWA (PC, Y + 1); /* set new PC */
|
||||
break;
|
||||
|
@ -609,7 +641,7 @@ switch (I_GETOP (MB)) { /* case on <1:6> */
|
|||
if ((reason = Ea (MB, &Y))) /* eff addr */
|
||||
break;
|
||||
MB = (Read (Y) + 1) & DMASK; /* incr, rewrite */
|
||||
Write (Y, MB);
|
||||
if ((reason = Write(Y, MB))) break; // [RLA]
|
||||
if (MB == 0) /* skip if zero */
|
||||
PC = NEWA (PC, PC + 1);
|
||||
break;
|
||||
|
@ -618,14 +650,14 @@ switch (I_GETOP (MB)) { /* case on <1:6> */
|
|||
if ((reason = Ea (MB, &Y))) /* eff addr */
|
||||
break;
|
||||
MB = Read (Y);
|
||||
Write (Y, AR); /* A to mem */
|
||||
if ((reason = Write(Y, AR))) break; /* [RLA] A to mem */
|
||||
AR = MB; /* mem to A */
|
||||
break;
|
||||
|
||||
case 015: case 055: /* STX */
|
||||
if ((reason = Ea (MB & ~IDX, &Y))) /* eff addr */
|
||||
break;
|
||||
Write (Y, XR); /* store XR */
|
||||
if ((reason = Write(Y, XR))) break; /* [RLA] store XR */
|
||||
break;
|
||||
|
||||
case 035: case 075: /* LDX */
|
||||
|
@ -695,7 +727,11 @@ switch (I_GETOP (MB)) { /* case on <1:6> */
|
|||
|
||||
case 074: /* OTA */
|
||||
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;
|
||||
if (t2 & IOT_SKIP) /* skip? */
|
||||
PC = NEWA (PC, PC + 1);
|
||||
|
@ -1055,13 +1091,18 @@ return SCPE_OK;
|
|||
|
||||
/* 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;
|
||||
if (addr == M_XR) /* write XR loc? */
|
||||
XR = val; /* update XR */
|
||||
return;
|
||||
if (addr == M_XR) /* write XR loc? */
|
||||
XR = val;
|
||||
// [RLA] Implement "break on memory write" ...
|
||||
if (sim_brk_summ && sim_brk_test (addr, SWMASK ('W')))
|
||||
return STOP_IBKPT;
|
||||
else
|
||||
return SCPE_OK;
|
||||
}
|
||||
|
||||
/* Add */
|
||||
|
@ -1086,6 +1127,51 @@ else C = 0;
|
|||
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 */
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/* [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 */
|
||||
|
||||
int32 dmaio (int32 inst, int32 fnc, int32 dat, int32 dev)
|
||||
|
@ -1287,7 +1430,7 @@ C = 0;
|
|||
dp = 0;
|
||||
ext = pme = extoff_pending = 0;
|
||||
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++)
|
||||
dma_ad[i] = dma_wc[i] = dma_eor[i] = 0;
|
||||
chan_req = 0;
|
||||
|
@ -1295,7 +1438,10 @@ pcq_r = find_reg ("PCQ", NULL, dptr);
|
|||
if (pcq_r)
|
||||
pcq_r->qptr = 0;
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -1349,6 +1495,28 @@ for (i = MEMSIZE; i < MAXMEMSIZE; i++)
|
|||
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)
|
||||
{
|
||||
uint32 i, newmax;
|
||||
|
@ -1373,7 +1541,7 @@ t_stat cpu_show_nchan (FILE *st, UNIT *uptr, int32 val, void *desc)
|
|||
{
|
||||
if (dma_nch)
|
||||
fprintf (st, "DMA channels = %d", dma_nch);
|
||||
else fprintf (st, "no DMA channels");
|
||||
else fprintf (st, "no DMA");
|
||||
return SCPE_OK;
|
||||
}
|
||||
|
||||
|
@ -1481,17 +1649,42 @@ return SCPE_OK;
|
|||
|
||||
/* 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)
|
||||
{
|
||||
DEVICE *dptr;
|
||||
DIB *dibp;
|
||||
uint32 i, j, dno, chan;
|
||||
uint32 i, j, dno;
|
||||
|
||||
for (i = 0; i < DEV_MAX; i++)
|
||||
iotab[i] = NULL;
|
||||
for (i = 0; i < (DMA_MAX + DMC_MAX); i++)
|
||||
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 */
|
||||
if ((dibp == NULL) || (dptr->flags & DEV_DIS)) /* exist, enabled? */
|
||||
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 */
|
||||
} /* end for */
|
||||
if (dibp->chan) { /* DMA/DMC? */
|
||||
chan = dibp->chan - 1;
|
||||
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 */
|
||||
}
|
||||
// [RLA] set up the channel map
|
||||
if (dibp->chan != 0)
|
||||
if (set_chanmap(dptr, dibp, dno, dibp->chan-1)) return TRUE;
|
||||
if (dibp->chan2 != 0)
|
||||
if (set_chanmap(dptr, dibp, dno, dibp->chan2-1)) return TRUE;
|
||||
// [RLA] If the device uses extended interrupts, check that they're enabled.
|
||||
if ((dibp->inum != INT_V_NONE) && (dibp->inum >= INT_V_EXTD) && (ext_ints == 0)) {
|
||||
printf ("%s uses extended interrupts but that option is disabled\n", sim_dname (dptr));
|
||||
if (sim_log)
|
||||
fprintf (sim_log, "%s uses extended interrupts but that option is disabled\n", sim_dname (dptr));
|
||||
return TRUE;
|
||||
}
|
||||
} /* end for */
|
||||
for (i = 0; i < DEV_MAX; i++) { /* fill in blanks */
|
||||
if (iotab[i] == NULL)
|
||||
|
@ -1598,7 +1775,8 @@ else lnt = hst_lnt;
|
|||
di = hst_p - lnt; /* work forward */
|
||||
if (di < 0)
|
||||
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 */
|
||||
h = &hst[(++di) % hst_lnt]; /* entry pointer */
|
||||
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 */
|
||||
if (has_opnd[op])
|
||||
fprintf (st, " [%06o]", h->opnd);
|
||||
if (h->iack) // [RLA]
|
||||
fprintf(st, " INTERRUPT"); // [RLA]
|
||||
fputc ('\n', st); /* end line */
|
||||
} /* end else instruction */
|
||||
} /* end for */
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
used in advertising or otherwise to promote the sale, use or other dealings
|
||||
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)
|
||||
22-May-10 RMS Added check for 64b definitions
|
||||
15-Feb-05 RMS Added start button interrupt
|
||||
|
@ -110,11 +111,15 @@
|
|||
/* Device information block */
|
||||
|
||||
struct h316_dib {
|
||||
uint32 dev; /* device number */
|
||||
uint32 chan; /* dma/dmc channel */
|
||||
uint32 num; /* number of slots */
|
||||
int32 (*io) (int32 inst, int32 fnc, int32 dat, int32 dev); };
|
||||
|
||||
uint32 dev; /* device number */
|
||||
uint32 num; /* number of slots */
|
||||
uint32 chan; /* dma/dmc channel */
|
||||
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;
|
||||
|
||||
/* DMA/DMC channel numbers */
|
||||
|
@ -142,33 +147,35 @@ typedef struct h316_dib DIB;
|
|||
|
||||
/* I/O device codes */
|
||||
|
||||
#define PTR 001 /* paper tape reader */
|
||||
#define PTP 002 /* paper tape punch */
|
||||
#define LPT 003 /* line printer */
|
||||
#define TTY 004 /* console */
|
||||
#define CDR 005 /* card reader */
|
||||
#define MT 010 /* mag tape data */
|
||||
#define CLK_KEYS 020 /* clock/keys (CPU) */
|
||||
#define FHD 022 /* fixed head disk */
|
||||
#define DMA 024 /* DMA control */
|
||||
#define DP 025 /* moving head disk */
|
||||
#define PTR 001 /* paper tape reader */
|
||||
#define PTP 002 /* paper tape punch */
|
||||
#define LPT 003 /* line printer */
|
||||
#define TTY 004 /* console */
|
||||
#define CDR 005 /* card reader */
|
||||
#define MT 010 /* mag tape data */
|
||||
#define CLK_KEYS 020 /* clock/keys (CPU) */
|
||||
#define FHD 022 /* fixed head disk */
|
||||
#define DMA 024 /* DMA control */
|
||||
#define DP 025 /* moving head disk */
|
||||
#define DEV_MAX 64
|
||||
|
||||
/* Interrupt flags, definitions correspond to SMK bits */
|
||||
|
||||
#define INT_V_CLK 0 /* clock */
|
||||
#define INT_V_MPE 1 /* parity error */
|
||||
#define INT_V_LPT 2 /* line printer */
|
||||
#define INT_V_CDR 4 /* card reader */
|
||||
#define INT_V_TTY 5 /* teletype */
|
||||
#define INT_V_PTP 6 /* paper tape punch */
|
||||
#define INT_V_PTR 7 /* paper tape reader */
|
||||
#define INT_V_FHD 8 /* fixed head disk */
|
||||
#define INT_V_DP 12 /* moving head disk */
|
||||
#define INT_V_MT 15 /* mag tape */
|
||||
#define INT_V_START 16 /* start button */
|
||||
#define INT_V_NODEF 17 /* int not deferred */
|
||||
#define INT_V_ON 18 /* int on */
|
||||
#define INT_V_CLK 0 /* clock */
|
||||
#define INT_V_MPE 1 /* parity error */
|
||||
#define INT_V_LPT 2 /* line printer */
|
||||
#define INT_V_CDR 4 /* card reader */
|
||||
#define INT_V_TTY 5 /* teletype */
|
||||
#define INT_V_PTP 6 /* paper tape punch */
|
||||
#define INT_V_PTR 7 /* paper tape reader */
|
||||
#define INT_V_FHD 8 /* fixed head disk */
|
||||
#define INT_V_DP 12 /* moving head disk */
|
||||
#define INT_V_MT 15 /* mag tape */
|
||||
#define INT_V_START 16 /* start button */
|
||||
#define INT_V_NODEF 17 /* int not deferred */
|
||||
#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 */
|
||||
|
||||
|
@ -195,14 +202,22 @@ typedef struct h316_dib DIB;
|
|||
#define INT_NMI (INT_START)
|
||||
#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 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 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 */
|
||||
|
||||
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_dmc (UNIT *uptr, int32 val, char *cptr, void *desc);
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
4651 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)
|
||||
04-Sep-05 RMS Fixed missing return (Peter Schorn)
|
||||
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
|
||||
*/
|
||||
|
||||
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[] = {
|
||||
{ UDATA (&dp_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
fhd 516-4400 fixed head disk
|
||||
|
||||
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)
|
||||
15-May-06 RMS Fixed bug in autosize attach (David Gesswein)
|
||||
04-Jan-04 RMS Changed sim_fsize calling sequence
|
||||
|
@ -114,7 +115,7 @@ uint32 fhd_csword (uint32 cs, uint32 ch);
|
|||
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 = {
|
||||
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
|
||||
|
||||
3-Jul-13 RLA compatibility changes for extended interrupts
|
||||
09-Jun-07 RMS Fixed lost last print line (Theo Engel)
|
||||
19-Jan-06 RMS Added UNIT_TEXT flag
|
||||
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
|
||||
*/
|
||||
|
||||
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) };
|
||||
|
||||
|
|
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
|
||||
|
||||
3-Jul-13 RLA compatibility changes for extended interrupts
|
||||
19-Mar-12 RMS Fixed declaration of chan_req (Mark Pizzolato)
|
||||
09-Jun-07 RMS Fixed bug in write without stop (Theo Engel)
|
||||
16-Feb-06 RMS Added tape capacity checking
|
||||
|
@ -119,7 +120,7 @@ void mt_wrwd (UNIT *uptr, uint32 dat);
|
|||
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[] = {
|
||||
{ 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
|
||||
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)
|
||||
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)
|
||||
|
@ -158,7 +159,7 @@ t_stat ttp_write (int32 c);
|
|||
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 = {
|
||||
UDATA (&ptr_svc, UNIT_SEQ+UNIT_ATTABLE+UNIT_ROABLE, 0),
|
||||
|
@ -203,7 +204,7 @@ DEVICE ptr_dev = {
|
|||
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 = {
|
||||
UDATA (&ptp_svc, UNIT_SEQ+UNIT_ATTABLE, 0), SERIAL_OUT_WAIT
|
||||
|
@ -243,7 +244,7 @@ DEVICE ptp_dev = {
|
|||
#define TTR 2
|
||||
#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[] = {
|
||||
{ UDATA (&tti_svc, TT_MODE_KSR, 0), KBD_POLL_WAIT },
|
||||
|
@ -308,7 +309,7 @@ DEVICE tty_dev = {
|
|||
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 };
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
used in advertising or otherwise to promote the sale, use or other dealings
|
||||
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
|
||||
24-Oct-03 RMS Added DMA/DMC support
|
||||
17-Sep-01 RMS Removed multiconsole support
|
||||
|
@ -41,6 +42,11 @@ extern DEVICE clk_dev;
|
|||
extern DEVICE dp_dev;
|
||||
extern DEVICE fhd_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 uint16 M[];
|
||||
|
||||
|
@ -64,12 +70,19 @@ DEVICE *sim_devices[] = {
|
|||
&cpu_dev,
|
||||
&ptr_dev,
|
||||
&ptp_dev,
|
||||
&tty_dev,
|
||||
&lpt_dev,
|
||||
&clk_dev,
|
||||
&dp_dev,
|
||||
&fhd_dev,
|
||||
&tty_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
|
||||
};
|
||||
|
||||
|
|
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
|
13422
H316/tests/c-listing-ps.txt
Normal file
13422
H316/tests/c-listing-ps.txt
Normal file
File diff suppressed because it is too large
Load diff
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 ...
|
||||
|
||||
|
|
@ -68,6 +68,7 @@ A remote console session will close when an EOF character is entered (i.e. ^D or
|
|||
Serial Console Support
|
||||
Separate TCP listening ports per line
|
||||
Outgoing connections per line (virtual Null Modem cable).
|
||||
Packet sending and reception semantics for simulated network device support using either TCP or UDP transport.
|
||||
|
||||
#### Asynchronous I/O
|
||||
* Disk and Tape I/O can be asynchronous. Asynchronous support exists
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
Name="VCCLCompilerTool"
|
||||
Optimization="0"
|
||||
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"
|
||||
BasicRuntimeChecks="3"
|
||||
RuntimeLibrary="1"
|
||||
|
@ -128,7 +128,7 @@
|
|||
InlineFunctionExpansion="1"
|
||||
OmitFramePointers="true"
|
||||
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"
|
||||
RuntimeLibrary="0"
|
||||
EnableFunctionLevelLinking="true"
|
||||
|
@ -201,14 +201,30 @@
|
|||
RelativePath="..\H316\h316_fhd.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\H316\h316_hi.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\H316\h316_imp.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\H316\h316_lp.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\H316\h316_mi.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\H316\h316_mt.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\H316\h316_rtc.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\H316\h316_stddev.c"
|
||||
>
|
||||
|
@ -217,6 +233,10 @@
|
|||
RelativePath="..\H316\h316_sys.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\H316\h316_udp.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\scp.c"
|
||||
>
|
||||
|
@ -266,6 +286,10 @@
|
|||
RelativePath="..\H316\h316_defs.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\H316\h316_imp.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
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_DIR)H316_CPU.C,$(H316_DIR)H316_SYS.C,\
|
||||
$(H316_DIR)H316_FHD.C,$(H316_DIR)H316_MT.C,\
|
||||
$(H316_DIR)H316_DP.C
|
||||
H316_OPTIONS = /INCL=($(SIMH_DIR),$(H316_DIR))/DEF=($(CC_DEFS))
|
||||
$(H316_DIR)H316_DP.C,$(H316_DIR)H316_RTC.C,\
|
||||
$(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.
|
||||
|
|
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
|
||||
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_dp.c
|
||||
H316_OPT = -I ${H316D}
|
||||
${H316D}/h316_dp.c ${H316D}/h316_rtc.c ${H316D}/h316_imp.c \
|
||||
${H316D}/h316_hi.c ${H316D}/h316_mi.c ${H316D}/h316_udp.c
|
||||
H316_OPT = -I ${H316D} -D VM_IMPTIP
|
||||
|
||||
|
||||
HP2100D = HP2100
|
||||
|
|
75
sim_sock.c
75
sim_sock.c
|
@ -69,6 +69,8 @@
|
|||
/* OS dependent routines
|
||||
|
||||
sim_master_sock create master socket
|
||||
sim_connect_sock connect a socket to a remote destination
|
||||
sim_connect_sock_ex connect a socket to a remote destination
|
||||
sim_accept_conn accept connection
|
||||
sim_read_sock read from socket
|
||||
sim_write_sock write from socket
|
||||
|
@ -99,6 +101,11 @@ SOCKET sim_connect_sock (const char *hostport, const char *default_host, const c
|
|||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
SOCKET sim_connect_sock_ex (const char *sourcehostport, const char *hostport, const char *default_host, const char *default_port, t_bool datagram)
|
||||
{
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
SOCKET sim_accept_conn (SOCKET master, char **connectaddr);
|
||||
{
|
||||
return INVALID_SOCKET;
|
||||
|
@ -710,12 +717,12 @@ return 0;
|
|||
|
||||
#endif /* endif !Win32 && !VMS */
|
||||
|
||||
static SOCKET sim_create_sock (int af)
|
||||
static SOCKET sim_create_sock_ex (int af, t_bool datagram)
|
||||
{
|
||||
SOCKET newsock;
|
||||
int32 err;
|
||||
|
||||
newsock = socket (af, SOCK_STREAM, 0); /* create socket */
|
||||
newsock = socket (af, (datagram ? SOCK_DGRAM : SOCK_STREAM), 0);/* create socket */
|
||||
if (newsock == INVALID_SOCKET) { /* socket error? */
|
||||
err = WSAGetLastError ();
|
||||
#if defined(WSAEAFNOSUPPORT)
|
||||
|
@ -727,6 +734,11 @@ if (newsock == INVALID_SOCKET) { /* socket error? */
|
|||
return newsock;
|
||||
}
|
||||
|
||||
static SOCKET sim_create_sock (int af)
|
||||
{
|
||||
return sim_create_sock_ex (af, FALSE);
|
||||
}
|
||||
|
||||
/*
|
||||
Some platforms and/or network stacks have varying support for listening on
|
||||
an IPv6 socket and receiving connections from both IPv4 and IPv6 client
|
||||
|
@ -823,27 +835,70 @@ return newsock; /* got it! */
|
|||
|
||||
SOCKET sim_connect_sock (const char *hostport, const char *default_host, const char *default_port)
|
||||
{
|
||||
return sim_connect_sock_ex (NULL, hostport, default_host, default_port, FALSE);
|
||||
}
|
||||
|
||||
SOCKET sim_connect_sock_ex (const char *sourcehostport, const char *hostport, const char *default_host, const char *default_port, t_bool datagram)
|
||||
{
|
||||
SOCKET newsock = INVALID_SOCKET;
|
||||
int32 sta;
|
||||
char host[CBUFSIZE], port[CBUFSIZE];
|
||||
t_stat r;
|
||||
struct addrinfo hints;
|
||||
struct addrinfo *result = NULL;
|
||||
struct addrinfo *result = NULL, *source = NULL;
|
||||
|
||||
r = sim_parse_addr (hostport, host, sizeof(host), default_host, port, sizeof(port), default_port, NULL);
|
||||
if (r != SCPE_OK)
|
||||
return newsock;
|
||||
return INVALID_SOCKET;
|
||||
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_protocol = IPPROTO_TCP;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_protocol = (datagram ? IPPROTO_UDP : IPPROTO_TCP);
|
||||
hints.ai_socktype = (datagram ? SOCK_DGRAM : SOCK_STREAM);
|
||||
if (p_getaddrinfo(host[0] ? host : NULL, port[0] ? port : NULL, &hints, &result))
|
||||
return newsock;
|
||||
newsock = sim_create_sock (result->ai_family); /* create socket */
|
||||
return INVALID_SOCKET;
|
||||
|
||||
if (sourcehostport) {
|
||||
|
||||
/* Validate the local/source side address which we'll bind to */
|
||||
r = sim_parse_addr (sourcehostport, host, sizeof(host), NULL, port, sizeof(port), NULL, NULL);
|
||||
if (r != SCPE_OK) {
|
||||
p_freeaddrinfo (result);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
hints.ai_family = result->ai_family; /* Same family as connect destination */
|
||||
hints.ai_protocol = (datagram ? IPPROTO_UDP : IPPROTO_TCP);
|
||||
hints.ai_socktype = (datagram ? SOCK_DGRAM : SOCK_STREAM);
|
||||
if (p_getaddrinfo(host[0] ? host : NULL, port[0] ? port : NULL, &hints, &source)) {
|
||||
p_freeaddrinfo (result);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
newsock = sim_create_sock_ex (result->ai_family, datagram);/* create socket */
|
||||
if (newsock == INVALID_SOCKET) { /* socket error? */
|
||||
p_freeaddrinfo (result);
|
||||
p_freeaddrinfo (source);
|
||||
return newsock;
|
||||
}
|
||||
|
||||
sta = bind (newsock, source->ai_addr, source->ai_addrlen);
|
||||
p_freeaddrinfo(source);
|
||||
source = NULL;
|
||||
if (sta == SOCKET_ERROR) { /* bind error? */
|
||||
p_freeaddrinfo (result);
|
||||
return sim_err_sock (newsock, "bind", 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (newsock == INVALID_SOCKET) { /* socket error? */
|
||||
p_freeaddrinfo (result);
|
||||
return newsock;
|
||||
newsock = sim_create_sock_ex (result->ai_family, datagram);/* create socket */
|
||||
if (newsock == INVALID_SOCKET) { /* socket error? */
|
||||
p_freeaddrinfo (result);
|
||||
return newsock;
|
||||
}
|
||||
}
|
||||
|
||||
sta = sim_setnonblock (newsock); /* set nonblocking */
|
||||
|
|
|
@ -104,6 +104,7 @@
|
|||
t_stat sim_parse_addr (const char *cptr, char *host, size_t hostlen, const char *default_host, char *port, size_t port_len, const char *default_port, const char *validate_addr);
|
||||
SOCKET sim_master_sock (const char *hostport, t_stat *parse_status);
|
||||
SOCKET sim_connect_sock (const char *hostport, const char *default_host, const char *default_port);
|
||||
SOCKET sim_connect_sock_ex (const char *sourcehostport, const char *hostport, const char *default_host, const char *default_port, t_bool datagram);
|
||||
SOCKET sim_accept_conn (SOCKET master, char **connectaddr);
|
||||
int32 sim_check_conn (SOCKET sock, t_bool rd);
|
||||
int32 sim_read_sock (SOCKET sock, char *buf, int32 nbytes);
|
||||
|
|
79
sim_tmxr.c
79
sim_tmxr.c
|
@ -862,6 +862,10 @@ if (lp->destination || lp->port || lp->txlogname) {
|
|||
sprintf (growstring(&tptr, 32), ",Buffered=%d", lp->txbsz);
|
||||
if (!lp->txbfd && (lp->mp->buffered > 0))
|
||||
sprintf (growstring(&tptr, 32), ",UnBuffered");
|
||||
if (lp->mp->datagram != lp->datagram)
|
||||
sprintf (growstring(&tptr, 8), ",%s", lp->datagram ? "UDP" : "TCP");
|
||||
if (lp->port)
|
||||
sprintf (growstring(&tptr, 12 + strlen (lp->port)), ",%s%s", lp->port, ((lp->mp->notelnet != lp->notelnet) && (!lp->datagram)) ? (lp->notelnet ? ";notelnet" : ";telnet") : "");
|
||||
if (lp->destination) {
|
||||
if (lp->serport) {
|
||||
char portname[CBUFSIZE];
|
||||
|
@ -870,10 +874,8 @@ if (lp->destination || lp->port || lp->txlogname) {
|
|||
sprintf (growstring(&tptr, 25 + strlen (lp->destination)), ",Connect=%s%s%s", portname, strcmp("9600-8N1", lp->serconfig) ? ";" : "", strcmp("9600-8N1", lp->serconfig) ? lp->serconfig : "");
|
||||
}
|
||||
else
|
||||
sprintf (growstring(&tptr, 25 + strlen (lp->destination)), ",Connect=%s%s", lp->destination, (lp->mp->notelnet != lp->notelnet) ? (lp->notelnet ? ";notelnet" : ";telnet") : "");
|
||||
sprintf (growstring(&tptr, 25 + strlen (lp->destination)), ",Connect=%s%s", lp->destination, ((lp->mp->notelnet != lp->notelnet) && (!lp->datagram)) ? (lp->notelnet ? ";notelnet" : ";telnet") : "");
|
||||
}
|
||||
if (lp->port)
|
||||
sprintf (growstring(&tptr, 12 + strlen (lp->port)), ",%s%s", lp->port, (lp->mp->notelnet != lp->notelnet) ? (lp->notelnet ? ";notelnet" : ";telnet") : "");
|
||||
if (lp->txlogname)
|
||||
sprintf (growstring(&tptr, 12 + strlen (lp->txlogname)), ",Log=%s", lp->txlogname);
|
||||
if (lp->loopback)
|
||||
|
@ -1136,7 +1138,7 @@ for (i = 0; i < mp->lines; i++) { /* check each line in se
|
|||
(!lp->modem_control || (lp->modembits & TMXR_MDM_DTR))) {
|
||||
sprintf (msg, "tmxr_poll_conn() - establishing outgoing connection to: %s", lp->destination);
|
||||
tmxr_debug_connect_line (lp, msg);
|
||||
lp->connecting = sim_connect_sock (lp->destination, "localhost", NULL);
|
||||
lp->connecting = sim_connect_sock_ex (lp->datagram ? lp->port : NULL, lp->destination, "localhost", NULL, lp->datagram);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1204,7 +1206,7 @@ if ((lp->destination) && (!lp->serport)) {
|
|||
if ((!lp->modem_control) || (lp->modembits & TMXR_MDM_DTR)) {
|
||||
sprintf (msg, "tmxr_reset_ln_ex() - connecting to %s", lp->destination);
|
||||
tmxr_debug_connect_line (lp, msg);
|
||||
lp->connecting = sim_connect_sock (lp->destination, "localhost", NULL);
|
||||
lp->connecting = sim_connect_sock_ex (lp->datagram ? lp->port : NULL, lp->destination, "localhost", NULL, lp->datagram);
|
||||
}
|
||||
}
|
||||
tmxr_init_line (lp); /* initialize line state */
|
||||
|
@ -1382,7 +1384,7 @@ if (lp->mp && lp->modem_control) { /* This API ONLY works on mo
|
|||
|
||||
sprintf (msg, "tmxr_set_get_modem_bits() - establishing outgoing connection to: %s", lp->destination);
|
||||
tmxr_debug_connect_line (lp, msg);
|
||||
lp->connecting = sim_connect_sock (lp->destination, "localhost", NULL);
|
||||
lp->connecting = sim_connect_sock_ex (lp->datagram ? lp->port : NULL, lp->destination, "localhost", NULL, lp->datagram);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1900,6 +1902,8 @@ if (nbytes) { /* >0? write */
|
|||
lp->txbpr = 0;
|
||||
lp->txcnt = lp->txcnt + sbytes; /* update counts */
|
||||
nbytes = nbytes - sbytes;
|
||||
if ((nbytes == 0) && (lp->datagram)) /* if Empty buffer on datagram line */
|
||||
lp->txbpi = lp->txbpr = 0; /* Start next packet at beginning of buffer */
|
||||
}
|
||||
if (sbytes < 0) { /* I/O Error? */
|
||||
lp->txbpi = lp->txbpr = 0; /* Drop the data we already know we can't send */
|
||||
|
@ -2012,7 +2016,7 @@ char tbuf[CBUFSIZE], listen[CBUFSIZE], destination[CBUFSIZE],
|
|||
SOCKET sock;
|
||||
SERHANDLE serport;
|
||||
char *tptr = cptr;
|
||||
t_bool nolog, notelnet, listennotelnet, unbuffered, modem_control, loopback;
|
||||
t_bool nolog, notelnet, listennotelnet, unbuffered, modem_control, loopback, datagram;
|
||||
TMLN *lp;
|
||||
t_stat r = SCPE_ARG;
|
||||
|
||||
|
@ -2031,6 +2035,7 @@ while (*tptr) {
|
|||
memset(port, '\0', sizeof(port));
|
||||
memset(option, '\0', sizeof(option));
|
||||
nolog = notelnet = listennotelnet = unbuffered = loopback = FALSE;
|
||||
datagram = mp->datagram;
|
||||
if (line != -1)
|
||||
notelnet = listennotelnet = mp->notelnet;
|
||||
modem_control = mp->modem_control;
|
||||
|
@ -2100,6 +2105,18 @@ while (*tptr) {
|
|||
modem_control = TRUE;
|
||||
continue;
|
||||
}
|
||||
if ((0 == MATCH_CMD (gbuf, "DATAGRAM")) || (0 == MATCH_CMD (gbuf, "UDP"))) {
|
||||
if ((NULL != cptr) && ('\0' != *cptr))
|
||||
return SCPE_2MARG;
|
||||
notelnet = datagram = TRUE;
|
||||
continue;
|
||||
}
|
||||
if ((0 == MATCH_CMD (gbuf, "STREAM")) || (0 == MATCH_CMD (gbuf, "TCP"))) {
|
||||
if ((NULL != cptr) && ('\0' != *cptr))
|
||||
return SCPE_2MARG;
|
||||
datagram = FALSE;
|
||||
continue;
|
||||
}
|
||||
if (0 == MATCH_CMD (gbuf, "CONNECT")) {
|
||||
if ((NULL == cptr) || ('\0' == *cptr))
|
||||
return SCPE_ARG;
|
||||
|
@ -2114,21 +2131,24 @@ while (*tptr) {
|
|||
strncpy (hostport, cptr, sizeof(hostport)-1);
|
||||
if ((cptr = strchr (hostport, ';')))
|
||||
*(cptr++) = '\0';
|
||||
sock = sim_connect_sock (hostport, "localhost", NULL);
|
||||
if (sock != INVALID_SOCKET)
|
||||
sim_close_sock (sock, 0);
|
||||
else
|
||||
return SCPE_ARG;
|
||||
if (cptr) {
|
||||
get_glyph (cptr, cptr, 0); /* upcase this string */
|
||||
if (0 == MATCH_CMD (cptr, "NOTELNET"))
|
||||
notelnet = TRUE;
|
||||
else
|
||||
if (0 == MATCH_CMD (cptr, "TELNET"))
|
||||
notelnet = FALSE;
|
||||
if (datagram)
|
||||
return SCPE_ARG;
|
||||
else
|
||||
notelnet = FALSE;
|
||||
else
|
||||
return SCPE_ARG;
|
||||
}
|
||||
sock = sim_connect_sock_ex (NULL, hostport, "localhost", NULL, datagram);
|
||||
if (sock != INVALID_SOCKET)
|
||||
sim_close_sock (sock, 0);
|
||||
else
|
||||
return SCPE_ARG;
|
||||
cptr = hostport;
|
||||
}
|
||||
strcpy(destination, cptr);
|
||||
|
@ -2231,7 +2251,7 @@ while (*tptr) {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (listen[0]) {
|
||||
if ((listen[0]) && (!datagram)) {
|
||||
sock = sim_master_sock (listen, &r); /* make master socket */
|
||||
if (r != SCPE_OK)
|
||||
return r;
|
||||
|
@ -2306,7 +2326,16 @@ while (*tptr) {
|
|||
}
|
||||
}
|
||||
else {
|
||||
sock = sim_connect_sock (destination, "localhost", NULL);
|
||||
lp->datagram = datagram;
|
||||
if (datagram) {
|
||||
if (listen[0]) {
|
||||
lp->port = (char *)realloc (lp->port, 1 + strlen (listen));
|
||||
strcpy(lp->port, listen); /* save port */
|
||||
}
|
||||
else
|
||||
return SCPE_ARG;
|
||||
}
|
||||
sock = sim_connect_sock_ex (datagram ? listen : NULL, destination, "localhost", NULL, datagram);
|
||||
if (sock != INVALID_SOCKET) {
|
||||
_mux_detach_line (lp, FALSE, TRUE);
|
||||
lp->destination = (char *)malloc(1+strlen(destination));
|
||||
|
@ -2370,7 +2399,7 @@ while (*tptr) {
|
|||
lp->txlog = NULL;
|
||||
}
|
||||
}
|
||||
if (listen[0]) {
|
||||
if ((listen[0]) && (!datagram)) {
|
||||
if ((mp->lines == 1) && (mp->master)) /* single line mux can have either line specific OR mux listener but NOT both */
|
||||
return SCPE_ARG;
|
||||
sock = sim_master_sock (listen, &r); /* make master socket */
|
||||
|
@ -2409,7 +2438,16 @@ while (*tptr) {
|
|||
}
|
||||
}
|
||||
else {
|
||||
sock = sim_connect_sock (destination, "localhost", NULL);
|
||||
lp->datagram = datagram;
|
||||
if (datagram) {
|
||||
if (listen[0]) {
|
||||
lp->port = (char *)realloc (lp->port, 1 + strlen (listen));
|
||||
strcpy(lp->port, listen); /* save port */
|
||||
}
|
||||
else
|
||||
return SCPE_ARG;
|
||||
}
|
||||
sock = sim_connect_sock_ex (datagram ? listen : NULL, destination, "localhost", NULL, datagram);
|
||||
if (sock != INVALID_SOCKET) {
|
||||
_mux_detach_line (lp, FALSE, TRUE);
|
||||
lp->destination = (char *)malloc(1+strlen(destination));
|
||||
|
@ -3673,7 +3711,10 @@ if (ln >= 0)
|
|||
|
||||
if ((lp->sock) || (lp->connecting)) { /* tcp connection? */
|
||||
if (lp->destination) /* remote connection? */
|
||||
fprintf (st, "Connection to remote port %s\n", lp->destination);/* print port name */
|
||||
if (lp->datagram)
|
||||
fprintf (st, "Datagram Connection from %s to remote port %s\n", lp->port, lp->destination);/* print port name */
|
||||
else
|
||||
fprintf (st, "Connection to remote port %s\n", lp->destination);/* print port name */
|
||||
else /* incoming connection */
|
||||
fprintf (st, "Connection from IP address %s\n", lp->ipad);
|
||||
}
|
||||
|
@ -3689,7 +3730,7 @@ if (lp->sock) {
|
|||
free (peername);
|
||||
}
|
||||
|
||||
if (lp->port)
|
||||
if ((lp->port) && (!lp->datagram))
|
||||
fprintf (st, "Listening on port %s\n", lp->port); /* print port name */
|
||||
|
||||
if (lp->serport) /* serial connection? */
|
||||
|
|
|
@ -114,7 +114,7 @@ struct tmln {
|
|||
int32 tsta; /* Telnet state */
|
||||
int32 rcve; /* rcv enable */
|
||||
int32 xmte; /* xmt enable */
|
||||
int32 dstb; /* disable Tlnt bin */
|
||||
int32 dstb; /* disable Telnet binary mode */
|
||||
t_bool notelnet; /* raw binary data (no telnet interpretation) */
|
||||
int32 rxbpr; /* rcv buf remove */
|
||||
int32 rxbpi; /* rcv buf insert */
|
||||
|
@ -151,6 +151,7 @@ struct tmln {
|
|||
char *destination; /* Outgoing destination address:port */
|
||||
t_bool loopback; /* Line in loopback mode */
|
||||
t_bool halfduplex; /* Line in half-duplex mode */
|
||||
t_bool datagram; /* Line is datagram packet oriented */
|
||||
int32 lpbpr; /* loopback buf remove */
|
||||
int32 lpbpi; /* loopback buf insert */
|
||||
int32 lpbcnt; /* loopback buf used count */
|
||||
|
@ -176,6 +177,7 @@ struct tmxr {
|
|||
uint32 last_poll_time; /* time of last connection poll */
|
||||
t_bool notelnet; /* default telnet capability for incoming connections */
|
||||
t_bool modem_control; /* multiplexer supports modem control behaviors */
|
||||
t_bool datagram; /* Lines are datagram packet oriented */
|
||||
};
|
||||
|
||||
int32 tmxr_poll_conn (TMXR *mp);
|
||||
|
|
Loading…
Add table
Reference in a new issue