-
Notifications
You must be signed in to change notification settings - Fork 137
Expand file tree
/
Copy pathh316_mi.c
More file actions
771 lines (656 loc) · 35.3 KB
/
h316_mi.c
File metadata and controls
771 lines (656 loc) · 35.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
/* 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
ILOOP 1 interface (local) loopback enabled
RLOOP 1 remote (line) 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 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 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, CONST char *cptr);
t_stat mi_detach (UNIT *uptr);
t_stat mi_set_loopback (UNIT *uptr, int32 val, CONST char *cptr, void *desc);
t_stat mi_show_loopback (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
////////////////////////////////////////////////////////////////////////////////
////////////////////// 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), REG_RO + PV_RZRO }, \
{ FLDATA (ILOOP, mi##N##_db.iloop, 0), REG_RO + 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) { \
{ MTAB_XTD|MTAB_VDV, 0, "LOOPBACK", "LOOPINTERFACE", &mi_set_loopback, &mi_show_loopback, NULL }, \
{ MTAB_XTD|MTAB_VDV, 1, NULL, "NOLOOPINTERFACE", &mi_set_loopback, NULL, NULL }, \
{ MTAB_XTD|MTAB_VDV, 2, NULL, "LOOPLINE", &mi_set_loopback, NULL, NULL }, \
{ MTAB_XTD|MTAB_VDV, 3, NULL, "NOLOOPLINE", &mi_set_loopback, NULL, NULL }, \
{ 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;
udp_set_link_loopback (PDEVICE(line), PMIDB(line)->link, 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;
udp_set_link_loopback (PDEVICE(line), PMIDB(line)->link, 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...
sim_printf("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) {
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;
udp_set_link_loopback (PDEVICE(line), PMIDB(line)->link, 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;
udp_set_link_loopback (PDEVICE(line), PMIDB(line)->link, 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;
udp_set_link_loopback (PDEVICE(line), PMIDB(line)->link, 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);
}
////////////////////////////////////////////////////////////////////////////////
/////////////////// H O S T E V E N T S E R V I C E ////////////////////
////////////////////////////////////////////////////////////////////////////////
// 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, CONST 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')) != 0);
// 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)
return sim_messagef(SCPE_ARG,"MI%d - physical COM support is not yet implemented\n", line);
// 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));
}
t_stat mi_set_loopback (UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
t_stat ret = SCPE_OK; uint16 line = uptr->mline;
switch (val) {
case 0: // LOOPINTERFACE
case 1: // NOLOOPINTERFACE
PMIDB(line)->iloop = (val == 0) ? 1 : 0;
break;
case 2: // LOOPLINE
case 3: // NOLOOPLINE
if (PMIDB(line)->link == NOLINK)
return SCPE_UNATT;
val = (val == 2) ? 1 : 0;
ret = udp_set_link_loopback (PDEVICE(line), PMIDB(line)->link, val);
if (ret == SCPE_OK)
PMIDB(line)->lloop = val;
break;
}
return ret;
}
t_stat mi_show_loopback (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
uint16 line = uptr->mline;
if (PMIDB(line)->iloop)
fprintf (st, "Interface (local) Loopback");
if (PMIDB(line)->lloop)
fprintf (st, "Line (remote) Loopback");
return SCPE_OK;
}
#endif // #ifdef VM_IMPTIP from the very top