H316: Implement the IMP host interface.
- From Charles Anthony passed through Lars Brinkhoff - Cleaned up for cross platform builds - Mark Pizzolato
This commit is contained in:
parent
e52d65d8e4
commit
e8bceec8e2
5 changed files with 451 additions and 37 deletions
404
H316/h316_hi.c
404
H316/h316_hi.c
|
@ -50,6 +50,44 @@
|
||||||
|
|
||||||
IMPLEMENT THIS MODULE!!!
|
IMPLEMENT THIS MODULE!!!
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// 1822 sect 4.5 Host cable connections
|
||||||
|
//
|
||||||
|
// IMP Ready
|
||||||
|
//
|
||||||
|
// IMP Ready Test Host --> IMP trigger IMP Master Ready
|
||||||
|
// IMP Master Ready IMP --> Host
|
||||||
|
//
|
||||||
|
// Host Ready
|
||||||
|
//
|
||||||
|
// Host Ready Test IMP --> Host trigger Host Master Ready
|
||||||
|
// Host Master Ready IMP --> Host
|
||||||
|
//
|
||||||
|
// Host to IMP Data
|
||||||
|
//
|
||||||
|
// Host-to-IMP Data Line Host --> IMP
|
||||||
|
// There's-Your-Host-Bit Host --> IMP
|
||||||
|
// Ready-For-Next-Host-Bit IMP --> Host
|
||||||
|
// Last-Host-Bit Host --> IMP
|
||||||
|
//
|
||||||
|
// IMP to Host Data
|
||||||
|
//
|
||||||
|
// IMP-to-Host Data Line IMP --> Host
|
||||||
|
// There's-Your-IMP-Bit IMP --> Host
|
||||||
|
// Ready-For-Next-IMP-Bit Host --> IMP
|
||||||
|
// Last-IMP-Bit IMP --> Host
|
||||||
|
//
|
||||||
|
|
||||||
|
// Last-IMP-Bit is implemented as an out-of-band flag in UDP_PACKET
|
||||||
|
#define PFLG_FINAL 00001
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
//
|
||||||
|
// For the nonce, assume ready bits are always on. We need an out-of-band
|
||||||
|
// packet exchange to model the ready bit behavior. (This could also reset
|
||||||
|
// the UDP_PACKET sequence numbers.)
|
||||||
|
|
||||||
|
|
||||||
#ifdef VM_IMPTIP
|
#ifdef VM_IMPTIP
|
||||||
#include "h316_defs.h" // H316 emulator definitions
|
#include "h316_defs.h" // H316 emulator definitions
|
||||||
#include "h316_imp.h" // ARPAnet IMP/TIP definitions
|
#include "h316_imp.h" // ARPAnet IMP/TIP definitions
|
||||||
|
@ -66,7 +104,8 @@ int32 hi1_io (int32 inst, int32 fnc, int32 dat, int32 dev);
|
||||||
int32 hi2_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 hi3_io (int32 inst, int32 fnc, int32 dat, int32 dev);
|
||||||
int32 hi4_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_rx_service (UNIT *uptr);
|
||||||
|
void hi_rx_local (uint16 line, uint16 txnext, uint16 txcount);
|
||||||
t_stat hi_reset (DEVICE *dptr);
|
t_stat hi_reset (DEVICE *dptr);
|
||||||
t_stat hi_attach (UNIT *uptr, CONST char *cptr);
|
t_stat hi_attach (UNIT *uptr, CONST char *cptr);
|
||||||
t_stat hi_detach (UNIT *uptr);
|
t_stat hi_detach (UNIT *uptr);
|
||||||
|
@ -80,7 +119,7 @@ t_stat hi_detach (UNIT *uptr);
|
||||||
// Host interface data blocks ...
|
// Host interface data blocks ...
|
||||||
// The HIDB is our own internal data structure for each host. It keeps data
|
// The HIDB is our own internal data structure for each host. It keeps data
|
||||||
// about the TCP/IP connection, buffers, etc.
|
// about the TCP/IP connection, buffers, etc.
|
||||||
#define HI_HIDB(N) {0, 0, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE}
|
#define HI_HIDB(N) {FALSE, FALSE, 0, 0, 0, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, 0, HI_TXBPS}
|
||||||
HIDB hi1_db = HI_HIDB(1), hi2_db = HI_HIDB(2);
|
HIDB hi1_db = HI_HIDB(1), hi2_db = HI_HIDB(2);
|
||||||
HIDB hi3_db = HI_HIDB(3), hi4_db = HI_HIDB(4);
|
HIDB hi3_db = HI_HIDB(3), hi4_db = HI_HIDB(4);
|
||||||
|
|
||||||
|
@ -98,7 +137,7 @@ DIB hi3_dib = HI_DIB(3), hi4_dib = HI_DIB(4);
|
||||||
// The UNIT data also contains four "user" fields which devices can reuse for
|
// 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.
|
// 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 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}
|
#define HI_UNIT(N) {UDATA (&hi_rx_service, UNIT_ATTABLE, 0), HI_POLL_DELAY, N, 0, 0, 0}
|
||||||
UNIT hi1_unit = HI_UNIT(1), hi2_unit = HI_UNIT(2);
|
UNIT hi1_unit = HI_UNIT(1), hi2_unit = HI_UNIT(2);
|
||||||
UNIT hi3_unit = HI_UNIT(3), hi4_unit = HI_UNIT(4);
|
UNIT hi3_unit = HI_UNIT(3), hi4_unit = HI_UNIT(4);
|
||||||
|
|
||||||
|
@ -106,17 +145,19 @@ UNIT hi3_unit = HI_UNIT(3), hi4_unit = HI_UNIT(4);
|
||||||
// These are the simh device "registers" - they c can be viewed with the
|
// These are the simh device "registers" - they c can be viewed with the
|
||||||
// "EXAMINE HIxn STATE" command and modified by "DEPOSIT HIxn ..."
|
// "EXAMINE HIxn STATE" command and modified by "DEPOSIT HIxn ..."
|
||||||
#define HI_REG(N) { \
|
#define HI_REG(N) { \
|
||||||
{ DRDATA (POLL, hi##N##_unit.wait, 24), REG_NZ + PV_LEFT }, \
|
{ 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 (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) }, \
|
{ FLDATA (RXIEN, dev_ext_enb, INT_V_HI##N##RX-INT_V_EXTD) }, \
|
||||||
{ DRDATA (RXTOT, hi##N##_db.rxtotal,32), REG_RO + PV_LEFT }, \
|
{ 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 (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) }, \
|
{ FLDATA (TXIEN, dev_ext_enb, INT_V_HI##N##TX-INT_V_EXTD) }, \
|
||||||
{ DRDATA (TXTOT, hi##N##_db.txtotal,32), REG_RO + PV_LEFT }, \
|
{ DRDATA (TXTOT, hi##N##_db.txtotal,32), REG_RO + PV_LEFT }, \
|
||||||
{ FLDATA (LLOOP, hi##N##_db.lloop, 0), PV_RZRO }, \
|
{ FLDATA (LLOOP, hi##N##_db.iloop, 0), PV_RZRO }, \
|
||||||
{ FLDATA (ERROR, hi##N##_db.error, 0), PV_RZRO }, \
|
{ FLDATA (ERROR, hi##N##_db.error, 0), PV_RZRO }, \
|
||||||
{ FLDATA (READY, hi##N##_db.ready, 0), PV_RZRO }, \
|
{ FLDATA (READY, hi##N##_db.ready, 0), PV_RZRO }, \
|
||||||
{ FLDATA (FULL, hi##N##_db.full , 0), PV_RZRO }, \
|
{ FLDATA (FULL, hi##N##_db.full , 0), PV_RZRO }, \
|
||||||
|
{ DRDATA (LINK, hi##N##_db.link, 32), REG_RO + PV_LEFT }, \
|
||||||
|
{ DRDATA (BPS, hi##N##_db.bps, 32), REG_NZ + PV_LEFT }, \
|
||||||
{ NULL } \
|
{ NULL } \
|
||||||
}
|
}
|
||||||
REG hi1_reg[] = HI_REG(1), hi2_reg[] = HI_REG(2);
|
REG hi1_reg[] = HI_REG(1), hi2_reg[] = HI_REG(2);
|
||||||
|
@ -184,8 +225,9 @@ HIDB *const hi_hidbs [HI_NUM] = {&hi1_db, &hi2_db, &hi3_db, &hi4_db };
|
||||||
// Reset receiver (clear flags AND initialize all data) ...
|
// Reset receiver (clear flags AND initialize all data) ...
|
||||||
void hi_reset_rx (uint16 host)
|
void hi_reset_rx (uint16 host)
|
||||||
{
|
{
|
||||||
PHIDB(host)->lloop = PHIDB(host)->error = PHIDB(host)->enabled = FALSE;
|
PHIDB(host)->iloop = PHIDB(host)->error = PHIDB(host)->enabled = FALSE;
|
||||||
PHIDB(host)->ready = PHIDB(host)->eom = FALSE;
|
PHIDB(host)->ready = TRUE; // XXX
|
||||||
|
PHIDB(host)->eom = FALSE;
|
||||||
PHIDB(host)->rxtotal = 0;
|
PHIDB(host)->rxtotal = 0;
|
||||||
CLR_RX_IRQ(host); CLR_RX_IEN(host);
|
CLR_RX_IRQ(host); CLR_RX_IEN(host);
|
||||||
}
|
}
|
||||||
|
@ -193,11 +235,255 @@ void hi_reset_rx (uint16 host)
|
||||||
// Reset transmitter (clear flags AND initialize all data) ...
|
// Reset transmitter (clear flags AND initialize all data) ...
|
||||||
void hi_reset_tx (uint16 host)
|
void hi_reset_tx (uint16 host)
|
||||||
{
|
{
|
||||||
PHIDB(host)->lloop = PHIDB(host)->enabled = PHIDB(host)->full = FALSE;
|
PHIDB(host)->iloop = PHIDB(host)->enabled = PHIDB(host)->full = FALSE;
|
||||||
PHIDB(host)->txtotal = 0;
|
PHIDB(host)->txtotal = 0;
|
||||||
CLR_TX_IRQ(host); CLR_TX_IEN(host);
|
CLR_TX_IRQ(host); CLR_TX_IEN(host);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the DMC control words (starting address, end and length) for the channel.
|
||||||
|
void hi_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 hi_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 hi_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,"HI%d - UNRECOVERABLE I/O ERROR!\n", line);
|
||||||
|
hi_reset_rx(line); hi_reset_tx(line);
|
||||||
|
sim_cancel(PUNIT(line)); hi_detach(PUNIT(line));
|
||||||
|
PHIDB(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 hi_debug_hio (uint16 line, uint32 dmc, const char *ptext)
|
||||||
|
{
|
||||||
|
uint16 next, last, count;
|
||||||
|
if (!ISHDBG(line, IMP_DBG_IOT)) return;
|
||||||
|
hi_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 hi_debug_msg (uint16 line, uint16 next, uint16 count, const char *ptext)
|
||||||
|
{
|
||||||
|
uint16 i; char buf[CBUFSIZE]; int len = 0;
|
||||||
|
if (!ISHDBG(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 hi_start_tx (uint16 line, uint16 flags)
|
||||||
|
{
|
||||||
|
// This handles all the work of the "start host 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.
|
||||||
|
hi_get_dmc(PDIB(line)->txdmc, &next, &last, &count);
|
||||||
|
hi_update_dmc(PDIB(line)->txdmc, count);
|
||||||
|
hi_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 host be attached!
|
||||||
|
if (PHIDB(line)->iloop) {
|
||||||
|
hi_rx_local(line, next, count);
|
||||||
|
} else if (PHIDB(line)->link != NOLINK) {
|
||||||
|
//for (int i = 0; i < count; i ++) fprintf (stderr, "%06o\r\n", M[next + i]);
|
||||||
|
// The host interface needs some out-of-band data bits. The format
|
||||||
|
// of the data going out over the wire is:
|
||||||
|
// struct
|
||||||
|
// uint16 flags;
|
||||||
|
// uint16 data [MAXDATA - 1];
|
||||||
|
// Put the packet into a temp buffer for assembly.
|
||||||
|
uint16 *tmp = (uint16 *)malloc ((count + 1) * sizeof (*tmp));
|
||||||
|
uint16 i;
|
||||||
|
|
||||||
|
tmp [0] = flags;
|
||||||
|
for (i = 0; i < count; i ++)
|
||||||
|
tmp [i + 1] = M [next+i];
|
||||||
|
//ret = udp_send(PDEVICE(line), PHIDB(line)->link, &M[next], count);
|
||||||
|
ret = udp_send(PDEVICE(line), PHIDB(line)->link, tmp, count);
|
||||||
|
free (tmp);
|
||||||
|
if (ret != SCPE_OK && ret != 66) hi_link_error(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX the host interface is significantly faster... Need new math here.
|
||||||
|
// 1822 pg 4-9 100 KBS
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
PHIDB(line)->txdelay = (nbits * 1000000UL) / (PHIDB(line)->bps * rtc_interval);
|
||||||
|
//fprintf(stderr,"HI%d - transmit packet, length=%d, bits=%ld, interval=%ld, delay=%ld\n", line, count, nbits, rtc_interval, PHIDB(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 hi_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 (PHIDB(line)->txdelay == 0) return;
|
||||||
|
if (PHIDB(line)->txdelay <= quantum) {
|
||||||
|
SET_TX_IRQ(line); PHIDB(line)->txdelay = 0; PHIDB(line)->txtotal++;
|
||||||
|
sim_debug(IMP_DBG_IOT, PDEVICE(line), "transmit done (message #%d, intreq=%06o)\n", PHIDB(line)->txtotal, dev_ext_int);
|
||||||
|
} else
|
||||||
|
PHIDB(line)->txdelay -= quantum;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the receiver ...
|
||||||
|
void hi_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 (PHIDB(line)->rxpending) {
|
||||||
|
sim_debug(IMP_DBG_WARN,PDEVICE(line),"start input while input already pending\n");
|
||||||
|
}
|
||||||
|
PHIDB(line)->rxpending = TRUE; PHIDB(line)->rxerror = FALSE;
|
||||||
|
CLR_RX_IRQ(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll for receiver data ...
|
||||||
|
void hi_poll_rx (uint16 line)
|
||||||
|
{
|
||||||
|
// This routine is called by hi_rx_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;
|
||||||
|
uint16 *tmp = NULL;
|
||||||
|
uint16 i;
|
||||||
|
|
||||||
|
// If the modem isn't attached, then the read never completes!
|
||||||
|
if (PHIDB(line)->link == NOLINK) return;
|
||||||
|
|
||||||
|
// Get the DMC words for this channel, or zeros if no read is pending ...
|
||||||
|
if (PHIDB(line)->rxpending) {
|
||||||
|
hi_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.
|
||||||
|
// The host interface needs some out-of-band data bits. The format
|
||||||
|
// of the data coming over the wire is:
|
||||||
|
// struct
|
||||||
|
// uint16 flags;
|
||||||
|
// uint16 data [MAXDATA - 1];
|
||||||
|
// Read the packet into a temp buffer for disassembly.
|
||||||
|
tmp = (uint16 *)malloc (MAXDATA * sizeof (*tmp));
|
||||||
|
|
||||||
|
//count = udp_receive(PDEVICE(line), PHIDB(line)->link, pdata, maxbuf);
|
||||||
|
count = udp_receive(PDEVICE(line), PHIDB(line)->link, tmp, maxbuf+1);
|
||||||
|
if (count == 0) {free (tmp); return; }
|
||||||
|
if (count < 0) {free (tmp); hi_link_error(line); return; }
|
||||||
|
|
||||||
|
PHIDB(line)->eom = !! tmp[0] & PFLG_FINAL;
|
||||||
|
for (i = 0; i < count - 1; i ++)
|
||||||
|
* (pdata + i) = tmp [i + 1];
|
||||||
|
free (tmp);
|
||||||
|
tmp = NULL;
|
||||||
|
// Now would be a good time to worry about whether a receive is pending!
|
||||||
|
if (!PHIDB(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);
|
||||||
|
PHIDB(line)->rxerror = TRUE; count = maxbuf;
|
||||||
|
}
|
||||||
|
hi_update_dmc(PDIB(line)->rxdmc, count);
|
||||||
|
hi_debug_msg (line, next, count, "received");
|
||||||
|
|
||||||
|
// Assert the interrupt request and we're done!
|
||||||
|
SET_RX_IRQ(line); PHIDB(line)->rxpending = FALSE; PHIDB(line)->rxtotal++;
|
||||||
|
sim_debug(IMP_DBG_IOT, PDEVICE(line), "receive done (message #%d, intreq=%06o)\n", PHIDB(line)->rxtotal, dev_ext_int);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Receive cross patched data ...
|
||||||
|
void hi_rx_local (uint16 line, uint16 txnext, uint16 txcount)
|
||||||
|
{
|
||||||
|
// This routine is invoked by the hi_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 hi_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 (!PHIDB(line)->rxpending) return;
|
||||||
|
|
||||||
|
// Get the DMC words for the receiver and copy data from one buffer to the other.
|
||||||
|
hi_get_dmc(PDIB(line)->rxdmc, &rxnext, &rxlast, &maxbuf);
|
||||||
|
if (txcount > maxbuf) {txcount = maxbuf; PHIDB(line)->rxerror = TRUE;}
|
||||||
|
memmove(&M[rxnext], &M[txnext], txcount * sizeof(uint16));
|
||||||
|
|
||||||
|
// Update the receiver DMC pointers, assert IRQ and we're done!
|
||||||
|
hi_update_dmc(PDIB(line)->rxdmc, txcount);
|
||||||
|
hi_debug_msg (line, rxnext, txcount, "received");
|
||||||
|
SET_RX_IRQ(line); PHIDB(line)->rxpending = FALSE; PHIDB(line)->rxtotal++;
|
||||||
|
sim_debug(IMP_DBG_IOT, PDEVICE(line), "receive done (message #%d, intreq=%06o)\n", PHIDB(line)->rxtotal, dev_ext_int);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -222,25 +508,32 @@ int32 hi_io (uint16 host, int32 inst, int32 fnc, int32 dat, int32 dev)
|
||||||
switch (fnc) {
|
switch (fnc) {
|
||||||
case 000:
|
case 000:
|
||||||
// HnROUT - start regular host output ...
|
// HnROUT - start regular host output ...
|
||||||
sim_debug(IMP_DBG_IOT, PDEVICE(host), "start regular output (PC=%06o)\n", PC-1);
|
hi_debug_hio(host, PDIB(host)->txdmc, "output");
|
||||||
return dat;
|
hi_start_tx(host, 0); return dat;
|
||||||
case 001:
|
case 001:
|
||||||
// HnIN - start host input ...
|
// HnIN - start host input ...
|
||||||
sim_debug(IMP_DBG_IOT, PDEVICE(host), "start input (PC=%06o)\n", PC-1);
|
hi_debug_hio(host, PDIB(host)->rxdmc, "input");
|
||||||
|
hi_start_rx(host); return dat;
|
||||||
return dat;
|
return dat;
|
||||||
case 002:
|
case 002:
|
||||||
// HnFOUT - start final host output ...
|
// HnFOUT - start final host output ...
|
||||||
sim_debug(IMP_DBG_IOT, PDEVICE(host), "start final output (PC=%06o)\n", PC-1);
|
sim_debug(IMP_DBG_IOT, PDEVICE(host), "start final output (PC=%06o)\n", PC-1);
|
||||||
|
hi_start_tx(host, PFLG_FINAL); return dat;
|
||||||
return dat;
|
return dat;
|
||||||
case 003:
|
case 003:
|
||||||
// HnXP - cross patch ...
|
// HnXP - cross patch ...
|
||||||
sim_debug(IMP_DBG_IOT, PDEVICE(host), "enable cross patch (PC=%06o)\n", PC-1);
|
sim_debug(IMP_DBG_IOT, PDEVICE(host), "enable cross patch (PC=%06o)\n", PC-1);
|
||||||
|
PHIDB(host)->iloop = TRUE;
|
||||||
|
udp_set_link_loopback (PDEVICE(host), PHIDB(host)->link, TRUE);
|
||||||
return dat;
|
return dat;
|
||||||
case 004:
|
case 004:
|
||||||
// HnUNXP - un-cross patch ...
|
// HnUNXP - un-cross patch ...
|
||||||
sim_debug(IMP_DBG_IOT, PDEVICE(host), "disable cross patch (PC=%06o)\n", PC-1);
|
sim_debug(IMP_DBG_IOT, PDEVICE(host), "disable cross patch (PC=%06o)\n", PC-1);
|
||||||
|
PHIDB(host)->iloop = FALSE;
|
||||||
|
udp_set_link_loopback (PDEVICE(host), PHIDB(host)->link, FALSE);
|
||||||
return dat;
|
return dat;
|
||||||
case 005:
|
case 005:
|
||||||
|
//fprintf (stderr, "HnENAB unimp.\r\n");
|
||||||
// HnENAB - enable ...
|
// HnENAB - enable ...
|
||||||
sim_debug(IMP_DBG_IOT, PDEVICE(host), "enable host (PC=%06o)\n", PC-1);
|
sim_debug(IMP_DBG_IOT, PDEVICE(host), "enable host (PC=%06o)\n", PC-1);
|
||||||
return dat;
|
return dat;
|
||||||
|
@ -251,18 +544,22 @@ int32 hi_io (uint16 host, int32 inst, int32 fnc, int32 dat, int32 dev)
|
||||||
switch (fnc) {
|
switch (fnc) {
|
||||||
case 000:
|
case 000:
|
||||||
// HnERR - skip on host error ...
|
// HnERR - skip on host error ...
|
||||||
sim_debug(IMP_DBG_IOT, PDEVICE(host), "skip on error (PC=%06o %s)\n", PC-1, "NOSKIP");
|
sim_debug(IMP_DBG_IOT,PDEVICE(host),"skip on error (PC=%06o, %s)\n",
|
||||||
return dat;
|
PC-1, PHIDB(host)->rxerror ? "SKIP" : "NOSKIP");
|
||||||
|
return PHIDB(host)->rxerror ? IOSKIP(dat) : dat;
|
||||||
case 001:
|
case 001:
|
||||||
// HnRDY - skip on host ready ...
|
// HnRDY - skip on host ready ...
|
||||||
sim_debug(IMP_DBG_IOT, PDEVICE(host), "skip on ready (PC=%06o %s)\n", PC-1, "NOSKIP");
|
//sim_debug(IMP_DBG_IOT, PDEVICE(host), "skip on ready (PC=%06o %s)\n", PC-1, PHIDB(host)->ready ? "SKIP" : "NOSKIP");
|
||||||
return dat;
|
//fprintf (stderr, "HnRDY unimpl.; always ready\r\n");
|
||||||
|
return PHIDB(host)->ready ? IOSKIP(dat) : dat;
|
||||||
case 002:
|
case 002:
|
||||||
// HnEOM - skip on end of message ...
|
// 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");
|
sim_debug(IMP_DBG_IOT, PDEVICE(host), "skip on end of message (PC=%06o %s)\n", PC-1, PHIDB(host)->eom ? "SKIP" : "NOSKIP");
|
||||||
|
return PHIDB(host)->eom ? IOSKIP(dat) : dat;
|
||||||
return dat;
|
return dat;
|
||||||
case 005:
|
case 005:
|
||||||
// HnFULL - skip on host buffer full ...
|
// HnFULL - skip on host buffer full ...
|
||||||
|
fprintf (stderr, "HnFULL unimp.\r\n");
|
||||||
sim_debug(IMP_DBG_IOT, PDEVICE(host), "skip on buffer full (PC=%06o %s)\n", PC-1, "NOSKIP");
|
sim_debug(IMP_DBG_IOT, PDEVICE(host), "skip on buffer full (PC=%06o %s)\n", PC-1, "NOSKIP");
|
||||||
return dat;
|
return dat;
|
||||||
}
|
}
|
||||||
|
@ -280,9 +577,27 @@ int32 hi_io (uint16 host, int32 inst, int32 fnc, int32 dat, int32 dev)
|
||||||
/////////////////// H O S T E V E N T S E R V I C E ////////////////////
|
/////////////////// H O S T E V E N T S E R V I C E ////////////////////
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// Unit service ...
|
// Receiver service ...
|
||||||
t_stat hi_service (UNIT *uptr)
|
t_stat hi_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->hline;
|
||||||
|
hi_poll_rx(line);
|
||||||
|
sim_activate(uptr, uptr->wait);
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transmitter service ...
|
||||||
|
t_stat hi_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 <= HI_NUM; ++i) hi_poll_tx(i, quantum);
|
||||||
return SCPE_OK;
|
return SCPE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,25 +614,62 @@ t_stat hi_reset (DEVICE *dptr)
|
||||||
UNIT *uptr = dptr->units;
|
UNIT *uptr = dptr->units;
|
||||||
uint16 host= uptr->hline;
|
uint16 host= uptr->hline;
|
||||||
hi_reset_rx(host); hi_reset_tx(host);
|
hi_reset_rx(host); hi_reset_tx(host);
|
||||||
|
sim_cancel(uptr);
|
||||||
|
if ((uptr->flags & UNIT_ATT) != 0) sim_activate(uptr, uptr->wait);
|
||||||
return SCPE_OK;
|
return SCPE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach (connect) ...
|
// Attach (connect) ...
|
||||||
t_stat hi_attach (UNIT *uptr, CONST char *cptr)
|
t_stat hi_attach (UNIT *uptr, CONST char *cptr)
|
||||||
{
|
{
|
||||||
// simh calls this routine for (what else?) the ATTACH command.
|
// simh calls this routine for (what else?) the ATTACH command. There are
|
||||||
uint16 host = uptr->hline;
|
// three distinct formats for ATTACH -
|
||||||
fprintf(stderr,"HI%d - host interface not yet implemented\n", host);
|
//
|
||||||
return SCPE_IERR;
|
// ATTACH -p HIn COHnn - attach MIn to a physical COM port
|
||||||
|
// ATTACH HIn llll:w.x.y.z:rrrr - connect via UDP to a remote simh host
|
||||||
|
//
|
||||||
|
t_stat ret; char *pfn; uint16 host = uptr->hline;
|
||||||
|
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,"HI%d - physical COM support is not yet implemented\n", host);
|
||||||
|
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 HIn ..." 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(host), cptr, &(PHIDB(host)->link));
|
||||||
|
if (ret != SCPE_OK) {free(pfn); return ret;};
|
||||||
|
|
||||||
|
// Reset the flags and start polling ...
|
||||||
|
uptr->flags |= UNIT_ATT; uptr->filename = pfn;
|
||||||
|
return hi_reset(find_dev_from_unit(uptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detach (connect) ...
|
// Detach (connect) ...
|
||||||
t_stat hi_detach (UNIT *uptr)
|
t_stat hi_detach (UNIT *uptr)
|
||||||
{
|
{
|
||||||
// simh calls this routine for (you guessed it!) the DETACH command.
|
// simh calls this routine for (you guessed it!) the DETACH command. This
|
||||||
uint16 host = uptr->hline;
|
// disconnects the modem from any UDP connection or COM port and effectively
|
||||||
fprintf(stderr,"HI%d - host interface not yet implemented\n", host);
|
// makes the modem "off line". A disconnected modem acts like a real modem
|
||||||
return SCPE_IERR;
|
// with its phone line unplugged.
|
||||||
|
t_stat ret; uint16 line = uptr->hline;
|
||||||
|
if ((uptr->flags & UNIT_ATT) == 0) return SCPE_OK;
|
||||||
|
ret = udp_release(PDEVICE(line), PHIDB(line)->link);
|
||||||
|
if (ret != SCPE_OK) return ret;
|
||||||
|
PHIDB(line)->link = NOLINK; uptr->flags &= ~UNIT_ATT;
|
||||||
|
free (uptr->filename); uptr->filename = NULL;
|
||||||
|
return hi_reset(PDEVICE(line));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,8 @@
|
||||||
#define MI_MAX_MSG 256 // longest possible modem message (words!)
|
#define MI_MAX_MSG 256 // longest possible modem message (words!)
|
||||||
#define HI_MAX_MSG 256 // longest possible host message (words!)
|
#define HI_MAX_MSG 256 // longest possible host message (words!)
|
||||||
#define MI_RXPOLL 100 // RX polling delay for UDP messages
|
#define MI_RXPOLL 100 // RX polling delay for UDP messages
|
||||||
#define MI_TXBPS 56000UL // default TX speed (bits per second)
|
#define MI_TXBPS 56000UL // default modem TX speed (bits per second)
|
||||||
|
#define HI_TXBPS 100000UL // default host TX speed (bits per second)
|
||||||
#define HI_POLL_DELAY 1000 // polling delay for messages
|
#define HI_POLL_DELAY 1000 // polling delay for messages
|
||||||
|
|
||||||
// Modem interface, line #1 ...
|
// Modem interface, line #1 ...
|
||||||
|
@ -158,16 +159,21 @@ typedef struct _MIDB MIDB;
|
||||||
// One of these is allocated to every host interface ...
|
// One of these is allocated to every host interface ...
|
||||||
struct _HIDB {
|
struct _HIDB {
|
||||||
// Receiver (HOST -> IMP) data ...
|
// Receiver (HOST -> IMP) 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 host messages received
|
uint32 rxtotal; // total host messages received
|
||||||
// Transmitter (IMP -> HOST) data ...
|
// Transmitter (IMP -> HOST) data ...
|
||||||
|
uint32 txdelay; // RTC ticks until TX done interrupt
|
||||||
uint32 txtotal; // total host messages sent
|
uint32 txtotal; // total host messages sent
|
||||||
// Other data ...
|
// Other data ...
|
||||||
t_bool lloop; // local loop back enabled
|
t_bool iloop; // local loop back enabled
|
||||||
t_bool enabled; // TRUE if the host is enabled
|
t_bool enabled; // TRUE if the host is enabled
|
||||||
t_bool error; // TRUE for any host error
|
t_bool error; // TRUE for any host error
|
||||||
t_bool ready; // TRUE if the host is ready
|
t_bool ready; // TRUE if the host is ready
|
||||||
t_bool full; // TRUE if the host buffer is full
|
t_bool full; // TRUE if the host buffer is full
|
||||||
t_bool eom; // TRUE when end of message is reached
|
t_bool eom; // TRUE when end of message is reached
|
||||||
|
int32 link; // h316_udp link number
|
||||||
|
uint32 bps; // simulated line speed or COM port baud rate
|
||||||
};
|
};
|
||||||
typedef struct _HIDB HIDB;
|
typedef struct _HIDB HIDB;
|
||||||
|
|
||||||
|
@ -187,6 +193,16 @@ typedef struct _HIDB HIDB;
|
||||||
extern uint32 rtc_interval;
|
extern uint32 rtc_interval;
|
||||||
extern t_stat mi_tx_service (uint32 quantum);
|
extern t_stat mi_tx_service (uint32 quantum);
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
// Prototypes for UDP modem/host interface emulation routines ...
|
// Prototypes for UDP modem/host interface emulation routines ...
|
||||||
#define NOLINK (-1)
|
#define NOLINK (-1)
|
||||||
t_stat udp_create (DEVICE *pdtr, const char *premote, int32 *plink);
|
t_stat udp_create (DEVICE *pdtr, const char *premote, int32 *plink);
|
||||||
|
|
|
@ -630,6 +630,13 @@ int32 mi_io (uint16 line, int32 inst, int32 fnc, int32 dat, int32 dev)
|
||||||
return IOBADFNC(dat);
|
return IOBADFNC(dat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/////////////////// H O S T E V E N T S E R V I C E ////////////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
// Receiver service ...
|
// Receiver service ...
|
||||||
t_stat mi_rx_service (UNIT *uptr)
|
t_stat mi_rx_service (UNIT *uptr)
|
||||||
{
|
{
|
||||||
|
|
|
@ -136,15 +136,6 @@
|
||||||
|
|
||||||
// Local constants ...
|
// Local constants ...
|
||||||
#define MAXLINKS 10 // maximum number of simultaneous connections
|
#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)
|
|
||||||
|
|
||||||
// UDP connection data structure ...
|
// UDP connection data structure ...
|
||||||
// One of these blocks is allocated for every simulated modem link.
|
// One of these blocks is allocated for every simulated modem link.
|
||||||
|
@ -336,6 +327,11 @@ t_stat udp_send (DEVICE *dptr, int32 link, uint16 *pdata, uint16 count)
|
||||||
|
|
||||||
// Send it and we're outta here ...
|
// Send it and we're outta here ...
|
||||||
iret = tmxr_put_packet_ln (&udp_lines[link], (const uint8 *)&pkt, (size_t)pktlen);
|
iret = tmxr_put_packet_ln (&udp_lines[link], (const uint8 *)&pkt, (size_t)pktlen);
|
||||||
|
if (iret == 111)
|
||||||
|
{
|
||||||
|
fprintf (stderr, "link %d got connection refused\n", link);
|
||||||
|
return SCPE_OK;
|
||||||
|
}
|
||||||
if (iret != SCPE_OK) return udp_error(link, "tmxr_put_packet_ln()");
|
if (iret != SCPE_OK) return udp_error(link, "tmxr_put_packet_ln()");
|
||||||
sim_debug(IMP_DBG_UDP, dptr, "link %d - packet sent (sequence=%d, length=%d)\n", link, ntohl(pkt.sequence), ntohs(pkt.count));
|
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;
|
return SCPE_OK;
|
||||||
|
|
43
H316/tests/dps8m_imp.cmd
Normal file
43
H316/tests/dps8m_imp.cmd
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
;; *** IMP NODE #2 SETUP ***
|
||||||
|
|
||||||
|
; IMP #2 connects to IMP #2 via modem line 1 on both ends ...
|
||||||
|
; Also, a host is connected to host line 1
|
||||||
|
|
||||||
|
; 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
|
||||||
|
|
||||||
|
; Port numbering scheme.
|
||||||
|
; The host is 45xx
|
||||||
|
; IMPs are 44xx
|
||||||
|
;
|
||||||
|
; port abcd
|
||||||
|
; iom
|
||||||
|
; a 4
|
||||||
|
; b 4
|
||||||
|
; c iom number
|
||||||
|
; d line # 1-5 mi1-mi5 6-9 hi1-h15
|
||||||
|
; host
|
||||||
|
; a 4
|
||||||
|
; b 5
|
||||||
|
; c host number
|
||||||
|
; d 0
|
||||||
|
|
||||||
|
; Start up the modem links!
|
||||||
|
echo Attaching modem links ...
|
||||||
|
ATTACH MI1 4421::4431
|
||||||
|
|
||||||
|
; Start up the host links!
|
||||||
|
echo Attaching host links ...
|
||||||
|
ATTACH HI1 4426::4500
|
||||||
|
;set hi1 debug=warn;udp;io
|
||||||
|
;set mi1 debug=udp
|
||||||
|
; And we're done ..
|
||||||
|
;echo Type GO to start ...
|
||||||
|
Go
|
||||||
|
|
Loading…
Add table
Reference in a new issue