Merge branch 'master' of github.com:simh/simh

This commit is contained in:
Mark Pizzolato 2013-11-25 05:01:32 -08:00
commit a7c2d7bf35
35 changed files with 24965 additions and 147 deletions

View file

@ -25,6 +25,12 @@
cpu H316/H516 CPU cpu H316/H516 CPU
21-May-13 RLA Add IMP/TIP support
Move SMK/OTK instructions here (from CLK)
Make SET CPU DMA work as documented
Implement extended interrupts
Add "interrupt taken" flag to CPU HISTORY
Add "break on write" breakpoints
19-Nov-11 RMS Fixed XR behavior (Adrian Wise) 19-Nov-11 RMS Fixed XR behavior (Adrian Wise)
19-Nov-11 RMS Fixed bugs in double precision, normalization, SC (Adrian Wise) 19-Nov-11 RMS Fixed bugs in double precision, normalization, SC (Adrian Wise)
10-Jan-10 RMS Fixed bugs in LDX, STX introduced in 3.8-1 (Theo Engel) 10-Jan-10 RMS Fixed bugs in LDX, STX introduced in 3.8-1 (Theo Engel)
@ -176,12 +182,19 @@
unknown I/O device and stop_dev flag set unknown I/O device and stop_dev flag set
I/O error in I/O simulator I/O error in I/O simulator
2. Interrupts. Interrupts are maintained by two parallel variables: 2. Interrupts. Interrupts are maintained by parallel variables:
dev_int device interrupt flags dev_int[2] device interrupt flags
dev_enb device interrupt enable flags dev_enb[2] device interrupt enable flags
In addition, dev_int contains the interrupt enable and interrupt no Note that these are actually arrays of two 16 bit words each. The first
word of each vector contains the bits for the standard interrupt devices,
and the second word is the bits for the extended interrupts 1..17. The
IMP uses these extended interrupts, however this was a standard H316 option
and is in no way IMP specific. Actually the H316 supported up to 48 extra
interrupts, but it seems like overkill to implement them all.
In addition, dev_int[0] contains the interrupt enable and interrupt no
defer flags. If interrupt enable and interrupt no defer are set, and defer flags. If interrupt enable and interrupt no defer are set, and
at least one interrupt request is pending, then an interrupt occurs. at least one interrupt request is pending, then an interrupt occurs.
The order of flags in these variables corresponds to the order The order of flags in these variables corresponds to the order
@ -212,6 +225,9 @@
*/ */
#include "h316_defs.h" #include "h316_defs.h"
#ifdef VM_IMPTIP
#include "h316_imp.h"
#endif
#define PCQ_SIZE 64 /* must be 2**n */ #define PCQ_SIZE 64 /* must be 2**n */
#define PCQ_MASK (PCQ_SIZE - 1) #define PCQ_MASK (PCQ_SIZE - 1)
@ -242,6 +258,7 @@ typedef struct {
int32 xr; int32 xr;
int32 ea; int32 ea;
int32 opnd; int32 opnd;
t_bool iack; // [RLA] TRUE if an interrupt occurred
} InstHistory; } InstHistory;
uint16 M[MAXMEMSIZE] = { 0 }; /* memory */ uint16 M[MAXMEMSIZE] = { 0 }; /* memory */
@ -259,6 +276,9 @@ int32 sc = 0; /* shift count */
int32 ss[4]; /* sense switches */ int32 ss[4]; /* sense switches */
int32 dev_int = 0; /* dev ready */ int32 dev_int = 0; /* dev ready */
int32 dev_enb = 0; /* dev enable */ int32 dev_enb = 0; /* dev enable */
uint32 ext_ints = 0; // [RLA] 16 if extended interrupts enabled
uint16 dev_ext_int = 0; // [RLA] extended interrupt request bitmap
uint16 dev_ext_enb = 0; // [RLA] extended interrupt enable bitmap
int32 ind_max = 8; /* iadr nest limit */ int32 ind_max = 8; /* iadr nest limit */
int32 stop_inst = 1; /* stop on ill inst */ int32 stop_inst = 1; /* stop on ill inst */
int32 stop_dev = 2; /* stop on ill dev */ int32 stop_dev = 2; /* stop on ill dev */
@ -276,6 +296,9 @@ int32 hst_p = 0; /* history pointer */
int32 hst_lnt = 0; /* history length */ int32 hst_lnt = 0; /* history length */
InstHistory *hst = NULL; /* instruction history */ InstHistory *hst = NULL; /* instruction history */
extern int32 sim_int_char;
extern DEVICE *sim_devices[];
t_bool devtab_init (void); t_bool devtab_init (void);
int32 dmaio (int32 inst, int32 fnc, int32 dat, int32 dev); int32 dmaio (int32 inst, int32 fnc, int32 dat, int32 dev);
int32 undio (int32 inst, int32 fnc, int32 dat, int32 dev); int32 undio (int32 inst, int32 fnc, int32 dat, int32 dev);
@ -289,6 +312,11 @@ t_stat cpu_show_hist (FILE *st, UNIT *uptr, int32 val, void *desc);
t_stat cpu_show_dma (FILE *st, UNIT *uptr, int32 val, void *desc); t_stat cpu_show_dma (FILE *st, UNIT *uptr, int32 val, void *desc);
t_stat cpu_set_nchan (UNIT *uptr, int32 val, char *cptr, void *desc); t_stat cpu_set_nchan (UNIT *uptr, int32 val, char *cptr, void *desc);
t_stat cpu_show_nchan (FILE *st, UNIT *uptr, int32 val, void *desc); t_stat cpu_show_nchan (FILE *st, UNIT *uptr, int32 val, void *desc);
t_stat cpu_set_interrupts (UNIT *uptr, int32 val, char *cptr, void *desc);
t_stat cpu_show_interrupts (FILE *st, UNIT *uptr, int32 val, void *desc);
int32 sim_ota_2024 (int32 inst, int32 fnc, int32 dat, int32 dev);
int32 cpu_interrupt (int32 vec);
int32 cpu_ext_interrupt (void);
/* CPU data structures /* CPU data structures
@ -298,7 +326,7 @@ t_stat cpu_show_nchan (FILE *st, UNIT *uptr, int32 val, void *desc);
cpu_mod CPU modifiers list cpu_mod CPU modifiers list
*/ */
DIB cpu_dib = { DMA, IOBUS, 1, &dmaio }; DIB cpu_dib = { DMA, 1, IOBUS, IOBUS, INT_V_NONE, INT_V_NONE, &dmaio, 0 };
UNIT cpu_unit = { UNIT cpu_unit = {
UDATA (NULL, UNIT_FIX+UNIT_BINK+UNIT_EXT+UNIT_HSA+UNIT_DMC, MAXMEMSIZE) UDATA (NULL, UNIT_FIX+UNIT_BINK+UNIT_EXT+UNIT_HSA+UNIT_DMC, MAXMEMSIZE)
@ -324,6 +352,8 @@ REG cpu_reg[] = {
{ FLDATA (START, dev_int, INT_V_START) }, { FLDATA (START, dev_int, INT_V_START) },
{ ORDATA (DEVINT, dev_int, 16), REG_RO }, { ORDATA (DEVINT, dev_int, 16), REG_RO },
{ ORDATA (DEVENB, dev_enb, 16), REG_RO }, { ORDATA (DEVENB, dev_enb, 16), REG_RO },
{ ORDATA (EXTINT, dev_ext_int, 16), REG_RO },
{ ORDATA (EXTENB, dev_ext_enb, 16), REG_RO },
{ ORDATA (CHREQ, chan_req, DMA_MAX + DMC_MAX) }, { ORDATA (CHREQ, chan_req, DMA_MAX + DMC_MAX) },
{ BRDATA (DMAAD, dma_ad, 8, 16, DMA_MAX) }, { BRDATA (DMAAD, dma_ad, 8, 16, DMA_MAX) },
{ BRDATA (DMAWC, dma_wc, 8, 16, DMA_MAX) }, { BRDATA (DMAWC, dma_wc, 8, 16, DMA_MAX) },
@ -353,18 +383,14 @@ MTAB cpu_mod[] = {
{ UNIT_MSIZE, 32768, NULL, "32K", &cpu_set_size }, { UNIT_MSIZE, 32768, NULL, "32K", &cpu_set_size },
{ MTAB_XTD | MTAB_VDV, 0, "channels", "CHANNELS", { MTAB_XTD | MTAB_VDV, 0, "channels", "CHANNELS",
&cpu_set_nchan, &cpu_show_nchan, NULL }, &cpu_set_nchan, &cpu_show_nchan, NULL },
{ MTAB_XTD | MTAB_VDV, 0, NULL, "DMA", // [RLA] this is the way it's
&cpu_set_nchan, NULL, NULL }, // [RLA] documented to work!
{ UNIT_DMC, 0, "no DMC", "NODMC", NULL }, { UNIT_DMC, 0, "no DMC", "NODMC", NULL },
{ UNIT_DMC, UNIT_DMC, "DMC", "DMC", NULL }, { UNIT_DMC, UNIT_DMC, "DMC", "DMC", NULL },
{ MTAB_XTD|MTAB_VDV|MTAB_NMO|MTAB_SHP, 0, "HISTORY", "HISTORY", { MTAB_XTD|MTAB_VDV|MTAB_NMO|MTAB_SHP, 0, "HISTORY", "HISTORY",
&cpu_set_hist, &cpu_show_hist }, &cpu_set_hist, &cpu_show_hist },
{ MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "DMA1", NULL, { MTAB_XTD | MTAB_VDV, 0, "extended interrupts", "EXTINT",
NULL, &cpu_show_dma, NULL }, &cpu_set_interrupts, &cpu_show_interrupts, NULL },
{ MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DMA2", NULL,
NULL, &cpu_show_dma, NULL },
{ MTAB_XTD | MTAB_VDV | MTAB_NMO, 2, "DMA3", NULL,
NULL, &cpu_show_dma, NULL },
{ MTAB_XTD | MTAB_VDV | MTAB_NMO, 3, "DMA4", NULL,
NULL, &cpu_show_dma, NULL },
{ 0 } { 0 }
}; };
@ -380,9 +406,10 @@ t_stat sim_instr (void)
{ {
int32 AR, BR, MB, Y, t1, t2, t3, skip, dev; int32 AR, BR, MB, Y, t1, t2, t3, skip, dev;
uint32 ut; uint32 ut;
t_bool iack; // [RLA] TRUE if an interrupt was taken this cycle
t_stat reason; t_stat reason;
t_stat Ea (int32 inst, int32 *addr); t_stat Ea (int32 inst, int32 *addr);
void Write (int32 addr, int32 val); t_stat Write (int32 addr, int32 val); // [RLA] Write() can now cause a break
int32 Add16 (int32 val1, int32 val2); int32 Add16 (int32 val1, int32 val2);
int32 Add31 (int32 val1, int32 val2); int32 Add31 (int32 val1, int32 val2);
int32 Operate (int32 MB, int32 AR); int32 Operate (int32 MB, int32 AR);
@ -443,6 +470,7 @@ if (chan_req) { /* channel request? */
return STOP_DMAER; return STOP_DMAER;
if ((r = t >> IOT_V_REASON) != 0) if ((r = t >> IOT_V_REASON) != 0)
return r; return r;
// [RLA] Note that we intentionally ignore address breaks here!
Write (ad, t & DMASK); /* write to mem */ Write (ad, t & DMASK); /* write to mem */
} }
else { /* no, output */ else { /* no, output */
@ -464,6 +492,7 @@ if (chan_req) { /* channel request? */
} }
else { /* DMC */ else { /* DMC */
st = (st & DMA_IN) | ((ad + 1) & X_AMASK); st = (st & DMA_IN) | ((ad + 1) & X_AMASK);
// [RLA] Note that we intentionally ignore address breaks here!
Write (dmcad, st); /* update start */ Write (dmcad, st); /* update start */
end = Read (dmcad + 1); /* get end */ end = Read (dmcad + 1); /* get end */
if (((ad ^ end) & X_AMASK) == 0) { /* start == end? */ if (((ad ^ end) & X_AMASK) == 0) { /* start == end? */
@ -479,12 +508,14 @@ if (chan_req) { /* channel request? */
/* Interrupts */ /* Interrupts */
if ((dev_int & (INT_PEND|INT_NMI|dev_enb)) > INT_PEND) {/* int req? */ //[RLA] Todo - add WDT interrupts ????
pme = ext; /* save extend */ iack = FALSE;
if (cpu_unit.flags & UNIT_EXT) /* ext opt? extend on */ if ((dev_int & (INT_PEND|INT_NMI|dev_enb)) > INT_PEND) { // [RLA] check for standard interrupt
ext = 1; MB = cpu_interrupt(M_INT); iack = TRUE;
dev_int = dev_int & ~INT_ON; /* intr off */ }
MB = 0120000 | M_INT; /* inst = JST* 63 */ else if ( ((dev_ext_int & dev_ext_enb) != 0) // [RLA] check for extended interrupt
&& ((dev_int & INT_PEND) == INT_PEND) ) {
MB = cpu_ext_interrupt(); iack = TRUE;
} }
/* Instruction fetch */ /* Instruction fetch */
@ -512,6 +543,7 @@ if (hst_lnt) { /* instr hist? */
hst[hst_p].ar = AR; hst[hst_p].ar = AR;
hst[hst_p].br = BR; hst[hst_p].br = BR;
hst[hst_p].xr = XR; hst[hst_p].xr = XR;
hst[hst_p].iack = iack; // [RLA] record if interrupt taken
} }
/* Memory reference instructions */ /* Memory reference instructions */
@ -547,9 +579,9 @@ switch (I_GETOP (MB)) { /* case on <1:6> */
case 004: case 024: case 044: case 064: /* STA */ case 004: case 024: case 044: case 064: /* STA */
if ((reason = Ea (MB, &Y))) /* eff addr */ if ((reason = Ea (MB, &Y))) /* eff addr */
break; break;
Write (Y, AR); /* store A */ if ((reason = Write(Y, AR))) break; /* [RLA] store A */
if (dp) { /* double prec? */ if (dp) { /* double prec? */
Write (Y | 1, BR); /* store B */ if ((reason = Write(Y | 1, BR))) break; /* [RLA] store B */
sc = 0; sc = 0;
} }
break; break;
@ -590,7 +622,7 @@ switch (I_GETOP (MB)) { /* case on <1:6> */
if ((reason = Ea (MB, &Y))) /* eff addr */ if ((reason = Ea (MB, &Y))) /* eff addr */
break; break;
MB = NEWA (Read (Y), PC); /* merge old PC */ MB = NEWA (Read (Y), PC); /* merge old PC */
Write (Y, MB); if ((reason = Write(Y, MB))) break; // [RLA]
PCQ_ENTRY; PCQ_ENTRY;
PC = NEWA (PC, Y + 1); /* set new PC */ PC = NEWA (PC, Y + 1); /* set new PC */
break; break;
@ -609,7 +641,7 @@ switch (I_GETOP (MB)) { /* case on <1:6> */
if ((reason = Ea (MB, &Y))) /* eff addr */ if ((reason = Ea (MB, &Y))) /* eff addr */
break; break;
MB = (Read (Y) + 1) & DMASK; /* incr, rewrite */ MB = (Read (Y) + 1) & DMASK; /* incr, rewrite */
Write (Y, MB); if ((reason = Write(Y, MB))) break; // [RLA]
if (MB == 0) /* skip if zero */ if (MB == 0) /* skip if zero */
PC = NEWA (PC, PC + 1); PC = NEWA (PC, PC + 1);
break; break;
@ -618,14 +650,14 @@ switch (I_GETOP (MB)) { /* case on <1:6> */
if ((reason = Ea (MB, &Y))) /* eff addr */ if ((reason = Ea (MB, &Y))) /* eff addr */
break; break;
MB = Read (Y); MB = Read (Y);
Write (Y, AR); /* A to mem */ if ((reason = Write(Y, AR))) break; /* [RLA] A to mem */
AR = MB; /* mem to A */ AR = MB; /* mem to A */
break; break;
case 015: case 055: /* STX */ case 015: case 055: /* STX */
if ((reason = Ea (MB & ~IDX, &Y))) /* eff addr */ if ((reason = Ea (MB & ~IDX, &Y))) /* eff addr */
break; break;
Write (Y, XR); /* store XR */ if ((reason = Write(Y, XR))) break; /* [RLA] store XR */
break; break;
case 035: case 075: /* LDX */ case 035: case 075: /* LDX */
@ -695,6 +727,10 @@ switch (I_GETOP (MB)) { /* case on <1:6> */
case 074: /* OTA */ case 074: /* OTA */
dev = MB & DEVMASK; dev = MB & DEVMASK;
// [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); t2 = iotab[dev] (ioOTA, I_GETFNC (MB), AR, dev);
reason = t2 >> IOT_V_REASON; reason = t2 >> IOT_V_REASON;
if (t2 & IOT_SKIP) /* skip? */ if (t2 & IOT_SKIP) /* skip? */
@ -1055,13 +1091,18 @@ return SCPE_OK;
/* Write memory */ /* Write memory */
void Write (int32 addr, int32 val) t_stat Write (int32 addr, int32 val)
{ {
// [RLA] Write() now checks for address breaks ...
if (((addr == 0) || (addr >= 020)) && MEM_ADDR_OK (addr)) if (((addr == 0) || (addr >= 020)) && MEM_ADDR_OK (addr))
M[addr] = val; M[addr] = val;
if (addr == M_XR) /* write XR loc? */ if (addr == M_XR) /* write XR loc? */
XR = val; /* update XR */ XR = val;
return; // [RLA] Implement "break on memory write" ...
if (sim_brk_summ && sim_brk_test (addr, SWMASK ('W')))
return STOP_IBKPT;
else
return SCPE_OK;
} }
/* Add */ /* Add */
@ -1086,6 +1127,51 @@ else C = 0;
return r; return r;
} }
// [RLA] Standard (fixed vector) interrupt action ...
int32 cpu_interrupt (int32 vec) {
pme = ext; /* save extend */
if (cpu_unit.flags & UNIT_EXT) ext = 1; /* ext opt? extend on */
dev_int = dev_int & ~INT_ON; /* intr off */
return 0120000 | vec; /* inst = JST* vector */
}
// [RLA] Extended (priority) interrupt action ...
int32 cpu_ext_interrupt (void) {
// Unlike the standard interrupts, which have a fixed vector shared by all
// devices, the extended interrupts have a unique vector for every device.
// Moreover, extended interrupts are prioritized so that the lowest numbered
// interrupts have priority. That means we have to actually scan the bitmap
// of active interrupts to figure out which one to take.
//
// One uncomfortable thing about the external interrupts is that it appears
// that they were edge triggered - once an interrupt on a given level was
// granted, that interrupt wouldn't occur again until another edge occurred on
// the same request. I'm "uncomfortable" with this because it's different from
// the way the standard interrupt works - that's completely level sensitive.
// Still, this Honeywell document
//
// http://bitsavers.informatik.uni-stuttgart.de/pdf/honeywell/series16/h316/70130072167D_316_Interfacing_Apr73.pdf
//
// (read Chapter 4, Priority Interrupts, the very first paragraph) at least
// seems to imply edge triggering. And the IMP firmware is written as if they
// are edge triggered - there are many cases (modem output, task, RTC) where
// the IMP code does nothing to clear the interrupt request flag. So we're
// going with edge triggered version for now...
int32 i; uint16 m, irq;
irq = dev_ext_int & dev_ext_enb;
for (i = 1, m = SIGN; m != 0; ++i, m >>= 1) {
if ((irq & m) != 0) {
// Extended interrupts are edge triggered (see above) - when this
// interrupt is granted, clear the request ...
CLR_EXT_INT(m);
return cpu_interrupt(M_INT+i);
}
}
// If we get here, it means that we were called with no interrupt bits set.
// That really should never happen, so just HALT ...
return(0);
}
/* Unimplemented I/O device */ /* Unimplemented I/O device */
int32 undio (int32 op, int32 fnc, int32 val, int32 dev) int32 undio (int32 op, int32 fnc, int32 val, int32 dev)
@ -1093,6 +1179,63 @@ int32 undio (int32 op, int32 fnc, int32 val, int32 dev)
return ((stop_dev << IOT_V_REASON) | val); return ((stop_dev << IOT_V_REASON) | val);
} }
/* [RLA] Special I/O devices */
int32 sim_ota_2024 (int32 inst, int32 fnc, int32 dat, int32 dev)
{
// OTA instructions with a device code of 20 or 24 are really SMK
// (Set interrupt Mask) instructions. OTA 20 sets the standard H316
// interrupt mask, and OTA 120, OTA 220 and OTA 320 set the extended
// interrupt mask (of which only one, OTA 120, is used by the IMP).
//
// Further, OTA 1020 is the OTK instruction which sets special CPU
// flags (single or double precision HSA, extended addressing mode,
// the carry flag, etc).
//
// The original simh implementation handled the regular SMK and OTK
// as special cases in the CLK device. Why the CLK device??? Because
// it also uses device code 20! Shame - these have nothing to do with
// the clock!
//
// This routine implements these special OTKs as part of the CPU.
// That allows us to implement the extra interrupt masks needed by the
// IMP, and it also allows the CLK device to be disabled without losing
// the SMK or OTK instructions. The clock was an option on the original
// H316 and is not required to be present, and the IMP in particular
// needs it to be disabled.
// Although OTA 24 is reserved nothing we currently simulate uses it!
if (dev == 024) return IOBADFNC (dat);
// Device code 20...
switch (fnc) {
case 000: // SMK 020 - set standard interrupt mask
dev_enb = dat; break;
case 001: // SMK 120 - set extended interrupt mask #1
if (ext_ints < 16) return IOBADFNC(dat);
dev_ext_enb = dat; break;
case 002: // SMK 220 - set extended interrupt mask #2
case 003: // SMK 320 - set extended interrupt mask #3
return IOBADFNC(dat);
case 010: // OTK - output keys
C = (dat >> 15) & 1; /* set C */
if (cpu_unit.flags & UNIT_HSA) /* HSA included? */
dp = (dat >> 14) & 1; /* set dp */
if (cpu_unit.flags & UNIT_EXT) { /* ext opt? */
if (dat & 020000) { /* ext set? */
ext = 1; /* yes, set */
extoff_pending = 0;
}
else extoff_pending = 1; /* no, clr later */
}
sc = dat & 037; /* set sc */
break;
default:
return IOBADFNC (dat);
}
return dat;
}
/* DMA control */ /* DMA control */
int32 dmaio (int32 inst, int32 fnc, int32 dat, int32 dev) int32 dmaio (int32 inst, int32 fnc, int32 dat, int32 dev)
@ -1287,7 +1430,7 @@ C = 0;
dp = 0; dp = 0;
ext = pme = extoff_pending = 0; ext = pme = extoff_pending = 0;
dev_int = dev_int & ~(INT_PEND|INT_NMI); dev_int = dev_int & ~(INT_PEND|INT_NMI);
dev_enb = 0; dev_ext_int = dev_enb = dev_ext_enb = 0;
for (i = 0; i < DMA_MAX; i++) for (i = 0; i < DMA_MAX; i++)
dma_ad[i] = dma_wc[i] = dma_eor[i] = 0; dma_ad[i] = dma_wc[i] = dma_eor[i] = 0;
chan_req = 0; chan_req = 0;
@ -1295,7 +1438,10 @@ pcq_r = find_reg ("PCQ", NULL, dptr);
if (pcq_r) if (pcq_r)
pcq_r->qptr = 0; pcq_r->qptr = 0;
else return SCPE_IERR; else return SCPE_IERR;
sim_brk_types = sim_brk_dflt = SWMASK ('E'); // [RLA] We now have two break types - "E" (break on execution) and also "W"
// [RLA] (break on write)...
sim_brk_types = SWMASK('W') | SWMASK('E');
sim_brk_dflt = SWMASK ('E');
return SCPE_OK; return SCPE_OK;
} }
@ -1349,6 +1495,28 @@ for (i = MEMSIZE; i < MAXMEMSIZE; i++)
return SCPE_OK; return SCPE_OK;
} }
/* [RLA] Set/Show number of interrupts supported */
t_stat cpu_set_interrupts (UNIT *uptr, int32 val, char *cptr, void *desc)
{
uint32 newint; t_stat ret;
if (cptr == NULL) return SCPE_ARG;
newint = get_uint (cptr, 10, 49, &ret);
if (ret != SCPE_OK) return ret;
if ((newint != 0) && (newint != 16)) return SCPE_ARG;
ext_ints = newint;
return SCPE_OK;
}
t_stat cpu_show_interrupts (FILE *st, UNIT *uptr, int32 val, void *desc)
{
if (ext_ints == 0)
fprintf(st,"standard interrupts");
else
fprintf(st,"extended interrupts = %d", ext_ints);
return SCPE_OK;
}
t_stat cpu_set_nchan (UNIT *uptr, int32 val, char *cptr, void *desc) t_stat cpu_set_nchan (UNIT *uptr, int32 val, char *cptr, void *desc)
{ {
uint32 i, newmax; uint32 i, newmax;
@ -1373,7 +1541,7 @@ t_stat cpu_show_nchan (FILE *st, UNIT *uptr, int32 val, void *desc)
{ {
if (dma_nch) if (dma_nch)
fprintf (st, "DMA channels = %d", dma_nch); fprintf (st, "DMA channels = %d", dma_nch);
else fprintf (st, "no DMA channels"); else fprintf (st, "no DMA");
return SCPE_OK; return SCPE_OK;
} }
@ -1481,11 +1649,36 @@ return SCPE_OK;
/* Set up I/O dispatch and channel maps */ /* Set up I/O dispatch and channel maps */
// [RLA] Check for DMC conflicts (on both DMC channels!) ...
t_bool set_chanmap (DEVICE *dptr, DIB *dibp, uint32 dno, uint32 chan)
{
if ((chan < DMC_V_DMC1) && (chan >= dma_nch)) {
printf ("%s configured for DMA channel %d\n", sim_dname (dptr), chan + 1);
if (sim_log)
fprintf (sim_log, "%s configured for DMA channel %d\n", sim_dname (dptr), chan + 1);
return TRUE;
}
if ((chan >= DMC_V_DMC1) && !(cpu_unit.flags & UNIT_DMC)) {
printf ("%s configured for DMC, option disabled\n", sim_dname (dptr));
if (sim_log)
fprintf (sim_log, "%s configured for DMC, option disabled\n", sim_dname (dptr));
return TRUE;
}
if (chan_map[chan]) { /* channel conflict? */
printf ("%s DMA/DMC channel conflict, devno = %02o\n", sim_dname (dptr), dno);
if (sim_log)
fprintf (sim_log, "%s DMA/DMC channel conflict, devno = %02o\n", sim_dname (dptr), dno);
return TRUE;
}
chan_map[chan] = dno; /* channel back map */
return FALSE;
}
t_bool devtab_init (void) t_bool devtab_init (void)
{ {
DEVICE *dptr; DEVICE *dptr;
DIB *dibp; DIB *dibp;
uint32 i, j, dno, chan; uint32 i, j, dno;
for (i = 0; i < DEV_MAX; i++) for (i = 0; i < DEV_MAX; i++)
iotab[i] = NULL; iotab[i] = NULL;
@ -1507,34 +1700,18 @@ for (i = 0; (dptr = sim_devices[i]); i++) { /* loop thru devices *
} }
iotab[dno + j] = dibp->io; /* set I/O routine */ iotab[dno + j] = dibp->io; /* set I/O routine */
} /* end for */ } /* end for */
if (dibp->chan) { /* DMA/DMC? */ // [RLA] set up the channel map
chan = dibp->chan - 1; if (dibp->chan != 0)
if ((chan < DMC_V_DMC1) && (chan >= dma_nch)) { if (set_chanmap(dptr, dibp, dno, dibp->chan-1)) return TRUE;
printf ("%s configured for DMA channel %d\n", if (dibp->chan2 != 0)
sim_dname (dptr), chan + 1); if (set_chanmap(dptr, dibp, dno, dibp->chan2-1)) return TRUE;
// [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) if (sim_log)
fprintf (sim_log, "%s configured for DMA channel %d\n", fprintf (sim_log, "%s uses extended interrupts but that option is disabled\n", sim_dname (dptr));
sim_dname (dptr), chan + 1);
return TRUE; 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 */
}
} /* end for */ } /* end for */
for (i = 0; i < DEV_MAX; i++) { /* fill in blanks */ for (i = 0; i < DEV_MAX; i++) { /* fill in blanks */
if (iotab[i] == NULL) if (iotab[i] == NULL)
@ -1598,7 +1775,8 @@ else lnt = hst_lnt;
di = hst_p - lnt; /* work forward */ di = hst_p - lnt; /* work forward */
if (di < 0) if (di < 0)
di = di + hst_lnt; di = di + hst_lnt;
fprintf (st, "PC C A B X ea IR\n\n"); fprintf (st, " PC C A B X ea IR\n");
fprintf (st, "----- - ------ ------ ------ ----- -----------\n\n");
for (k = 0; k < lnt; k++) { /* print specified */ for (k = 0; k < lnt; k++) { /* print specified */
h = &hst[(++di) % hst_lnt]; /* entry pointer */ h = &hst[(++di) % hst_lnt]; /* entry pointer */
if (h->pc & HIST_PC) { /* instruction? */ if (h->pc & HIST_PC) { /* instruction? */
@ -1615,6 +1793,8 @@ for (k = 0; k < lnt; k++) { /* print specified */
op = I_GETOP (h->ir) & 017; /* base op */ op = I_GETOP (h->ir) & 017; /* base op */
if (has_opnd[op]) if (has_opnd[op])
fprintf (st, " [%06o]", h->opnd); fprintf (st, " [%06o]", h->opnd);
if (h->iack) // [RLA]
fprintf(st, " INTERRUPT"); // [RLA]
fputc ('\n', st); /* end line */ fputc ('\n', st); /* end line */
} /* end else instruction */ } /* end else instruction */
} /* end for */ } /* end for */

View file

@ -23,6 +23,7 @@
used in advertising or otherwise to promote the sale, use or other dealings used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization from Robert M Supnik. in this Software without prior written authorization from Robert M Supnik.
31-May-13 RLA DIB - add second channel, interrupt and user parameter
19-Nov-11 RMS Removed XR macro, added XR_LOC macro (from Adrian Wise) 19-Nov-11 RMS Removed XR macro, added XR_LOC macro (from Adrian Wise)
22-May-10 RMS Added check for 64b definitions 22-May-10 RMS Added check for 64b definitions
15-Feb-05 RMS Added start button interrupt 15-Feb-05 RMS Added start button interrupt
@ -111,10 +112,14 @@
struct h316_dib { struct h316_dib {
uint32 dev; /* device number */ uint32 dev; /* device number */
uint32 chan; /* dma/dmc channel */
uint32 num; /* number of slots */ uint32 num; /* number of slots */
int32 (*io) (int32 inst, int32 fnc, int32 dat, int32 dev); }; 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; typedef struct h316_dib DIB;
/* DMA/DMC channel numbers */ /* DMA/DMC channel numbers */
@ -169,6 +174,8 @@ typedef struct h316_dib DIB;
#define INT_V_START 16 /* start button */ #define INT_V_START 16 /* start button */
#define INT_V_NODEF 17 /* int not deferred */ #define INT_V_NODEF 17 /* int not deferred */
#define INT_V_ON 18 /* int on */ #define INT_V_ON 18 /* int on */
#define INT_V_EXTD 16 /* first extended interrupt */
#define INT_V_NONE -1 /* no interrupt used */
/* I/O macros */ /* I/O macros */
@ -195,14 +202,22 @@ typedef struct h316_dib DIB;
#define INT_NMI (INT_START) #define INT_NMI (INT_START)
#define INT_PEND (INT_ON | INT_NODEF) #define INT_PEND (INT_ON | INT_NODEF)
// [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 SET_INT(x) dev_int = dev_int | (x)
#define CLR_INT(x) dev_int = dev_int & ~(x) #define CLR_INT(x) dev_int = dev_int & ~(x)
#define TST_INT(x) ((dev_int & (x)) != 0) #define TST_INT(x) ((dev_int & (x)) != 0)
#define CLR_ENB(x) dev_enb = dev_enb & ~(x) #define CLR_ENB(x) dev_enb = dev_enb & ~(x)
#define TST_INTREQ(x) ((dev_int & dev_enb & (x)) != 0) #define TST_INTREQ(x) ((dev_int & dev_enb & (x)) != 0)
/* Prototypes */ // [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_iobus (UNIT *uptr, int32 val, char *cptr, void *desc);
t_stat io_set_dma (UNIT *uptr, int32 val, char *cptr, void *desc); t_stat io_set_dma (UNIT *uptr, int32 val, char *cptr, void *desc);
t_stat io_set_dmc (UNIT *uptr, int32 val, char *cptr, void *desc); t_stat io_set_dmc (UNIT *uptr, int32 val, char *cptr, void *desc);

View file

@ -27,6 +27,7 @@
4651 disk subsystem 4651 disk subsystem
4720 disk subsystem 4720 disk subsystem
3-Jul-13 RLA compatibility changes for extended interrupts
19-Mar-12 RMS Fixed declaration of chan_req (Mark Pizzolato) 19-Mar-12 RMS Fixed declaration of chan_req (Mark Pizzolato)
04-Sep-05 RMS Fixed missing return (Peter Schorn) 04-Sep-05 RMS Fixed missing return (Peter Schorn)
15-Jul-05 RMS Fixed bug in attach routine 15-Jul-05 RMS Fixed bug in attach routine
@ -269,7 +270,7 @@ t_stat dp_showformat (FILE *st, UNIT *uptr, int32 val, void *desc);
dp_mod DP modifier list dp_mod DP modifier list
*/ */
DIB dp_dib = { DP, DMC1, 1, &dpio }; DIB dp_dib = { DP, 1, DMC1, IOBUS, INT_V_DP, INT_V_NONE, &dpio, 0 };
UNIT dp_unit[] = { UNIT dp_unit[] = {
{ UDATA (&dp_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+ { UDATA (&dp_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+

View file

@ -26,6 +26,7 @@
fhd 516-4400 fixed head disk fhd 516-4400 fixed head disk
03-Sep-13 RMS Added explicit void * cast 03-Sep-13 RMS Added explicit void * cast
3-Jul-13 RLA compatibility changes for extended interrupts
19-Mar-12 RMS Fixed declaration of chan_req (Mark Pizzolato) 19-Mar-12 RMS Fixed declaration of chan_req (Mark Pizzolato)
15-May-06 RMS Fixed bug in autosize attach (David Gesswein) 15-May-06 RMS Fixed bug in autosize attach (David Gesswein)
04-Jan-04 RMS Changed sim_fsize calling sequence 04-Jan-04 RMS Changed sim_fsize calling sequence
@ -114,7 +115,7 @@ uint32 fhd_csword (uint32 cs, uint32 ch);
fhd_reg register list fhd_reg register list
*/ */
DIB fhd_dib = { FHD, IOBUS, 1, &fhdio }; DIB fhd_dib = { FHD, 1, IOBUS, IOBUS, INT_V_FHD, INT_V_NONE, &fhdio, 0 };
UNIT fhd_unit = { UNIT fhd_unit = {
UDATA (&fhd_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BUFABLE+UNIT_MUSTBUF, UDATA (&fhd_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BUFABLE+UNIT_MUSTBUF,

325
H316/h316_hi.c Normal file
View 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
View 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
View 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

View file

@ -25,6 +25,7 @@
lpt line printer lpt line printer
3-Jul-13 RLA compatibility changes for extended interrupts
09-Jun-07 RMS Fixed lost last print line (Theo Engel) 09-Jun-07 RMS Fixed lost last print line (Theo Engel)
19-Jan-06 RMS Added UNIT_TEXT flag 19-Jan-06 RMS Added UNIT_TEXT flag
03-Apr-06 RMS Fixed bug in blanks backscanning (Theo Engel) 03-Apr-06 RMS Fixed bug in blanks backscanning (Theo Engel)
@ -105,7 +106,7 @@ t_stat lpt_reset (DEVICE *dptr);
lpt_reg LPT register list lpt_reg LPT register list
*/ */
DIB lpt_dib = { LPT, IOBUS, 1, &lptio }; DIB lpt_dib = { LPT, 1, IOBUS, IOBUS, INT_V_LPT, INT_V_NONE, &lptio, 0 };
UNIT lpt_unit = { UDATA (&lpt_svc, UNIT_SEQ+UNIT_ATTABLE+UNIT_TEXT, 0) }; UNIT lpt_unit = { UDATA (&lpt_svc, UNIT_SEQ+UNIT_ATTABLE+UNIT_TEXT, 0) };

723
H316/h316_mi.c Normal file
View 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

View file

@ -25,6 +25,7 @@
mt 516-4100 seven track magnetic tape mt 516-4100 seven track magnetic tape
3-Jul-13 RLA compatibility changes for extended interrupts
19-Mar-12 RMS Fixed declaration of chan_req (Mark Pizzolato) 19-Mar-12 RMS Fixed declaration of chan_req (Mark Pizzolato)
09-Jun-07 RMS Fixed bug in write without stop (Theo Engel) 09-Jun-07 RMS Fixed bug in write without stop (Theo Engel)
16-Feb-06 RMS Added tape capacity checking 16-Feb-06 RMS Added tape capacity checking
@ -119,7 +120,7 @@ void mt_wrwd (UNIT *uptr, uint32 dat);
mt_mod MT modifier list mt_mod MT modifier list
*/ */
DIB mt_dib = { MT, IOBUS, MT_NUMDR, &mtio }; DIB mt_dib = { MT, MT_NUMDR, IOBUS, IOBUS, INT_V_MT, INT_V_NONE, &mtio, 0 };
UNIT mt_unit[] = { UNIT mt_unit[] = {
{ UDATA (&mt_svc, UNIT_ATTABLE + UNIT_ROABLE + UNIT_DISABLE, 0) }, { UDATA (&mt_svc, UNIT_ATTABLE + UNIT_ROABLE + UNIT_DISABLE, 0) },

384
H316/h316_rtc.c Normal file
View 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

View file

@ -30,6 +30,7 @@
10-Sep-13 RMS Fixed several bugs in the TTY logic 10-Sep-13 RMS Fixed several bugs in the TTY logic
Added SET file type commands to PTR/PTP Added SET file type commands to PTR/PTP
3-Jul-13 RLA compatibility changes for extended interrupts
09-Jun-07 RMS Fixed bug in clock increment (Theo Engel) 09-Jun-07 RMS Fixed bug in clock increment (Theo Engel)
30-Sep-06 RMS Fixed handling of non-printable characters in KSR mode 30-Sep-06 RMS Fixed handling of non-printable characters in KSR mode
03-Apr-06 RMS Fixed bugs in punch state handling (Theo Engel) 03-Apr-06 RMS Fixed bugs in punch state handling (Theo Engel)
@ -158,7 +159,7 @@ t_stat ttp_write (int32 c);
ptr_reg PTR register list ptr_reg PTR register list
*/ */
DIB ptr_dib = { PTR, IOBUS, 1, &ptrio }; DIB ptr_dib = { PTR, 1, IOBUS, IOBUS, INT_V_PTR, INT_V_NONE, &ptrio, 0 };
UNIT ptr_unit = { UNIT ptr_unit = {
UDATA (&ptr_svc, UNIT_SEQ+UNIT_ATTABLE+UNIT_ROABLE, 0), UDATA (&ptr_svc, UNIT_SEQ+UNIT_ATTABLE+UNIT_ROABLE, 0),
@ -203,7 +204,7 @@ DEVICE ptr_dev = {
ptp_reg PTP register list ptp_reg PTP register list
*/ */
DIB ptp_dib = { PTP, IOBUS, 1, &ptpio }; DIB ptp_dib = { PTP, 1, IOBUS, IOBUS, INT_V_PTP, INT_V_NONE, &ptpio, 0 };
UNIT ptp_unit = { UNIT ptp_unit = {
UDATA (&ptp_svc, UNIT_SEQ+UNIT_ATTABLE, 0), SERIAL_OUT_WAIT UDATA (&ptp_svc, UNIT_SEQ+UNIT_ATTABLE, 0), SERIAL_OUT_WAIT
@ -243,7 +244,7 @@ DEVICE ptp_dev = {
#define TTR 2 #define TTR 2
#define TTP 3 #define TTP 3
DIB tty_dib = { TTY, IOBUS, 1, &ttyio }; DIB tty_dib = { TTY, 1, IOBUS, IOBUS, INT_V_TTY, INT_V_NONE, &ttyio, 0 };
UNIT tty_unit[] = { UNIT tty_unit[] = {
{ UDATA (&tti_svc, TT_MODE_KSR, 0), KBD_POLL_WAIT }, { UDATA (&tti_svc, TT_MODE_KSR, 0), KBD_POLL_WAIT },
@ -308,7 +309,7 @@ DEVICE tty_dev = {
clk_reg CLK register list clk_reg CLK register list
*/ */
DIB clk_dib = { CLK_KEYS, IOBUS, 1, &clkio }; DIB clk_dib = { CLK_KEYS, 1, IOBUS, IOBUS, INT_V_CLK, INT_V_NONE, &clkio, 0 };
UNIT clk_unit = { UDATA (&clk_svc, 0, 0), 16000 }; UNIT clk_unit = { UDATA (&clk_svc, 0, 0), 16000 };

View file

@ -23,6 +23,7 @@
used in advertising or otherwise to promote the sale, use or other dealings used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization from Robert M Supnik. in this Software without prior written authorization from Robert M Supnik.
21-May-13 RLA Add IMP/TIP devices
01-Dec-04 RMS Fixed fprint_opr calling sequence 01-Dec-04 RMS Fixed fprint_opr calling sequence
24-Oct-03 RMS Added DMA/DMC support 24-Oct-03 RMS Added DMA/DMC support
17-Sep-01 RMS Removed multiconsole support 17-Sep-01 RMS Removed multiconsole support
@ -41,6 +42,11 @@ extern DEVICE clk_dev;
extern DEVICE dp_dev; extern DEVICE dp_dev;
extern DEVICE fhd_dev; extern DEVICE fhd_dev;
extern DEVICE mt_dev; extern DEVICE mt_dev;
#ifdef VM_IMPTIP
extern DEVICE rtc_dev, wdt_dev, imp_dev;
extern DEVICE mi1_dev, mi2_dev, mi3_dev, mi4_dev, mi5_dev;
extern DEVICE hi1_dev, hi2_dev, hi3_dev, hi4_dev;
#endif
extern REG cpu_reg[]; extern REG cpu_reg[];
extern uint16 M[]; extern uint16 M[];
@ -64,12 +70,19 @@ DEVICE *sim_devices[] = {
&cpu_dev, &cpu_dev,
&ptr_dev, &ptr_dev,
&ptp_dev, &ptp_dev,
&tty_dev,
&lpt_dev, &lpt_dev,
&clk_dev, &tty_dev,
&dp_dev,
&fhd_dev,
&mt_dev, &mt_dev,
&clk_dev,
&fhd_dev,
&dp_dev,
#ifdef VM_IMPTIP
&wdt_dev,
&rtc_dev,
&imp_dev,
&mi1_dev, &mi2_dev, &mi3_dev, &mi4_dev, &mi5_dev,
&hi1_dev, &hi2_dev, &hi3_dev, &hi4_dev,
#endif
NULL NULL
}; };

667
H316/h316_udp.c Normal file
View 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

File diff suppressed because it is too large Load diff

19
H316/tests/imp2.cmd Normal file
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

65
H316/tests/impconfig.cmd Normal file
View 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
View 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
View 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
View 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
View 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
View 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 ...

View file

@ -68,6 +68,7 @@ A remote console session will close when an EOF character is entered (i.e. ^D or
Serial Console Support Serial Console Support
Separate TCP listening ports per line Separate TCP listening ports per line
Outgoing connections per line (virtual Null Modem cable). 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 #### Asynchronous I/O
* Disk and Tape I/O can be asynchronous. Asynchronous support exists * Disk and Tape I/O can be asynchronous. Asynchronous support exists

View file

@ -45,7 +45,7 @@
Name="VCCLCompilerTool" Name="VCCLCompilerTool"
Optimization="0" Optimization="0"
AdditionalIncludeDirectories="./;../" AdditionalIncludeDirectories="./;../"
PreprocessorDefinitions="_CRT_NONSTDC_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;SIM_NEED_GIT_COMMIT_ID" PreprocessorDefinitions="_CRT_NONSTDC_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;SIM_NEED_GIT_COMMIT_ID;VM_IMPTIP"
MinimalRebuild="true" MinimalRebuild="true"
BasicRuntimeChecks="3" BasicRuntimeChecks="3"
RuntimeLibrary="1" RuntimeLibrary="1"
@ -128,7 +128,7 @@
InlineFunctionExpansion="1" InlineFunctionExpansion="1"
OmitFramePointers="true" OmitFramePointers="true"
AdditionalIncludeDirectories="./;../" AdditionalIncludeDirectories="./;../"
PreprocessorDefinitions="_CRT_NONSTDC_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;SIM_NEED_GIT_COMMIT_ID" PreprocessorDefinitions="_CRT_NONSTDC_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;SIM_NEED_GIT_COMMIT_ID;VM_IMPTIP"
StringPooling="true" StringPooling="true"
RuntimeLibrary="0" RuntimeLibrary="0"
EnableFunctionLevelLinking="true" EnableFunctionLevelLinking="true"
@ -201,14 +201,30 @@
RelativePath="..\H316\h316_fhd.c" RelativePath="..\H316\h316_fhd.c"
> >
</File> </File>
<File
RelativePath="..\H316\h316_hi.c"
>
</File>
<File
RelativePath="..\H316\h316_imp.c"
>
</File>
<File <File
RelativePath="..\H316\h316_lp.c" RelativePath="..\H316\h316_lp.c"
> >
</File> </File>
<File
RelativePath="..\H316\h316_mi.c"
>
</File>
<File <File
RelativePath="..\H316\h316_mt.c" RelativePath="..\H316\h316_mt.c"
> >
</File> </File>
<File
RelativePath="..\H316\h316_rtc.c"
>
</File>
<File <File
RelativePath="..\H316\h316_stddev.c" RelativePath="..\H316\h316_stddev.c"
> >
@ -217,6 +233,10 @@
RelativePath="..\H316\h316_sys.c" RelativePath="..\H316\h316_sys.c"
> >
</File> </File>
<File
RelativePath="..\H316\h316_udp.c"
>
</File>
<File <File
RelativePath="..\scp.c" RelativePath="..\scp.c"
> >
@ -266,6 +286,10 @@
RelativePath="..\H316\h316_defs.h" RelativePath="..\H316\h316_defs.h"
> >
</File> </File>
<File
RelativePath="..\H316\h316_imp.h"
>
</File>
<File <File
RelativePath="..\scp.h" RelativePath="..\scp.h"
> >

View file

@ -383,8 +383,10 @@ H316_LIB = $(LIB_DIR)H316-$(ARCH).OLB
H316_SOURCE = $(H316_DIR)H316_STDDEV.C,$(H316_DIR)H316_LP.C,\ H316_SOURCE = $(H316_DIR)H316_STDDEV.C,$(H316_DIR)H316_LP.C,\
$(H316_DIR)H316_CPU.C,$(H316_DIR)H316_SYS.C,\ $(H316_DIR)H316_CPU.C,$(H316_DIR)H316_SYS.C,\
$(H316_DIR)H316_FHD.C,$(H316_DIR)H316_MT.C,\ $(H316_DIR)H316_FHD.C,$(H316_DIR)H316_MT.C,\
$(H316_DIR)H316_DP.C $(H316_DIR)H316_DP.C,$(H316_DIR)H316_RTC.C,\
H316_OPTIONS = /INCL=($(SIMH_DIR),$(H316_DIR))/DEF=($(CC_DEFS)) $(H316_DIR)H316_IMP.C,$(H316_DIR)H316_HI.C,\
$(H316_DIR)H316_MI.C,$(H316_DIR)H316_UDP.C
H316_OPTIONS = /INCL=($(SIMH_DIR),$(H316_DIR))/DEF=($(CC_DEFS),"VM_IMPTIP=1")
# #
# Hewlett-Packard HP-2100 Simulator Definitions. # Hewlett-Packard HP-2100 Simulator Definitions.

Binary file not shown.

BIN
doc/h316_imp.doc Normal file

Binary file not shown.

View file

@ -868,8 +868,9 @@ PDP8_OPT = -I ${PDP8D}
H316D = H316 H316D = H316
H316 = ${H316D}/h316_stddev.c ${H316D}/h316_lp.c ${H316D}/h316_cpu.c \ H316 = ${H316D}/h316_stddev.c ${H316D}/h316_lp.c ${H316D}/h316_cpu.c \
${H316D}/h316_sys.c ${H316D}/h316_mt.c ${H316D}/h316_fhd.c \ ${H316D}/h316_sys.c ${H316D}/h316_mt.c ${H316D}/h316_fhd.c \
${H316D}/h316_dp.c ${H316D}/h316_dp.c ${H316D}/h316_rtc.c ${H316D}/h316_imp.c \
H316_OPT = -I ${H316D} ${H316D}/h316_hi.c ${H316D}/h316_mi.c ${H316D}/h316_udp.c
H316_OPT = -I ${H316D} -D VM_IMPTIP
HP2100D = HP2100 HP2100D = HP2100

View file

@ -69,6 +69,8 @@
/* OS dependent routines /* OS dependent routines
sim_master_sock create master socket 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_accept_conn accept connection
sim_read_sock read from socket sim_read_sock read from socket
sim_write_sock write 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; 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); SOCKET sim_accept_conn (SOCKET master, char **connectaddr);
{ {
return INVALID_SOCKET; return INVALID_SOCKET;
@ -710,12 +717,12 @@ return 0;
#endif /* endif !Win32 && !VMS */ #endif /* endif !Win32 && !VMS */
static SOCKET sim_create_sock (int af) static SOCKET sim_create_sock_ex (int af, t_bool datagram)
{ {
SOCKET newsock; SOCKET newsock;
int32 err; 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? */ if (newsock == INVALID_SOCKET) { /* socket error? */
err = WSAGetLastError (); err = WSAGetLastError ();
#if defined(WSAEAFNOSUPPORT) #if defined(WSAEAFNOSUPPORT)
@ -727,6 +734,11 @@ if (newsock == INVALID_SOCKET) { /* socket error? */
return newsock; 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 Some platforms and/or network stacks have varying support for listening on
an IPv6 socket and receiving connections from both IPv4 and IPv6 client an IPv6 socket and receiving connections from both IPv4 and IPv6 client
@ -823,28 +835,71 @@ return newsock; /* got it! */
SOCKET sim_connect_sock (const char *hostport, const char *default_host, const char *default_port) 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; SOCKET newsock = INVALID_SOCKET;
int32 sta; int32 sta;
char host[CBUFSIZE], port[CBUFSIZE]; char host[CBUFSIZE], port[CBUFSIZE];
t_stat r; t_stat r;
struct addrinfo hints; 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); r = sim_parse_addr (hostport, host, sizeof(host), default_host, port, sizeof(port), default_port, NULL);
if (r != SCPE_OK) if (r != SCPE_OK)
return newsock; return INVALID_SOCKET;
memset(&hints, 0, sizeof(hints)); memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; hints.ai_family = AF_UNSPEC;
hints.ai_protocol = IPPROTO_TCP; hints.ai_protocol = (datagram ? IPPROTO_UDP : IPPROTO_TCP);
hints.ai_socktype = SOCK_STREAM; hints.ai_socktype = (datagram ? SOCK_DGRAM : SOCK_STREAM);
if (p_getaddrinfo(host[0] ? host : NULL, port[0] ? port : NULL, &hints, &result)) if (p_getaddrinfo(host[0] ? host : NULL, port[0] ? port : NULL, &hints, &result))
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; return newsock;
newsock = sim_create_sock (result->ai_family); /* create socket */ }
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? */
newsock = sim_create_sock_ex (result->ai_family, datagram);/* create socket */
if (newsock == INVALID_SOCKET) { /* socket error? */ if (newsock == INVALID_SOCKET) { /* socket error? */
p_freeaddrinfo (result); p_freeaddrinfo (result);
return newsock; return newsock;
} }
}
sta = sim_setnonblock (newsock); /* set nonblocking */ sta = sim_setnonblock (newsock); /* set nonblocking */
if (sta == SOCKET_ERROR) { /* fcntl error? */ if (sta == SOCKET_ERROR) { /* fcntl error? */

View file

@ -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); 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_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 (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); SOCKET sim_accept_conn (SOCKET master, char **connectaddr);
int32 sim_check_conn (SOCKET sock, t_bool rd); int32 sim_check_conn (SOCKET sock, t_bool rd);
int32 sim_read_sock (SOCKET sock, char *buf, int32 nbytes); int32 sim_read_sock (SOCKET sock, char *buf, int32 nbytes);

View file

@ -862,6 +862,10 @@ if (lp->destination || lp->port || lp->txlogname) {
sprintf (growstring(&tptr, 32), ",Buffered=%d", lp->txbsz); sprintf (growstring(&tptr, 32), ",Buffered=%d", lp->txbsz);
if (!lp->txbfd && (lp->mp->buffered > 0)) if (!lp->txbfd && (lp->mp->buffered > 0))
sprintf (growstring(&tptr, 32), ",UnBuffered"); 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->destination) {
if (lp->serport) { if (lp->serport) {
char portname[CBUFSIZE]; 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 : ""); 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 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) if (lp->txlogname)
sprintf (growstring(&tptr, 12 + strlen (lp->txlogname)), ",Log=%s", lp->txlogname); sprintf (growstring(&tptr, 12 + strlen (lp->txlogname)), ",Log=%s", lp->txlogname);
if (lp->loopback) 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))) { (!lp->modem_control || (lp->modembits & TMXR_MDM_DTR))) {
sprintf (msg, "tmxr_poll_conn() - establishing outgoing connection to: %s", lp->destination); sprintf (msg, "tmxr_poll_conn() - establishing outgoing connection to: %s", lp->destination);
tmxr_debug_connect_line (lp, msg); 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)) { if ((!lp->modem_control) || (lp->modembits & TMXR_MDM_DTR)) {
sprintf (msg, "tmxr_reset_ln_ex() - connecting to %s", lp->destination); sprintf (msg, "tmxr_reset_ln_ex() - connecting to %s", lp->destination);
tmxr_debug_connect_line (lp, msg); 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 */ 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); sprintf (msg, "tmxr_set_get_modem_bits() - establishing outgoing connection to: %s", lp->destination);
tmxr_debug_connect_line (lp, msg); 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->txbpr = 0;
lp->txcnt = lp->txcnt + sbytes; /* update counts */ lp->txcnt = lp->txcnt + sbytes; /* update counts */
nbytes = nbytes - sbytes; 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? */ if (sbytes < 0) { /* I/O Error? */
lp->txbpi = lp->txbpr = 0; /* Drop the data we already know we can't send */ 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; SOCKET sock;
SERHANDLE serport; SERHANDLE serport;
char *tptr = cptr; char *tptr = cptr;
t_bool nolog, notelnet, listennotelnet, unbuffered, modem_control, loopback; t_bool nolog, notelnet, listennotelnet, unbuffered, modem_control, loopback, datagram;
TMLN *lp; TMLN *lp;
t_stat r = SCPE_ARG; t_stat r = SCPE_ARG;
@ -2031,6 +2035,7 @@ while (*tptr) {
memset(port, '\0', sizeof(port)); memset(port, '\0', sizeof(port));
memset(option, '\0', sizeof(option)); memset(option, '\0', sizeof(option));
nolog = notelnet = listennotelnet = unbuffered = loopback = FALSE; nolog = notelnet = listennotelnet = unbuffered = loopback = FALSE;
datagram = mp->datagram;
if (line != -1) if (line != -1)
notelnet = listennotelnet = mp->notelnet; notelnet = listennotelnet = mp->notelnet;
modem_control = mp->modem_control; modem_control = mp->modem_control;
@ -2100,6 +2105,18 @@ while (*tptr) {
modem_control = TRUE; modem_control = TRUE;
continue; 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 (0 == MATCH_CMD (gbuf, "CONNECT")) {
if ((NULL == cptr) || ('\0' == *cptr)) if ((NULL == cptr) || ('\0' == *cptr))
return SCPE_ARG; return SCPE_ARG;
@ -2114,21 +2131,24 @@ while (*tptr) {
strncpy (hostport, cptr, sizeof(hostport)-1); strncpy (hostport, cptr, sizeof(hostport)-1);
if ((cptr = strchr (hostport, ';'))) if ((cptr = strchr (hostport, ';')))
*(cptr++) = '\0'; *(cptr++) = '\0';
sock = sim_connect_sock (hostport, "localhost", NULL);
if (sock != INVALID_SOCKET)
sim_close_sock (sock, 0);
else
return SCPE_ARG;
if (cptr) { if (cptr) {
get_glyph (cptr, cptr, 0); /* upcase this string */ get_glyph (cptr, cptr, 0); /* upcase this string */
if (0 == MATCH_CMD (cptr, "NOTELNET")) if (0 == MATCH_CMD (cptr, "NOTELNET"))
notelnet = TRUE; notelnet = TRUE;
else else
if (0 == MATCH_CMD (cptr, "TELNET")) if (0 == MATCH_CMD (cptr, "TELNET"))
if (datagram)
return SCPE_ARG;
else
notelnet = FALSE; notelnet = FALSE;
else else
return SCPE_ARG; 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; cptr = hostport;
} }
strcpy(destination, cptr); 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 */ sock = sim_master_sock (listen, &r); /* make master socket */
if (r != SCPE_OK) if (r != SCPE_OK)
return r; return r;
@ -2306,7 +2326,16 @@ while (*tptr) {
} }
} }
else { 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) { if (sock != INVALID_SOCKET) {
_mux_detach_line (lp, FALSE, TRUE); _mux_detach_line (lp, FALSE, TRUE);
lp->destination = (char *)malloc(1+strlen(destination)); lp->destination = (char *)malloc(1+strlen(destination));
@ -2370,7 +2399,7 @@ while (*tptr) {
lp->txlog = NULL; 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 */ if ((mp->lines == 1) && (mp->master)) /* single line mux can have either line specific OR mux listener but NOT both */
return SCPE_ARG; return SCPE_ARG;
sock = sim_master_sock (listen, &r); /* make master socket */ sock = sim_master_sock (listen, &r); /* make master socket */
@ -2409,7 +2438,16 @@ while (*tptr) {
} }
} }
else { 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) { if (sock != INVALID_SOCKET) {
_mux_detach_line (lp, FALSE, TRUE); _mux_detach_line (lp, FALSE, TRUE);
lp->destination = (char *)malloc(1+strlen(destination)); lp->destination = (char *)malloc(1+strlen(destination));
@ -3673,6 +3711,9 @@ if (ln >= 0)
if ((lp->sock) || (lp->connecting)) { /* tcp connection? */ if ((lp->sock) || (lp->connecting)) { /* tcp connection? */
if (lp->destination) /* remote connection? */ if (lp->destination) /* remote connection? */
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 */ fprintf (st, "Connection to remote port %s\n", lp->destination);/* print port name */
else /* incoming connection */ else /* incoming connection */
fprintf (st, "Connection from IP address %s\n", lp->ipad); fprintf (st, "Connection from IP address %s\n", lp->ipad);
@ -3689,7 +3730,7 @@ if (lp->sock) {
free (peername); free (peername);
} }
if (lp->port) if ((lp->port) && (!lp->datagram))
fprintf (st, "Listening on port %s\n", lp->port); /* print port name */ fprintf (st, "Listening on port %s\n", lp->port); /* print port name */
if (lp->serport) /* serial connection? */ if (lp->serport) /* serial connection? */

View file

@ -114,7 +114,7 @@ struct tmln {
int32 tsta; /* Telnet state */ int32 tsta; /* Telnet state */
int32 rcve; /* rcv enable */ int32 rcve; /* rcv enable */
int32 xmte; /* xmt 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) */ t_bool notelnet; /* raw binary data (no telnet interpretation) */
int32 rxbpr; /* rcv buf remove */ int32 rxbpr; /* rcv buf remove */
int32 rxbpi; /* rcv buf insert */ int32 rxbpi; /* rcv buf insert */
@ -151,6 +151,7 @@ struct tmln {
char *destination; /* Outgoing destination address:port */ char *destination; /* Outgoing destination address:port */
t_bool loopback; /* Line in loopback mode */ t_bool loopback; /* Line in loopback mode */
t_bool halfduplex; /* Line in half-duplex mode */ t_bool halfduplex; /* Line in half-duplex mode */
t_bool datagram; /* Line is datagram packet oriented */
int32 lpbpr; /* loopback buf remove */ int32 lpbpr; /* loopback buf remove */
int32 lpbpi; /* loopback buf insert */ int32 lpbpi; /* loopback buf insert */
int32 lpbcnt; /* loopback buf used count */ int32 lpbcnt; /* loopback buf used count */
@ -176,6 +177,7 @@ struct tmxr {
uint32 last_poll_time; /* time of last connection poll */ uint32 last_poll_time; /* time of last connection poll */
t_bool notelnet; /* default telnet capability for incoming connections */ t_bool notelnet; /* default telnet capability for incoming connections */
t_bool modem_control; /* multiplexer supports modem control behaviors */ t_bool modem_control; /* multiplexer supports modem control behaviors */
t_bool datagram; /* Lines are datagram packet oriented */
}; };
int32 tmxr_poll_conn (TMXR *mp); int32 tmxr_poll_conn (TMXR *mp);