1 /* altairz80_sio.c: MITS Altair serial I/O card
3 Copyright (c) 2002-2008, Peter Schorn
5 Permission is hereby granted, free of charge, to any person obtaining a
6 copy of this software and associated documentation files (the "Software"),
7 to deal in the Software without restriction, including without limitation
8 the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 and/or sell copies of the Software, and to permit persons to whom the
10 Software is furnished to do so, subject to the following conditions:
12 The above copyright notice and this permission notice shall be included in
13 all copies or substantial portions of the Software.
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 Except as contained in this notice, the name of Peter Schorn shall not
23 be used in advertising or otherwise to promote the sale, use or other dealings
24 in this Software without prior written authorization from Peter Schorn.
26 Based on work by Charles E Owen (c) 1997
28 These functions support a simulated MITS 2SIO interface card.
29 The card had two physical I/O ports which could be connected
30 to any serial I/O device that would connect to a current loop,
31 RS232, or TTY interface. Available baud rates were jumper
32 selectable for each port from 110 to 9600.
34 All I/O is via programmed I/O. Each device has a status port
35 and a data port. A write to the status port can select
36 some options for the device (0x03 will reset the port).
37 A read of the status port gets the port status:
39 +---+---+---+---+---+---+---+---+
40 | X | X | X | X | X | X | O | I |
41 +---+---+---+---+---+---+---+---+
43 I - A 1 in this bit position means a character has been received
44 on the data port and is ready to be read.
45 O - A 1 in this bit means the port is ready to receive a character
46 on the data port and transmit it out over the serial line.
48 A read to the data port gets the buffered character, a write
49 to the data port writes the character to the device.
54 #include "altairz80_defs.h"
61 #elif defined (_WIN32)
65 #define UNIT_V_SIO_ANSI (UNIT_V_UF + 0) /* ANSI mode, strip bit 8 on output */
66 #define UNIT_SIO_ANSI (1 << UNIT_V_SIO_ANSI)
67 #define UNIT_V_SIO_UPPER (UNIT_V_UF + 1) /* upper case mode */
68 #define UNIT_SIO_UPPER (1 << UNIT_V_SIO_UPPER)
69 #define UNIT_V_SIO_BS (UNIT_V_UF + 2) /* map delete to backspace */
70 #define UNIT_SIO_BS (1 << UNIT_V_SIO_BS)
71 #define UNIT_V_SIO_VERBOSE (UNIT_V_UF + 3) /* verbose mode, i.e. show error messages */
72 #define UNIT_SIO_VERBOSE (1 << UNIT_V_SIO_VERBOSE)
73 #define UNIT_V_SIO_MAP (UNIT_V_UF + 4) /* mapping mode on */
74 #define UNIT_SIO_MAP (1 << UNIT_V_SIO_MAP)
75 #define UNIT_V_SIO_BELL (UNIT_V_UF + 5) /* ^G (bell character) rings bell */
76 #define UNIT_SIO_BELL (1 << UNIT_V_SIO_BELL)
77 #define UNIT_V_SIO_INTERRUPT (UNIT_V_UF + 6) /* create keyboard interrupts */
78 #define UNIT_SIO_INTERRUPT (1 << UNIT_V_SIO_INTERRUPT)
79 #define UNIT_V_SIO_SLEEP (UNIT_V_UF + 7) /* sleep after keyboard status check */
80 #define UNIT_SIO_SLEEP (1 << UNIT_V_SIO_SLEEP)
82 #define UNIT_V_SIMH_VERBOSE (UNIT_V_UF + 0) /* verbose mode for SIMH pseudo device */
83 #define UNIT_SIMH_VERBOSE (1 << UNIT_V_SIMH_VERBOSE)
84 #define UNIT_V_SIMH_TIMERON (UNIT_V_UF + 1) /* SIMH pseudo device timer generate interrupts */
85 #define UNIT_SIMH_TIMERON (1 << UNIT_V_SIMH_TIMERON)
87 #define TERMINALS 4 /* lines per mux */
88 #define SIO_CAN_READ 0x01 /* bit 0 is set iff character available */
89 #define SIO_CAN_WRITE 0x02 /* bit 1 is set iff character can be sent */
90 #define SIO_RESET 0x03 /* Command to reset SIO */
91 #define VGSIO_CAN_READ 0x02 /* bit 1 is set iff character available */
92 #define VGSIO_CAN_WRITE 0x01 /* bit 0 is set iff character can be sent */
93 #define KBD_HAS_CHAR 0x40 /* bit 6 is set iff character available */
94 #define KBD_HAS_NO_CHAR 0x01 /* bit 0 is set iff no character is available */
96 #define BACKSPACE_CHAR 0x08 /* backspace character */
97 #define DELETE_CHAR 0x7f /* delete character */
98 #define CONTROLC_CHAR 0x03 /* control C character */
99 #define CONTROLG_CHAR 0x07 /* control G char., rings bell when displayed */
100 #define CONTROLZ_CHAR 0x1a /* control Z character */
102 #define PORT_TABLE_SIZE 256 /* size of port mapping table */
103 #define SLEEP_ALLOWED_START_DEFAULT 100 /* default initial value for sleepAllowedCounter*/
105 static t_stat
sio_set_verbose (UNIT
*uptr
, int32 value
, char *cptr
, void *desc
);
106 static t_stat
simh_dev_set_timeron (UNIT
*uptr
, int32 value
, char *cptr
, void *desc
);
107 static t_stat
simh_dev_set_timeroff (UNIT
*uptr
, int32 value
, char *cptr
, void *desc
);
108 static t_stat
sio_reset(DEVICE
*dptr
);
109 static t_stat
sio_attach(UNIT
*uptr
, char *cptr
);
110 static t_stat
sio_detach(UNIT
*uptr
);
111 static t_stat
ptr_reset(DEVICE
*dptr
);
112 static t_stat
ptp_reset(DEVICE
*dptr
);
113 static t_stat
toBool(char tf
, int *result
);
114 static t_stat
sio_dev_set_port(UNIT
*uptr
, int32 value
, char *cptr
, void *desc
);
115 static t_stat
sio_dev_show_port(FILE *st
, UNIT
*uptr
, int32 val
, void *desc
);
116 static t_stat
sio_dev_set_interrupton(UNIT
*uptr
, int32 value
, char *cptr
, void *desc
);
117 static t_stat
sio_dev_set_interruptoff(UNIT
*uptr
, int32 value
, char *cptr
, void *desc
);
118 static t_stat
sio_svc(UNIT
*uptr
);
119 static t_stat
simh_dev_reset(DEVICE
*dptr
);
120 static t_stat
simh_svc(UNIT
*uptr
);
121 int32
nulldev (const int32 port
, const int32 io
, const int32 data
);
122 int32
sr_dev (const int32 port
, const int32 io
, const int32 data
);
123 int32
simh_dev (const int32 port
, const int32 io
, const int32 data
);
124 int32
sio0d (const int32 port
, const int32 io
, const int32 data
);
125 int32
sio0s (const int32 port
, const int32 io
, const int32 data
);
126 int32
sio1d (const int32 port
, const int32 io
, const int32 data
);
127 int32
sio1s (const int32 port
, const int32 io
, const int32 data
);
128 void printMessage(void);
129 void do_SIMH_sleep(void);
130 static void pollConnection(void);
131 static int32
mapCharacter(int32 ch
);
132 static void checkSleep(void);
133 static void voidSleep(void);
135 extern int32
getBankSelect(void);
136 extern void setBankSelect(const int32 b
);
137 extern uint32
getCommon(void);
138 extern uint8
GetBYTEWrapper(const uint32 Addr
);
139 extern uint32
sim_map_resource(uint32 baseaddr
, uint32 size
, uint32 resource_type
,
140 int32 (*routine
)(const int32
, const int32
, const int32
), uint8 unmap
);
142 extern int32 chiptype
;
143 extern const t_bool rtc_avail
;
144 extern FILE *sim_log
;
146 extern int32 sim_switches
;
147 extern const char *scp_error_messages
[];
149 extern UNIT cpu_unit
;
150 extern volatile int32 stop_cpu
;
151 extern int32 sim_interval
;
153 /* SIMH pseudo device status registers */
154 /* ZSDOS clock definitions */
155 static time_t ClockZSDOSDelta
= 0; /* delta between real clock and Altair clock */
156 static int32 setClockZSDOSPos
= 0; /* determines state for receiving address of parameter block */
157 static int32 setClockZSDOSAdr
= 0; /* address in M of 6 byte parameter block for setting time */
158 static int32 getClockZSDOSPos
= 0; /* determines state for sending clock information */
160 /* CPM3 clock definitions */
161 static time_t ClockCPM3Delta
= 0; /* delta between real clock and Altair clock */
162 static int32 setClockCPM3Pos
= 0; /* determines state for receiving address of parameter block */
163 static int32 setClockCPM3Adr
= 0; /* address in M of 5 byte parameter block for setting time */
164 static int32 getClockCPM3Pos
= 0; /* determines state for sending clock information */
165 static int32 daysCPM3SinceOrg
= 0; /* days since 1 Jan 1978 */
167 /* interrupt related */
168 static uint32 timeOfNextInterrupt
; /* time when next interrupt is scheduled */
169 int32 timerInterrupt
= FALSE
; /* timer interrupt pending */
170 int32 timerInterruptHandler
= 0x0fc00; /* default address of interrupt handling routine */
171 static int32 setTimerInterruptAdrPos
= 0; /* determines state for receiving timerInterruptHandler */
172 static int32 timerDelta
= 100; /* interrupt every 100 ms */
173 static int32 setTimerDeltaPos
= 0; /* determines state for receiving timerDelta */
175 /* stop watch and timer related */
176 static uint32 stopWatchDelta
= 0; /* stores elapsed time of stop watch */
177 static int32 getStopWatchDeltaPos
= 0; /* determines the state for receiving stopWatchDelta */
178 static uint32 stopWatchNow
= 0; /* stores starting time of stop watch */
179 static int32 markTimeSP
= 0; /* stack pointer for timer stack */
181 /* default time in microseconds to sleep for SIMHSleepCmd */
183 static uint32 SIMHSleep
= 1000; /* Sleep uses milliseconds */
184 #elif defined (__MWERKS__) && defined (macintosh)
185 static uint32 SIMHSleep
= 0; /* no sleep on Macintosh OS9 */
187 static uint32 SIMHSleep
= 100; /* on other platforms 100 micro seconds is good enough */
189 static uint32 sleepAllowedCounter
= 0; /* only sleep on no character available when == 0 */
190 static uint32 sleepAllowedStart
= SLEEP_ALLOWED_START_DEFAULT
; /* default start for above counter */
193 static int32 versionPos
= 0; /* determines state for sending device identifier */
194 static int32 lastCPMStatus
= 0; /* result of last attachCPM command */
195 static int32 lastCommand
= 0; /* most recent command processed on port 0xfeh */
196 static int32 getCommonPos
= 0; /* determines state for sending the 'common' register */
198 /* support for wild card expansion */
201 static uint32 globPosNameList
= 0;
202 static int32 globPosName
= 0;
203 static int32 globValid
= FALSE
;
204 static int32 globError
= 0;
205 #elif defined (_WIN32)
206 static WIN32_FIND_DATA FindFileData
;
207 static HANDLE hFind
= INVALID_HANDLE_VALUE
;
208 static int32 globFinished
= FALSE
;
209 static int32 globValid
= FALSE
;
210 static int32 globPosName
= 0;
213 /* SIO status registers */
214 static int32 warnLevelSIO
= 3; /* display at most 'warnLevelSIO' times the same warning */
215 static int32 warnUnattachedPTP
= 0; /* display a warning message if < warnLevel and SIO set to
216 VERBOSE and output to PTP without an attached file */
217 static int32 warnUnattachedPTR
= 0; /* display a warning message if < warnLevel and SIO set to
218 VERBOSE and attempt to read from PTR without an attached file */
219 static int32 warnPTREOF
= 0; /* display a warning message if < warnLevel and SIO set to
220 VERBOSE and attempt to read from PTR past EOF */
221 static int32 warnUnassignedPort
= 0; /* display a warning message if < warnLevel and SIO set to
222 VERBOSE and attempt to perform IN or OUT on an unassigned PORT */
224 int32 keyboardInterrupt
= FALSE
; /* keyboard interrupt pending */
225 uint32 keyboardInterruptHandler
= 0x0038;/* address of keyboard interrupt handler */
227 static TMLN TerminalLines
[TERMINALS
] = { /* four terminals */
231 static TMXR altairTMXR
= { /* mux descriptor */
232 TERMINALS
, 0, 0, TerminalLines
235 static UNIT sio_unit
= {
236 UDATA (&sio_svc
, UNIT_ATTABLE
| UNIT_SIO_MAP
| UNIT_SIO_SLEEP
, 0),
238 FALSE
, /* u3 = FALSE, no character available in buffer */
239 FALSE
, /* u4 = FALSE, terminal input is not attached to a file */
240 FALSE
, /* u5 = FALSE, terminal input has not yet reached EOF */
241 0 /* u6 = 0, not used */
244 static REG sio_reg
[] = {
245 { DRDATA (SIOWLEV
, warnLevelSIO
, 32) },
246 { DRDATA (WRNUPTP
, warnUnattachedPTP
, 32) },
247 { DRDATA (WRNUPTR
, warnUnattachedPTR
, 32) },
248 { DRDATA (WRNPTRE
, warnPTREOF
, 32) },
249 { DRDATA (WRUPORT
, warnUnassignedPort
, 32) },
250 { HRDATA (FILEATT
, sio_unit
.u4
, 8), REG_RO
}, /* TRUE iff terminal input is attached to a file */
251 { HRDATA (FILEEOF
, sio_unit
.u5
, 8), REG_RO
}, /* TRUE iff terminal input file has reached EOF */
252 { HRDATA (TSTATUS
, sio_unit
.u3
, 8) }, /* TRUE iff a character available in sio_unit.buf */
253 { DRDATA (TBUFFER
, sio_unit
.buf
, 8) }, /* input buffer for one character */
254 { DRDATA (KEYBDI
, keyboardInterrupt
, 3), REG_RO
},
255 { HRDATA (KEYBDH
, keyboardInterruptHandler
, 16) },
259 static MTAB sio_mod
[] = {
260 { UNIT_SIO_ANSI
, 0, "TTY", "TTY", NULL
}, /* keep bit 8 as is for output */
261 { UNIT_SIO_ANSI
, UNIT_SIO_ANSI
, "ANSI", "ANSI", NULL
}, /* set bit 8 to 0 before output */
262 { UNIT_SIO_UPPER
, 0, "ALL", "ALL", NULL
}, /* do not change case of input characters */
263 { UNIT_SIO_UPPER
, UNIT_SIO_UPPER
, "UPPER", "UPPER", NULL
}, /* change input characters to upper case */
264 { UNIT_SIO_BS
, 0, "BS", "BS", NULL
}, /* map delete to backspace */
265 { UNIT_SIO_BS
, UNIT_SIO_BS
, "DEL", "DEL", NULL
}, /* map backspace to delete */
266 { UNIT_SIO_VERBOSE
, 0, "QUIET", "QUIET", NULL
}, /* quiet, no error messages */
267 { UNIT_SIO_VERBOSE
, UNIT_SIO_VERBOSE
, "VERBOSE", "VERBOSE", &sio_set_verbose
},
268 /* verbose, display warning messages */
269 { UNIT_SIO_MAP
, 0, "NOMAP", "NOMAP", NULL
}, /* disable character mapping */
270 { UNIT_SIO_MAP
, UNIT_SIO_MAP
, "MAP", "MAP", NULL
}, /* enable all character mapping */
271 { UNIT_SIO_BELL
, 0, "BELL", "BELL", NULL
}, /* enable bell character */
272 { UNIT_SIO_BELL
, UNIT_SIO_BELL
, "NOBELL", "NOBELL", NULL
}, /* suppress ringing the bell */
273 { UNIT_SIO_SLEEP
, 0, "NOSLEEP", "NOSLEEP", NULL
}, /* no sleep after keyboard status check */
274 { UNIT_SIO_SLEEP
, UNIT_SIO_SLEEP
, "SLEEP", "SLEEP", NULL
}, /* sleep after keyboard status check */
275 /* no keyboard interrupts */
276 { UNIT_SIO_INTERRUPT
, 0, "NOINTERRUPT","NOINTERRUPT",&sio_dev_set_interruptoff
},
277 /* create keyboard interrupts */
278 { UNIT_SIO_INTERRUPT
, UNIT_SIO_INTERRUPT
, "INTERRUPT","INTERRUPT",&sio_dev_set_interrupton
},
279 { MTAB_XTD
|MTAB_VDV
|MTAB_VAL
, 0, "PORT", "PORT", &sio_dev_set_port
, &sio_dev_show_port
},
284 "SIO", &sio_unit
, sio_reg
, sio_mod
,
286 NULL
, NULL
, &sio_reset
,
287 NULL
, &sio_attach
, &sio_detach
,
291 static UNIT ptr_unit
= {
292 UDATA (NULL
, UNIT_SEQ
| UNIT_ATTABLE
| UNIT_ROABLE
, 0)
295 static REG ptr_reg
[] = {
296 { HRDATA (STAT
, ptr_unit
.u3
, 8) },
301 "PTR", &ptr_unit
, ptr_reg
, NULL
,
303 NULL
, NULL
, &ptr_reset
,
309 static UNIT ptp_unit
= {
310 UDATA (NULL
, UNIT_SEQ
+ UNIT_ATTABLE
, 0)
314 "PTP", &ptp_unit
, NULL
, NULL
,
316 NULL
, NULL
, &ptp_reset
,
322 /* Synthetic device SIMH for communication
323 between Altair and SIMH environment using port 0xfe */
324 static UNIT simh_unit
= {
325 UDATA (&simh_svc
, 0, 0), KBD_POLL_WAIT
328 static REG simh_reg
[] = {
329 { DRDATA (CZD
, ClockZSDOSDelta
, 32) },
330 { DRDATA (SCZP
, setClockZSDOSPos
, 8), REG_RO
},
331 { HRDATA (SCZA
, setClockZSDOSAdr
, 16), REG_RO
},
332 { DRDATA (GCZP
, getClockZSDOSPos
, 8), REG_RO
},
334 { DRDATA (CC3D
, ClockCPM3Delta
, 32) },
335 { DRDATA (SC3DP
, setClockCPM3Pos
, 8), REG_RO
},
336 { HRDATA (SC3DA
, setClockCPM3Adr
, 16), REG_RO
},
337 { DRDATA (GC3DP
, getClockCPM3Pos
, 8), REG_RO
},
338 { DRDATA (D3DO
, daysCPM3SinceOrg
, 32), REG_RO
},
340 { DRDATA (TOFNI
, timeOfNextInterrupt
, 32), REG_RO
},
341 { DRDATA (TIMI
, timerInterrupt
, 3) },
342 { HRDATA (TIMH
, timerInterruptHandler
, 16) },
343 { DRDATA (STIAP
, setTimerInterruptAdrPos
,8), REG_RO
},
344 { DRDATA (TIMD
, timerDelta
, 32) },
345 { DRDATA (STDP
, setTimerDeltaPos
, 8), REG_RO
},
346 { DRDATA (SLEEP
, SIMHSleep
, 32) },
347 { DRDATA (VOSLP
, sleepAllowedStart
, 32) },
349 { DRDATA (STPDT
, stopWatchDelta
, 32), REG_RO
},
350 { DRDATA (STPOS
, getStopWatchDeltaPos
, 8), REG_RO
},
351 { DRDATA (STPNW
, stopWatchNow
, 32), REG_RO
},
352 { DRDATA (MTSP
, markTimeSP
, 8), REG_RO
},
354 { DRDATA (VPOS
, versionPos
, 8), REG_RO
},
355 { DRDATA (LCPMS
, lastCPMStatus
, 8), REG_RO
},
356 { DRDATA (LCMD
, lastCommand
, 8), REG_RO
},
357 { DRDATA (CPOS
, getCommonPos
, 8), REG_RO
},
361 static MTAB simh_mod
[] = {
362 /* quiet, no warning messages */
363 { UNIT_SIMH_VERBOSE
, 0, "QUIET", "QUIET", NULL
},
364 /* verbose, display warning messages */
365 { UNIT_SIMH_VERBOSE
, UNIT_SIMH_VERBOSE
, "VERBOSE", "VERBOSE", NULL
},
366 /* timer generated interrupts are off */
367 { UNIT_SIMH_TIMERON
, 0, "TIMEROFF", "TIMEROFF", &simh_dev_set_timeroff
},
368 /* timer generated interrupts are on */
369 { UNIT_SIMH_TIMERON
, UNIT_SIMH_TIMERON
, "TIMERON", "TIMERON", &simh_dev_set_timeron
},
373 DEVICE simh_device
= {
374 "SIMH", &simh_unit
, simh_reg
, simh_mod
,
376 NULL
, NULL
, &simh_dev_reset
,
382 char messageBuffer
[256] = { 0 };
384 void printMessage(void) {
385 printf(messageBuffer
);
388 fprintf(sim_log
, messageBuffer
);
389 fprintf(sim_log
,"\n");
393 static void resetSIOWarningFlags(void) {
394 warnUnattachedPTP
= warnUnattachedPTR
= warnPTREOF
= warnUnassignedPort
= 0;
397 static t_stat
sio_set_verbose(UNIT
*uptr
, int32 value
, char *cptr
, void *desc
) {
398 resetSIOWarningFlags();
402 static t_stat
sio_attach(UNIT
*uptr
, char *cptr
) {
403 t_stat r
= SCPE_IERR
;
404 sio_unit
.u3
= FALSE
; /* no character in terminal input buffer */
405 get_uint(cptr
, 10, 65535, &r
); /* attempt to get port, discard result */
406 if (r
== SCPE_OK
) { /* string can be interpreted as port number */
407 sio_unit
.u4
= FALSE
; /* terminal input is not attached to a file */
408 return tmxr_attach(&altairTMXR
, uptr
, cptr
); /* attach mux */
410 sio_unit
.u4
= TRUE
; /* terminal input is attached to a file */
411 sio_unit
.u5
= FALSE
; /* EOF not yet reached */
412 return attach_unit(uptr
, cptr
);
415 static t_stat
sio_detach(UNIT
*uptr
) {
416 sio_unit
.u3
= FALSE
; /* no character in terminal input buffer */
417 if (sio_unit
.u4
) { /* is terminal input attached to a file? */
418 sio_unit
.u4
= FALSE
; /* not anymore, detach */
419 return detach_unit(uptr
);
421 return tmxr_detach(&altairTMXR
, uptr
);
424 static void pollConnection(void) {
425 if (sio_unit
.flags
& UNIT_ATT
) {
426 int32 temp
= tmxr_poll_conn(&altairTMXR
); /* poll connection */
428 TerminalLines
[temp
].rcve
= 1; /* enable receive */
429 tmxr_poll_rx(&altairTMXR
); /* poll input */
430 tmxr_poll_tx(&altairTMXR
); /* poll output */
435 static t_stat
sio_reset(DEVICE
*dptr
) {
437 sio_unit
.u3
= FALSE
; /* no character in terminal input buffer */
438 resetSIOWarningFlags();
439 if (sio_unit
.u4
) { /* is terminal input attached to a file? */
440 rewind(sio_unit
.fileref
); /* yes, rewind input */
441 sio_unit
.u5
= FALSE
; /* EOF not yet reached */
443 else if (sio_unit
.flags
& UNIT_ATT
)
444 for (i
= 0; i
< TERMINALS
; i
++)
445 if (TerminalLines
[i
].conn
)
446 tmxr_reset_ln(&TerminalLines
[i
]);
450 static t_stat
ptr_reset(DEVICE
*dptr
) {
451 resetSIOWarningFlags();
452 ptr_unit
.u3
= FALSE
; /* End Of File not yet reached */
453 if (ptr_unit
.flags
& UNIT_ATT
) /* attached? */
454 rewind(ptr_unit
.fileref
);
458 static t_stat
ptp_reset(DEVICE
*dptr
) {
459 resetSIOWarningFlags();
463 static int32
mapCharacter(int32 ch
) {
465 if (sio_unit
.flags
& UNIT_SIO_MAP
) {
466 if (sio_unit
.flags
& UNIT_SIO_BS
) {
467 if (ch
== BACKSPACE_CHAR
)
470 else if (ch
== DELETE_CHAR
)
471 return BACKSPACE_CHAR
;
472 if (sio_unit
.flags
& UNIT_SIO_UPPER
)
478 /* I/O instruction handlers, called from the CPU module when an
479 IN or OUT instruction is issued.
481 Each function is passed an 'io' flag, where 0 means a read from
482 the port, and 1 means a write to the port. On input, the actual
483 input is passed as the return value, on output, 'data' is written
486 Port 1 controls console I/O. We distinguish three cases:
487 1) SIO attached to a file (i.e. input taken from a file )
488 2) SIO attached to a port (i.e. Telnet console I/O )
489 3) SIO not attached to a port (i.e. "regular" console I/O )
493 int32 port
; /* this information belongs to port number 'port' */
494 int32 terminalLine
; /* map to this 'terminalLine' */
495 int32 sio_can_read
; /* bit mask to indicate that one can read from this port */
496 int32 sio_cannot_read
; /* bit mask to indicate that one cannot read from this port */
497 int32 sio_can_write
; /* bit mask to indicate that one can write to this port */
498 int32 hasReset
; /* TRUE iff SIO has reset command */
499 int32 sio_reset
; /* reset command */
500 int32 hasOUT
; /* TRUE iff port supports OUT command */
501 int32 isBuiltin
; /* TRUE iff mapping is built in */
504 static SIO_PORT_INFO port_table
[PORT_TABLE_SIZE
] = {
505 {0x00, 0, KBD_HAS_CHAR
, KBD_HAS_NO_CHAR
, SIO_CAN_WRITE
, FALSE
, 0, FALSE
, TRUE
},
506 {0x01, 0, 0, 0, 0, FALSE
, 0, FALSE
, TRUE
},
507 {0x02, 0, VGSIO_CAN_READ
, 0, VGSIO_CAN_WRITE
, FALSE
, 0, TRUE
, TRUE
},
508 {0x03, 0, VGSIO_CAN_READ
, 0, VGSIO_CAN_WRITE
, FALSE
, 0, FALSE
, TRUE
},
509 {0x10, 0, SIO_CAN_READ
, 0, SIO_CAN_WRITE
, TRUE
, SIO_RESET
, FALSE
, TRUE
},
510 {0x14, 1, SIO_CAN_READ
, 0, SIO_CAN_WRITE
, TRUE
, SIO_RESET
, FALSE
, TRUE
},
511 {0x16, 2, SIO_CAN_READ
, 0, SIO_CAN_WRITE
, TRUE
, SIO_RESET
, FALSE
, TRUE
},
512 {0x18, 3, SIO_CAN_READ
, 0, SIO_CAN_WRITE
, TRUE
, SIO_RESET
, FALSE
, TRUE
},
513 {0x11, 0, SIO_CAN_READ
, 0, SIO_CAN_WRITE
, TRUE
, SIO_RESET
, TRUE
, TRUE
},
514 {0x15, 1, SIO_CAN_READ
, 0, SIO_CAN_WRITE
, TRUE
, SIO_RESET
, TRUE
, TRUE
},
515 {0x17, 2, SIO_CAN_READ
, 0, SIO_CAN_WRITE
, TRUE
, SIO_RESET
, TRUE
, TRUE
},
516 {0x19, 3, SIO_CAN_READ
, 0, SIO_CAN_WRITE
, TRUE
, SIO_RESET
, TRUE
, TRUE
},
517 {-1, 0, 0, 0, 0, 0, 0, 0, 0} /* must be last */
520 static SIO_PORT_INFO
lookupPortInfo(const int32 port
, int32
*position
) {
522 while ((port_table
[i
].port
!= -1) && (port_table
[i
].port
!= port
)) i
++;
524 return port_table
[i
];
527 /* keyboard idle detection: sleep when feature enabled, no character available
528 (duty of caller) and operation not voided (e.g. when output is available) */
529 static void checkSleep(void) {
530 if (sio_unit
.flags
& UNIT_SIO_SLEEP
) {
531 if (sleepAllowedCounter
) sleepAllowedCounter
--;
532 else do_SIMH_sleep();
536 /* void sleep for next 'sleepAllowedStart' tests */
537 static void voidSleep(void) {
538 sleepAllowedCounter
= sleepAllowedStart
;
541 /* generic status port for keyboard input / terminal output */
542 int32
sio0s(const int32 port
, const int32 io
, const int32 data
) {
544 SIO_PORT_INFO spi
= lookupPortInfo(port
, &ch
);
545 assert(spi
.port
== port
);
547 if (io
== 0) { /* IN */
548 if (sio_unit
.u4
) /* attached to a file? */
549 if (sio_unit
.u5
) /* EOF reached? */
550 sio_detach(&sio_unit
); /* detach file and switch to keyboard input */
551 else return spi
.sio_can_read
| spi
.sio_can_write
;
552 if (sio_unit
.flags
& UNIT_ATT
) { /* attached to a port? */
553 if (tmxr_rqln(&TerminalLines
[spi
.terminalLine
]))
554 result
= spi
.sio_can_read
;
556 result
= spi
.sio_cannot_read
;
559 return result
| /* read possible if character available */
560 (TerminalLines
[spi
.terminalLine
].conn
&& TerminalLines
[spi
.terminalLine
].xmte
? spi
.sio_can_write
: 0x00);
561 /* write possible if connected and transmit
564 if (sio_unit
.u3
) /* character available? */
565 return spi
.sio_can_read
| spi
.sio_can_write
;
566 ch
= sim_poll_kbd(); /* no, try to get a character */
567 if (ch
) { /* character available? */
568 if (ch
== SCPE_STOP
) { /* stop CPU in case ^E (default) was typed */
570 sim_interval
= 0; /* detect stop condition as soon as possible*/
571 return spi
.sio_can_write
| spi
.sio_cannot_read
; /* do not consume stop character */
573 sio_unit
.u3
= TRUE
; /* indicate character available */
574 sio_unit
.buf
= ch
; /* store character in buffer */
575 return spi
.sio_can_read
| spi
.sio_can_write
;
578 return spi
.sio_can_write
| spi
.sio_cannot_read
;
579 } /* OUT follows, no fall-through from IN */
580 if (spi
.hasReset
&& (data
== spi
.sio_reset
)) /* reset command */
581 sio_unit
.u3
= FALSE
; /* indicate that no character is available */
582 return 0x00; /* ignored since OUT */
585 /* generic data port for keyboard input / terminal output */
586 int32
sio0d(const int32 port
, const int32 io
, const int32 data
) {
588 SIO_PORT_INFO spi
= lookupPortInfo(port
, &ch
);
589 assert(spi
.port
== port
);
591 if (io
== 0) { /* IN */
592 if (sio_unit
.u4
) { /* attached to a file? */
593 if (sio_unit
.u5
) { /* EOF reached? */
594 sio_detach(&sio_unit
); /* detach file and switch to keyboard input */
595 return CONTROLC_CHAR
; /* this time return ^C after all */
597 if ((ch
= getc(sio_unit
.fileref
)) == EOF
) { /* end of file? */
598 sio_unit
.u5
= TRUE
; /* terminal input file has reached EOF */
599 return CONTROLC_CHAR
; /* result is ^C (= CP/M interrupt) */
601 return mapCharacter(ch
); /* return mapped character */
603 if (sio_unit
.flags
& UNIT_ATT
)
604 return mapCharacter(tmxr_getc_ln(&TerminalLines
[spi
.terminalLine
]));
605 sio_unit
.u3
= FALSE
; /* no character is available any more */
606 return mapCharacter(sio_unit
.buf
); /* return previous character */
607 } /* OUT follows, no fall-through from IN */
609 ch
= sio_unit
.flags
& UNIT_SIO_ANSI
? data
& 0x7f : data
; /* clear highest bit in ANSI mode */
610 if ((ch
!= CONTROLG_CHAR
) || !(sio_unit
.flags
& UNIT_SIO_BELL
)) {
612 if ((sio_unit
.flags
& UNIT_ATT
) && (!sio_unit
.u4
)) /* attached to a port and not to a file */
613 tmxr_putc_ln(&TerminalLines
[spi
.terminalLine
], ch
); /* status ignored */
618 return 0x00; /* ignored since OUT */
621 /* PTR/PTP status port */
622 int32
sio1s(const int32 port
, const int32 io
, const int32 data
) {
623 if (io
== 0) { /* IN */
624 /* reset I bit iff PTR unit not attached or
625 no more data available. O bit is always
626 set since write always possible. */
627 if ((ptr_unit
.flags
& UNIT_ATT
) == 0) { /* PTR is not attached */
628 if ((sio_unit
.flags
& UNIT_SIO_VERBOSE
) && (warnUnattachedPTR
< warnLevelSIO
)) {
630 /*06*/ MESSAGE_2("Attempt to test status of unattached PTR[0x%02x]. 0x02 returned.", port
);
632 return SIO_CAN_WRITE
;
634 /* if EOF then SIO_CAN_WRITE else
635 (SIO_CAN_WRITE and SIO_CAN_READ) */
636 return ptr_unit
.u3
? SIO_CAN_WRITE
: (SIO_CAN_READ
| SIO_CAN_WRITE
);
638 if (data
== SIO_RESET
)
639 ptr_unit
.u3
= FALSE
; /* reset EOF indicator */
640 return 0x00; /* ignored since OUT */
643 /* PTR/PTP data port */
644 int32
sio1d(const int32 port
, const int32 io
, const int32 data
) {
646 if (io
== 0) { /* IN */
647 if (ptr_unit
.u3
) { /* EOF reached, no more data available */
648 if ((sio_unit
.flags
& UNIT_SIO_VERBOSE
) && (warnPTREOF
< warnLevelSIO
)) {
650 /*07*/ MESSAGE_2("PTR[0x%02x] attempted to read past EOF. 0x00 returned.", port
);
654 if ((ptr_unit
.flags
& UNIT_ATT
) == 0) { /* not attached */
655 if ((sio_unit
.flags
& UNIT_SIO_VERBOSE
) && (warnUnattachedPTR
< warnLevelSIO
)) {
657 /*08*/ MESSAGE_2("Attempt to read from unattached PTR[0x%02x]. 0x00 returned.", port
);
661 if ((ch
= getc(ptr_unit
.fileref
)) == EOF
) { /* end of file? */
662 ptr_unit
.u3
= TRUE
; /* remember EOF reached */
663 return CONTROLZ_CHAR
; /* ^Z denotes end of text file in CP/M */
667 if (ptp_unit
.flags
& UNIT_ATT
) /* unit must be attached */
668 putc(data
, ptp_unit
.fileref
);
669 /* else ignore data */
670 else if ((sio_unit
.flags
& UNIT_SIO_VERBOSE
) && (warnUnattachedPTP
< warnLevelSIO
)) {
672 /*09*/ MESSAGE_3("Attempt to output '0x%02x' to unattached PTP[0x%02x] - ignored.", data
, port
);
674 return 0x00; /* ignored since OUT */
677 static t_stat
toBool(char tf
, int *result
) {
689 static void show_sio_port_info(FILE *st
, SIO_PORT_INFO sip
) {
690 if (sio_unit
.flags
& UNIT_SIO_VERBOSE
)
691 fprintf(st
, "(Port=%02x/Terminal=%1i/Read=0x%02x/NotRead=0x%02x/"
692 "Write=0x%02x/Reset?=%s/Reset=0x%02x/Data?=%s)",
693 sip
.port
, sip
.terminalLine
, sip
.sio_can_read
, sip
.sio_cannot_read
,
694 sip
.sio_can_write
, sip
.hasReset
? "True" : "False", sip
.sio_reset
,
695 sip
.hasOUT
? "True" : "False");
697 fprintf(st
, "(%02x/%1i/%02x/%02x/%02x/%s/%02x/%s)",
698 sip
.port
, sip
.terminalLine
, sip
.sio_can_read
, sip
.sio_cannot_read
,
699 sip
.sio_can_write
, sip
.hasReset
? "T" : "F", sip
.sio_reset
,
700 sip
.hasOUT
? "T" : "F");
703 static uint32
equalSIP(SIO_PORT_INFO x
, SIO_PORT_INFO y
) {
704 /* isBuiltin is not relevant for equality, only for display */
705 return (x
.port
== y
.port
) && (x
.terminalLine
== y
.terminalLine
) &&
706 (x
.sio_can_read
== y
.sio_can_read
) && (x
.sio_cannot_read
== y
.sio_cannot_read
) &&
707 (x
.sio_can_write
== y
.sio_can_write
) && (x
.hasReset
== y
.hasReset
) &&
708 (x
.sio_reset
== y
.sio_reset
) && (x
.hasOUT
== y
.hasOUT
);
711 static t_stat
sio_dev_set_port(UNIT
*uptr
, int32 value
, char *cptr
, void *desc
) {
712 int32 result
, n
, position
;
713 SIO_PORT_INFO sip
= { 0 }, old
;
714 char hasReset
, hasOUT
;
715 if (cptr
== NULL
) return SCPE_ARG
;
716 result
= sscanf(cptr
, "%x%n", &sip
.port
, &n
);
717 if ((result
== 1) && (cptr
[n
] == 0)) {
718 old
= lookupPortInfo(sip
.port
, &position
);
719 if (old
.port
== -1) {
720 printf("No mapping for port 0x%02x exists - cannot remove.\n", sip
.port
);
724 port_table
[position
] = port_table
[position
+ 1];
727 while (port_table
[position
].port
!= -1);
728 sim_map_resource(sip
.port
, 1, RESOURCE_TYPE_IO
, &nulldev
, FALSE
);
729 if (sio_unit
.flags
& UNIT_SIO_VERBOSE
) {
730 printf("Removing mapping for port 0x%02x.\n\t", sip
.port
);
731 show_sio_port_info(stdout
, old
);
735 result
= sscanf(cptr
, "%x/%d/%x/%x/%x/%1c/%x/%1c%n", &sip
.port
,
736 &sip
.terminalLine
, &sip
.sio_can_read
, &sip
.sio_cannot_read
,
737 &sip
.sio_can_write
, &hasReset
, &sip
.sio_reset
, &hasOUT
, &n
);
738 if ((result
!= 8) || (result
== EOF
) || (cptr
[n
] != 0)) return SCPE_ARG
;
739 result
= toBool(hasReset
, &sip
.hasReset
);
740 if (result
!= SCPE_OK
) return result
;
741 result
= toBool(hasOUT
, &sip
.hasOUT
);
742 if (result
!= SCPE_OK
) return result
;
743 if (sip
.port
!= (sip
.port
& 0xff)) {
744 printf("Truncating port 0x%x to 0x%02x.\n", sip
.port
, sip
.port
& 0xff);
747 old
= lookupPortInfo(sip
.port
, &position
);
748 if (old
.port
== sip
.port
) {
749 if (sio_unit
.flags
& UNIT_SIO_VERBOSE
) {
750 printf("Replacing mapping for port 0x%02x.\n\t", sip
.port
);
751 show_sio_port_info(stdout
, old
);
753 show_sio_port_info(stdout
, sip
);
754 if (equalSIP(sip
, old
)) printf("[identical]");
758 port_table
[position
+ 1] = old
;
759 if (sio_unit
.flags
& UNIT_SIO_VERBOSE
) {
760 printf("Adding mapping for port 0x%02x.\n\t", sip
.port
);
761 show_sio_port_info(stdout
, sip
);
764 if (sio_unit
.flags
& UNIT_SIO_VERBOSE
) printf("\n");
765 port_table
[position
] = sip
;
766 sim_map_resource(sip
.port
, 1, RESOURCE_TYPE_IO
, (sip
.hasOUT
||
767 (sip
.sio_can_read
== 0) && (sip
.sio_cannot_read
== 0) &&
768 (sip
.sio_can_write
== 0)) ? &sio0d
: &sio0s
, FALSE
);
772 static t_stat
sio_dev_show_port(FILE *st
, UNIT
*uptr
, int32 val
, void *desc
) {
773 int32 i
, first
= TRUE
;
774 for (i
= 0; port_table
[i
].port
!= -1; i
++)
775 if (!port_table
[i
].isBuiltin
) {
776 if (first
) first
= FALSE
;
777 else fprintf(st
, " ");
778 show_sio_port_info(st
, port_table
[i
]);
780 if (first
) fprintf(st
, "no extra port");
784 static t_stat
sio_dev_set_interrupton(UNIT
*uptr
, int32 value
, char *cptr
, void *desc
) {
785 keyboardInterrupt
= FALSE
;
786 return sim_activate(&sio_unit
, sio_unit
.wait
); /* activate unit */
789 static t_stat
sio_dev_set_interruptoff(UNIT
*uptr
, int32 value
, char *cptr
, void *desc
) {
790 keyboardInterrupt
= FALSE
;
791 sim_cancel(&sio_unit
);
795 static t_stat
sio_svc(UNIT
*uptr
) {
796 if (sio0s(0, 0, 0) & KBD_HAS_CHAR
) {
797 keyboardInterrupt
= TRUE
;
799 if (sio_unit
.flags
& UNIT_SIO_INTERRUPT
)
800 sim_activate(&sio_unit
, sio_unit
.wait
); /* activate unit */
804 int32
nulldev(const int32 port
, const int32 io
, const int32 data
) {
805 if ((sio_unit
.flags
& UNIT_SIO_VERBOSE
) && (warnUnassignedPort
< warnLevelSIO
)) {
806 warnUnassignedPort
++;
808 MESSAGE_2("Attempt to input from unassigned port 0x%04x - ignored.", port
);
811 MESSAGE_3("Attempt to output 0x%02x to unassigned port 0x%04x - ignored.", data
, port
);
814 return io
== 0 ? 0xff : 0;
817 int32
sr_dev(const int32 port
, const int32 io
, const int32 data
) {
818 return io
== 0 ? SR
: 0;
821 static int32
toBCD(const int32 x
) {
822 return (x
/ 10) * 16 + (x
% 10);
825 static int32
fromBCD(const int32 x
) {
826 return 10 * ((0xf0 & x
) >> 4) + (0x0f & x
);
829 /* Z80 or 8080 programs communicate with the SIMH pseudo device via port 0xfe.
830 The following principles apply:
832 1) For commands that do not require parameters and do not return results
835 Special case is the reset command which needs to be send 128 times to make
836 sure that the internal state is properly reset.
838 2) For commands that require parameters and do not return results
846 Note: The calling program must send all parameter bytes. Otherwise
847 the pseudo device is left in an undefined state.
849 3) For commands that do not require parameters and return results
852 in a,(0feh) ; <A> contains first byte of result
853 in a,(0feh) ; <A> contains second byte of result
855 Note: The calling program must request all bytes of the result. Otherwise
856 the pseudo device is left in an undefined state.
858 4) Commands requiring parameters and returning results do not exist currently.
862 enum simhPseudoDeviceCommands
{ /* do not change order or remove commands, add only at the end */
863 printTimeCmd
, /* 0 print the current time in milliseconds */
864 startTimerCmd
, /* 1 start a new timer on the top of the timer stack */
865 stopTimerCmd
, /* 2 stop timer on top of timer stack and show time difference */
866 resetPTRCmd
, /* 3 reset the PTR device */
867 attachPTRCmd
, /* 4 attach the PTR device */
868 detachPTRCmd
, /* 5 detach the PTR device */
869 getSIMHVersionCmd
, /* 6 get the current version of the SIMH pseudo device */
870 getClockZSDOSCmd
, /* 7 get the current time in ZSDOS format */
871 setClockZSDOSCmd
, /* 8 set the current time in ZSDOS format */
872 getClockCPM3Cmd
, /* 9 get the current time in CP/M 3 format */
873 setClockCPM3Cmd
, /* 10 set the current time in CP/M 3 format */
874 getBankSelectCmd
, /* 11 get the selected bank */
875 setBankSelectCmd
, /* 12 set the selected bank */
876 getCommonCmd
, /* 13 get the base address of the common memory segment */
877 resetSIMHInterfaceCmd
, /* 14 reset the SIMH pseudo device */
878 showTimerCmd
, /* 15 show time difference to timer on top of stack */
879 attachPTPCmd
, /* 16 attach PTP to the file with name at beginning of CP/M command line*/
880 detachPTPCmd
, /* 17 detach PTP */
881 hasBankedMemoryCmd
, /* 18 determines whether machine has banked memory */
882 setZ80CPUCmd
, /* 19 set the CPU to a Z80 */
883 set8080CPUCmd
, /* 20 set the CPU to an 8080 */
884 startTimerInterruptsCmd
, /* 21 start timer interrupts */
885 stopTimerInterruptsCmd
, /* 22 stop timer interrupts */
886 setTimerDeltaCmd
, /* 23 set the timer interval in which interrupts occur */
887 setTimerInterruptAdrCmd
, /* 24 set the address to call by timer interrupts */
888 resetStopWatchCmd
, /* 25 reset the millisecond stop watch */
889 readStopWatchCmd
, /* 26 read the millisecond stop watch */
890 SIMHSleepCmd
, /* 27 let SIMH sleep for SIMHSleep microseconds */
891 getHostOSPathSeparator
, /* 28 obtain the file path separator of the OS under which SIMH runs */
892 getHostFilenames
/* 29 perform wildcard expansion and obtain list of file names */
895 #define CPM_COMMAND_LINE_LENGTH 128
896 #define TIMER_STACK_LIMIT 10 /* stack depth of timer stack */
897 static uint32 markTime
[TIMER_STACK_LIMIT
]; /* timer stack */
898 static struct tm currentTime
;
899 static int32 currentTimeValid
= FALSE
;
900 static char version
[] = "SIMH003";
902 static t_stat
simh_dev_reset(DEVICE
*dptr
) {
903 currentTimeValid
= FALSE
;
905 setClockZSDOSPos
= 0;
906 getClockZSDOSPos
= 0;
910 getStopWatchDeltaPos
= 0;
912 setTimerDeltaPos
= 0;
913 setTimerInterruptAdrPos
= 0;
917 lastCPMStatus
= SCPE_OK
;
918 timerInterrupt
= FALSE
;
919 if (simh_unit
.flags
& UNIT_SIMH_TIMERON
)
920 simh_dev_set_timeron(NULL
, 0, NULL
, NULL
);
924 static void warnNoRealTimeClock(void) {
925 if (simh_unit
.flags
& UNIT_SIMH_VERBOSE
) {
926 MESSAGE_1("Sorry - no real time clock available.");
930 static t_stat
simh_dev_set_timeron(UNIT
*uptr
, int32 value
, char *cptr
, void *desc
) {
932 timeOfNextInterrupt
= sim_os_msec() + timerDelta
;
933 return sim_activate(&simh_unit
, simh_unit
.wait
); /* activate unit */
935 warnNoRealTimeClock();
939 static t_stat
simh_dev_set_timeroff(UNIT
*uptr
, int32 value
, char *cptr
, void *desc
) {
940 timerInterrupt
= FALSE
;
941 sim_cancel(&simh_unit
);
945 static t_stat
simh_svc(UNIT
*uptr
) {
946 uint32 n
= sim_os_msec();
947 if (n
>= timeOfNextInterrupt
) {
948 timerInterrupt
= TRUE
;
949 timeOfNextInterrupt
+= timerDelta
;
950 if (n
>= timeOfNextInterrupt
) /* time of next interrupt is not in the future */
951 timeOfNextInterrupt
= n
+ timerDelta
; /* make sure it is in the future! */
953 if (simh_unit
.flags
& UNIT_SIMH_TIMERON
)
954 sim_activate(&simh_unit
, simh_unit
.wait
); /* activate unit */
958 static char cpmCommandLine
[CPM_COMMAND_LINE_LENGTH
];
959 static void createCPMCommandLine(void) {
960 int32 i
, len
= (GetBYTEWrapper(0x80) & 0x7f); /* 0x80 contains length of command line, discard first char */
961 for (i
= 0; i
< len
- 1; i
++)
962 cpmCommandLine
[i
] = (char)GetBYTEWrapper(0x82 + i
); /* the first char, typically ' ', is discarded */
963 cpmCommandLine
[i
] = 0; /* make C string */
966 /* The CP/M command line is used as the name of a file and UNIT* uptr is attached to it. */
967 static void attachCPM(UNIT
*uptr
) {
968 createCPMCommandLine();
969 if (uptr
== &ptr_unit
)
970 sim_switches
= SWMASK('R');
971 else if (uptr
== &ptp_unit
)
972 sim_switches
= SWMASK('W') | SWMASK('C'); /* 'C' option makes sure that file is properly truncated
973 if it had existed before */
974 lastCPMStatus
= attach_unit(uptr
, cpmCommandLine
);
975 if ((lastCPMStatus
!= SCPE_OK
) && (simh_unit
.flags
& UNIT_SIMH_VERBOSE
)) {
976 MESSAGE_3("Cannot open '%s' (%s).", cpmCommandLine
, scp_error_messages
[lastCPMStatus
- SCPE_BASE
]);
977 /* must keep curly braces as MESSAGE_N is a macro with two statements */
981 /* setClockZSDOSAdr points to 6 byte block in M: YY MM DD HH MM SS in BCD notation */
982 static void setClockZSDOS(void) {
984 int32 year
= fromBCD(GetBYTEWrapper(setClockZSDOSAdr
));
985 newTime
.tm_year
= year
< 50 ? year
+ 100 : year
;
986 newTime
.tm_mon
= fromBCD(GetBYTEWrapper(setClockZSDOSAdr
+ 1)) - 1;
987 newTime
.tm_mday
= fromBCD(GetBYTEWrapper(setClockZSDOSAdr
+ 2));
988 newTime
.tm_hour
= fromBCD(GetBYTEWrapper(setClockZSDOSAdr
+ 3));
989 newTime
.tm_min
= fromBCD(GetBYTEWrapper(setClockZSDOSAdr
+ 4));
990 newTime
.tm_sec
= fromBCD(GetBYTEWrapper(setClockZSDOSAdr
+ 5));
991 ClockZSDOSDelta
= mktime(&newTime
) - time(NULL
);
994 #define SECONDS_PER_MINUTE 60
995 #define SECONDS_PER_HOUR (60 * SECONDS_PER_MINUTE)
996 #define SECONDS_PER_DAY (24 * SECONDS_PER_HOUR)
997 static time_t mkCPM3Origin(void) {
1005 return mktime(&date
);
1008 /* setClockCPM3Adr points to 5 byte block in M:
1009 0 - 1 int16: days since 31 Dec 77
1013 static void setClockCPM3(void) {
1014 ClockCPM3Delta
= mkCPM3Origin() +
1015 (GetBYTEWrapper(setClockCPM3Adr
) + GetBYTEWrapper(setClockCPM3Adr
+ 1) * 256) * SECONDS_PER_DAY
+
1016 fromBCD(GetBYTEWrapper(setClockCPM3Adr
+ 2)) * SECONDS_PER_HOUR
+
1017 fromBCD(GetBYTEWrapper(setClockCPM3Adr
+ 3)) * SECONDS_PER_MINUTE
+
1018 fromBCD(GetBYTEWrapper(setClockCPM3Adr
+ 4)) - time(NULL
);
1021 static int32
simh_in(const int32 port
) {
1023 switch(lastCommand
) {
1025 case getHostFilenames
:
1028 if (globPosNameList
< globS
.gl_pathc
) {
1029 if (!(result
= globS
.gl_pathv
[globPosNameList
][globPosName
++])) {
1039 #elif defined (_WIN32)
1043 else if (!(result
= FindFileData
.cFileName
[globPosName
++])) {
1045 if (!FindNextFile(hFind
, &FindFileData
)) {
1046 globFinished
= TRUE
;
1048 hFind
= INVALID_HANDLE_VALUE
;
1059 result
= lastCPMStatus
;
1063 case getClockZSDOSCmd
:
1064 if (currentTimeValid
)
1065 switch(getClockZSDOSPos
) {
1068 result
= toBCD(currentTime
.tm_year
> 99 ?
1069 currentTime
.tm_year
- 100 : currentTime
.tm_year
);
1070 getClockZSDOSPos
= 1;
1074 result
= toBCD(currentTime
.tm_mon
+ 1);
1075 getClockZSDOSPos
= 2;
1079 result
= toBCD(currentTime
.tm_mday
);
1080 getClockZSDOSPos
= 3;
1084 result
= toBCD(currentTime
.tm_hour
);
1085 getClockZSDOSPos
= 4;
1089 result
= toBCD(currentTime
.tm_min
);
1090 getClockZSDOSPos
= 5;
1094 result
= toBCD(currentTime
.tm_sec
);
1095 getClockZSDOSPos
= lastCommand
= 0;
1099 result
= getClockZSDOSPos
= lastCommand
= 0;
1102 case getClockCPM3Cmd
:
1103 if (currentTimeValid
)
1104 switch(getClockCPM3Pos
) {
1106 result
= daysCPM3SinceOrg
& 0xff;
1107 getClockCPM3Pos
= 1;
1111 result
= (daysCPM3SinceOrg
>> 8) & 0xff;
1112 getClockCPM3Pos
= 2;
1116 result
= toBCD(currentTime
.tm_hour
);
1117 getClockCPM3Pos
= 3;
1121 result
= toBCD(currentTime
.tm_min
);
1122 getClockCPM3Pos
= 4;
1126 result
= toBCD(currentTime
.tm_sec
);
1127 getClockCPM3Pos
= lastCommand
= 0;
1131 result
= getClockCPM3Pos
= lastCommand
= 0;
1134 case getSIMHVersionCmd
:
1135 result
= version
[versionPos
++];
1137 versionPos
= lastCommand
= 0;
1140 case getBankSelectCmd
:
1141 if (cpu_unit
.flags
& UNIT_CPU_BANKED
)
1142 result
= getBankSelect();
1145 if (simh_unit
.flags
& UNIT_SIMH_VERBOSE
) {
1146 MESSAGE_1("Get selected bank ignored for non-banked memory.");
1153 if (getCommonPos
== 0) {
1154 result
= getCommon() & 0xff;
1158 result
= (getCommon() >> 8) & 0xff;
1159 getCommonPos
= lastCommand
= 0;
1163 case hasBankedMemoryCmd
:
1164 result
= cpu_unit
.flags
& UNIT_CPU_BANKED
? MAXBANKS
: 0;
1168 case readStopWatchCmd
:
1169 if (getStopWatchDeltaPos
== 0) {
1170 result
= stopWatchDelta
& 0xff;
1171 getStopWatchDeltaPos
= 1;
1174 result
= (stopWatchDelta
>> 8) & 0xff;
1175 getStopWatchDeltaPos
= lastCommand
= 0;
1179 case getHostOSPathSeparator
:
1180 #if defined (__MWERKS__) && defined (macintosh)
1181 result
= ':'; /* colon on Macintosh OS 9 */
1182 #elif defined (_WIN32)
1183 result
= '\\'; /* back slash in Windows */
1185 result
= '/'; /* slash in UNIX */
1190 if (simh_unit
.flags
& UNIT_SIMH_VERBOSE
) {
1191 MESSAGE_2("Undefined IN from SIMH pseudo device on port %03xh ignored.",
1194 result
= lastCommand
= 0;
1199 void do_SIMH_sleep(void) {
1200 #if defined (_WIN32)
1201 if ((SIMHSleep
/ 1000) && !sio_unit
.u4
) /* time to sleep and SIO not attached to a file */
1202 Sleep(SIMHSleep
/ 1000);
1204 if (SIMHSleep
&& !sio_unit
.u4
) /* time to sleep and SIO not attached to a file */
1209 static int32
simh_out(const int32 port
, const int32 data
) {
1211 switch(lastCommand
) {
1213 case setClockZSDOSCmd
:
1214 if (setClockZSDOSPos
== 0) {
1215 setClockZSDOSAdr
= data
;
1216 setClockZSDOSPos
= 1;
1219 setClockZSDOSAdr
|= (data
<< 8);
1221 setClockZSDOSPos
= lastCommand
= 0;
1225 case setClockCPM3Cmd
:
1226 if (setClockCPM3Pos
== 0) {
1227 setClockCPM3Adr
= data
;
1228 setClockCPM3Pos
= 1;
1231 setClockCPM3Adr
|= (data
<< 8);
1233 setClockCPM3Pos
= lastCommand
= 0;
1237 case setBankSelectCmd
:
1238 if (cpu_unit
.flags
& UNIT_CPU_BANKED
)
1239 setBankSelect(data
& BANKMASK
);
1240 else if (simh_unit
.flags
& UNIT_SIMH_VERBOSE
) {
1241 MESSAGE_2("Set selected bank to %i ignored for non-banked memory.", data
& 3);
1246 case setTimerDeltaCmd
:
1247 if (setTimerDeltaPos
== 0) {
1249 setTimerDeltaPos
= 1;
1252 timerDelta
|= (data
<< 8);
1253 setTimerDeltaPos
= lastCommand
= 0;
1257 case setTimerInterruptAdrCmd
:
1258 if (setTimerInterruptAdrPos
== 0) {
1259 timerInterruptHandler
= data
;
1260 setTimerInterruptAdrPos
= 1;
1263 timerInterruptHandler
|= (data
<< 8);
1264 setTimerInterruptAdrPos
= lastCommand
= 0;
1272 case getHostFilenames
:
1276 globPosNameList
= globPosName
= 0;
1277 createCPMCommandLine();
1278 globError
= glob(cpmCommandLine
, GLOB_ERR
, NULL
, &globS
);
1280 if (simh_unit
.flags
& UNIT_SIMH_VERBOSE
) {
1281 MESSAGE_3("Cannot expand '%s'. Error is %i.", cpmCommandLine
, globError
);
1287 #elif defined (_WIN32)
1291 globFinished
= FALSE
;
1292 createCPMCommandLine();
1293 hFind
= FindFirstFile(cpmCommandLine
, &FindFileData
);
1294 if (hFind
== INVALID_HANDLE_VALUE
) {
1295 if (simh_unit
.flags
& UNIT_SIMH_VERBOSE
) {
1296 MESSAGE_3("Cannot expand '%s'. Error is %lu.", cpmCommandLine
, GetLastError());
1308 case printTimeCmd
: /* print time */
1310 MESSAGE_2("Current time in milliseconds = %d.", sim_os_msec());
1313 warnNoRealTimeClock();
1317 case startTimerCmd
: /* create a new timer on top of stack */
1319 if (markTimeSP
< TIMER_STACK_LIMIT
)
1320 markTime
[markTimeSP
++] = sim_os_msec();
1322 MESSAGE_1("Timer stack overflow.");
1324 else warnNoRealTimeClock();
1327 case stopTimerCmd
: /* stop timer on top of stack and show time difference */
1329 if (markTimeSP
> 0) {
1330 uint32 delta
= sim_os_msec() - markTime
[--markTimeSP
];
1331 MESSAGE_2("Timer stopped. Elapsed time in milliseconds = %d.", delta
);
1334 MESSAGE_1("No timer active.");
1336 else warnNoRealTimeClock();
1339 case resetPTRCmd
: /* reset ptr device */
1343 case attachPTRCmd
: /* attach ptr to the file with name at beginning of CP/M command line */
1344 attachCPM(&ptr_unit
);
1347 case detachPTRCmd
: /* detach ptr */
1348 detach_unit(&ptr_unit
);
1351 case getSIMHVersionCmd
:
1355 case getClockZSDOSCmd
:
1357 now
+= ClockZSDOSDelta
;
1358 currentTime
= *localtime(&now
);
1359 currentTimeValid
= TRUE
;
1360 getClockZSDOSPos
= 0;
1363 case setClockZSDOSCmd
:
1364 setClockZSDOSPos
= 0;
1367 case getClockCPM3Cmd
:
1369 now
+= ClockCPM3Delta
;
1370 currentTime
= *localtime(&now
);
1371 currentTimeValid
= TRUE
;
1372 daysCPM3SinceOrg
= (int32
) ((now
- mkCPM3Origin()) / SECONDS_PER_DAY
);
1373 getClockCPM3Pos
= 0;
1376 case setClockCPM3Cmd
:
1377 setClockCPM3Pos
= 0;
1380 case getBankSelectCmd
:
1381 case setBankSelectCmd
:
1383 case hasBankedMemoryCmd
:
1384 case getHostOSPathSeparator
:
1387 case resetSIMHInterfaceCmd
:
1395 #elif defined (_WIN32)
1398 if (hFind
!= INVALID_HANDLE_VALUE
) {
1405 case showTimerCmd
: /* show time difference to timer on top of stack */
1407 if (markTimeSP
> 0) {
1408 uint32 delta
= sim_os_msec() - markTime
[markTimeSP
- 1];
1409 MESSAGE_2("Timer running. Elapsed in milliseconds = %d.", delta
);
1412 MESSAGE_1("No timer active.");
1414 else warnNoRealTimeClock();
1417 case attachPTPCmd
: /* attach ptp to the file with name at beginning of CP/M command line */
1418 attachCPM(&ptp_unit
);
1421 case detachPTPCmd
: /* detach ptp */
1422 detach_unit(&ptp_unit
);
1426 chiptype
= CHIP_TYPE_Z80
;
1430 chiptype
= CHIP_TYPE_8080
;
1433 case startTimerInterruptsCmd
:
1434 if (simh_dev_set_timeron(NULL
, 0, NULL
, NULL
) == SCPE_OK
) {
1435 timerInterrupt
= FALSE
;
1436 simh_unit
.flags
|= UNIT_SIMH_TIMERON
;
1440 case stopTimerInterruptsCmd
:
1441 simh_unit
.flags
&= ~UNIT_SIMH_TIMERON
;
1442 simh_dev_set_timeroff(NULL
, 0, NULL
, NULL
);
1445 case setTimerDeltaCmd
:
1446 setTimerDeltaPos
= 0;
1449 case setTimerInterruptAdrCmd
:
1450 setTimerInterruptAdrPos
= 0;
1453 case resetStopWatchCmd
:
1454 stopWatchNow
= rtc_avail
? sim_os_msec() : 0;
1457 case readStopWatchCmd
:
1458 getStopWatchDeltaPos
= 0;
1459 stopWatchDelta
= rtc_avail
? sim_os_msec() - stopWatchNow
: 0;
1463 if (simh_unit
.flags
& UNIT_SIMH_VERBOSE
) {
1464 MESSAGE_3("Unknown command (%i) to SIMH pseudo device on port %03xh ignored.",
1469 return 0x00; /* ignored, since OUT */
1472 /* port 0xfe is a device for communication SIMH <--> Altair machine */
1473 int32
simh_dev(const int32 port
, const int32 io
, const int32 data
) {
1474 return io
== 0 ? simh_in(port
) : simh_out(port
, data
);