First Commit of my working state
[simh.git] / AltairZ80 / altairz80_sio.c
CommitLineData
196ba1fc
PH
1/* altairz80_sio.c: MITS Altair serial I/O card
2
3 Copyright (c) 2002-2008, Peter Schorn
4
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:
11
12 The above copyright notice and this permission notice shall be included in
13 all copies or substantial portions of the Software.
14
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.
21
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.
25
26 Based on work by Charles E Owen (c) 1997
27
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.
33
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:
38
39 +---+---+---+---+---+---+---+---+
40 | X | X | X | X | X | X | O | I |
41 +---+---+---+---+---+---+---+---+
42
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.
47
48 A read to the data port gets the buffered character, a write
49 to the data port writes the character to the device.
50*/
51
52#include <ctype.h>
53
54#include "altairz80_defs.h"
55#include "sim_sock.h"
56#include "sim_tmxr.h"
57#include <time.h>
58#include <assert.h>
59#if UNIX_PLATFORM
60#include <glob.h>
61#elif defined (_WIN32)
62#include <windows.h>
63#endif
64
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)
81
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)
86
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 */
95
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 */
101
102#define PORT_TABLE_SIZE 256 /* size of port mapping table */
103#define SLEEP_ALLOWED_START_DEFAULT 100 /* default initial value for sleepAllowedCounter*/
104
105static t_stat sio_set_verbose (UNIT *uptr, int32 value, char *cptr, void *desc);
106static t_stat simh_dev_set_timeron (UNIT *uptr, int32 value, char *cptr, void *desc);
107static t_stat simh_dev_set_timeroff (UNIT *uptr, int32 value, char *cptr, void *desc);
108static t_stat sio_reset(DEVICE *dptr);
109static t_stat sio_attach(UNIT *uptr, char *cptr);
110static t_stat sio_detach(UNIT *uptr);
111static t_stat ptr_reset(DEVICE *dptr);
112static t_stat ptp_reset(DEVICE *dptr);
113static t_stat toBool(char tf, int *result);
114static t_stat sio_dev_set_port(UNIT *uptr, int32 value, char *cptr, void *desc);
115static t_stat sio_dev_show_port(FILE *st, UNIT *uptr, int32 val, void *desc);
116static t_stat sio_dev_set_interrupton(UNIT *uptr, int32 value, char *cptr, void *desc);
117static t_stat sio_dev_set_interruptoff(UNIT *uptr, int32 value, char *cptr, void *desc);
118static t_stat sio_svc(UNIT *uptr);
119static t_stat simh_dev_reset(DEVICE *dptr);
120static t_stat simh_svc(UNIT *uptr);
121int32 nulldev (const int32 port, const int32 io, const int32 data);
122int32 sr_dev (const int32 port, const int32 io, const int32 data);
123int32 simh_dev (const int32 port, const int32 io, const int32 data);
124int32 sio0d (const int32 port, const int32 io, const int32 data);
125int32 sio0s (const int32 port, const int32 io, const int32 data);
126int32 sio1d (const int32 port, const int32 io, const int32 data);
127int32 sio1s (const int32 port, const int32 io, const int32 data);
128void printMessage(void);
129void do_SIMH_sleep(void);
130static void pollConnection(void);
131static int32 mapCharacter(int32 ch);
132static void checkSleep(void);
133static void voidSleep(void);
134
135extern int32 getBankSelect(void);
136extern void setBankSelect(const int32 b);
137extern uint32 getCommon(void);
138extern uint8 GetBYTEWrapper(const uint32 Addr);
139extern uint32 sim_map_resource(uint32 baseaddr, uint32 size, uint32 resource_type,
140 int32 (*routine)(const int32, const int32, const int32), uint8 unmap);
141
142extern int32 chiptype;
143extern const t_bool rtc_avail;
144extern FILE *sim_log;
145extern uint32 PCX;
146extern int32 sim_switches;
147extern const char *scp_error_messages[];
148extern int32 SR;
149extern UNIT cpu_unit;
150extern volatile int32 stop_cpu;
151extern int32 sim_interval;
152
153/* SIMH pseudo device status registers */
154/* ZSDOS clock definitions */
155static time_t ClockZSDOSDelta = 0; /* delta between real clock and Altair clock */
156static int32 setClockZSDOSPos = 0; /* determines state for receiving address of parameter block */
157static int32 setClockZSDOSAdr = 0; /* address in M of 6 byte parameter block for setting time */
158static int32 getClockZSDOSPos = 0; /* determines state for sending clock information */
159
160/* CPM3 clock definitions */
161static time_t ClockCPM3Delta = 0; /* delta between real clock and Altair clock */
162static int32 setClockCPM3Pos = 0; /* determines state for receiving address of parameter block */
163static int32 setClockCPM3Adr = 0; /* address in M of 5 byte parameter block for setting time */
164static int32 getClockCPM3Pos = 0; /* determines state for sending clock information */
165static int32 daysCPM3SinceOrg = 0; /* days since 1 Jan 1978 */
166
167/* interrupt related */
168static 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 */
171static int32 setTimerInterruptAdrPos= 0; /* determines state for receiving timerInterruptHandler */
172static int32 timerDelta = 100; /* interrupt every 100 ms */
173static int32 setTimerDeltaPos = 0; /* determines state for receiving timerDelta */
174
175/* stop watch and timer related */
176static uint32 stopWatchDelta = 0; /* stores elapsed time of stop watch */
177static int32 getStopWatchDeltaPos = 0; /* determines the state for receiving stopWatchDelta */
178static uint32 stopWatchNow = 0; /* stores starting time of stop watch */
179static int32 markTimeSP = 0; /* stack pointer for timer stack */
180
181 /* default time in microseconds to sleep for SIMHSleepCmd */
182#if defined (_WIN32)
183static uint32 SIMHSleep = 1000; /* Sleep uses milliseconds */
184#elif defined (__MWERKS__) && defined (macintosh)
185static uint32 SIMHSleep = 0; /* no sleep on Macintosh OS9 */
186#else
187static uint32 SIMHSleep = 100; /* on other platforms 100 micro seconds is good enough */
188#endif
189static uint32 sleepAllowedCounter = 0; /* only sleep on no character available when == 0 */
190static uint32 sleepAllowedStart = SLEEP_ALLOWED_START_DEFAULT; /* default start for above counter */
191
192/* miscellaneous */
193static int32 versionPos = 0; /* determines state for sending device identifier */
194static int32 lastCPMStatus = 0; /* result of last attachCPM command */
195static int32 lastCommand = 0; /* most recent command processed on port 0xfeh */
196static int32 getCommonPos = 0; /* determines state for sending the 'common' register */
197
198/* support for wild card expansion */
199#if UNIX_PLATFORM
200static glob_t globS;
201static uint32 globPosNameList = 0;
202static int32 globPosName = 0;
203static int32 globValid = FALSE;
204static int32 globError = 0;
205#elif defined (_WIN32)
206static WIN32_FIND_DATA FindFileData;
207static HANDLE hFind = INVALID_HANDLE_VALUE;
208static int32 globFinished = FALSE;
209static int32 globValid = FALSE;
210static int32 globPosName = 0;
211#endif
212
213/* SIO status registers */
214static int32 warnLevelSIO = 3; /* display at most 'warnLevelSIO' times the same warning */
215static int32 warnUnattachedPTP = 0; /* display a warning message if < warnLevel and SIO set to
216 VERBOSE and output to PTP without an attached file */
217static 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 */
219static int32 warnPTREOF = 0; /* display a warning message if < warnLevel and SIO set to
220 VERBOSE and attempt to read from PTR past EOF */
221static 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 */
223
224 int32 keyboardInterrupt = FALSE; /* keyboard interrupt pending */
225 uint32 keyboardInterruptHandler = 0x0038;/* address of keyboard interrupt handler */
226
227static TMLN TerminalLines[TERMINALS] = { /* four terminals */
228 { 0 }
229};
230
231static TMXR altairTMXR = { /* mux descriptor */
232 TERMINALS, 0, 0, TerminalLines
233};
234
235static UNIT sio_unit = {
236 UDATA (&sio_svc, UNIT_ATTABLE | UNIT_SIO_MAP | UNIT_SIO_SLEEP, 0),
237 100000, /* wait */
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 */
242};
243
244static 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) },
256 { NULL }
257};
258
259static 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 },
280 { 0 }
281};
282
283DEVICE sio_dev = {
284 "SIO", &sio_unit, sio_reg, sio_mod,
285 1, 10, 31, 1, 8, 8,
286 NULL, NULL, &sio_reset,
287 NULL, &sio_attach, &sio_detach,
288 NULL, 0, 0,
289 NULL, NULL, NULL };
290
291static UNIT ptr_unit = {
292 UDATA (NULL, UNIT_SEQ | UNIT_ATTABLE | UNIT_ROABLE, 0)
293};
294
295static REG ptr_reg[] = {
296 { HRDATA (STAT, ptr_unit.u3, 8) },
297 { NULL }
298};
299
300DEVICE ptr_dev = {
301 "PTR", &ptr_unit, ptr_reg, NULL,
302 1, 10, 31, 1, 8, 8,
303 NULL, NULL, &ptr_reset,
304 NULL, NULL, NULL,
305 NULL, 0, 0,
306 NULL, NULL, NULL
307};
308
309static UNIT ptp_unit = {
310 UDATA (NULL, UNIT_SEQ + UNIT_ATTABLE, 0)
311};
312
313DEVICE ptp_dev = {
314 "PTP", &ptp_unit, NULL, NULL,
315 1, 10, 31, 1, 8, 8,
316 NULL, NULL, &ptp_reset,
317 NULL, NULL, NULL,
318 NULL, 0, 0,
319 NULL, NULL, NULL
320};
321
322/* Synthetic device SIMH for communication
323 between Altair and SIMH environment using port 0xfe */
324static UNIT simh_unit = {
325 UDATA (&simh_svc, 0, 0), KBD_POLL_WAIT
326};
327
328static 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 },
333
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 },
339
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) },
348
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 },
353
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 },
358 { NULL }
359};
360
361static 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 },
370 { 0 }
371};
372
373DEVICE simh_device = {
374 "SIMH", &simh_unit, simh_reg, simh_mod,
375 1, 10, 31, 1, 16, 4,
376 NULL, NULL, &simh_dev_reset,
377 NULL, NULL, NULL,
378 NULL, 0, 0,
379 NULL, NULL, NULL
380};
381
382char messageBuffer[256] = { 0 };
383
384void printMessage(void) {
385 printf(messageBuffer);
386 printf(NLP);
387 if (sim_log) {
388 fprintf(sim_log, messageBuffer);
389 fprintf(sim_log,"\n");
390 }
391}
392
393static void resetSIOWarningFlags(void) {
394 warnUnattachedPTP = warnUnattachedPTR = warnPTREOF = warnUnassignedPort = 0;
395}
396
397static t_stat sio_set_verbose(UNIT *uptr, int32 value, char *cptr, void *desc) {
398 resetSIOWarningFlags();
399 return SCPE_OK;
400}
401
402static 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 */
409 }
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);
413}
414
415static 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);
420 }
421 return tmxr_detach(&altairTMXR, uptr);
422}
423
424static void pollConnection(void) {
425 if (sio_unit.flags & UNIT_ATT) {
426 int32 temp = tmxr_poll_conn(&altairTMXR); /* poll connection */
427 if (temp >= 0)
428 TerminalLines[temp].rcve = 1; /* enable receive */
429 tmxr_poll_rx(&altairTMXR); /* poll input */
430 tmxr_poll_tx(&altairTMXR); /* poll output */
431 }
432}
433
434/* reset routines */
435static t_stat sio_reset(DEVICE *dptr) {
436 int32 i;
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 */
442 }
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]);
447 return SCPE_OK;
448}
449
450static 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);
455 return SCPE_OK;
456}
457
458static t_stat ptp_reset(DEVICE *dptr) {
459 resetSIOWarningFlags();
460 return SCPE_OK;
461}
462
463static int32 mapCharacter(int32 ch) {
464 ch &= 0xff;
465 if (sio_unit.flags & UNIT_SIO_MAP) {
466 if (sio_unit.flags & UNIT_SIO_BS) {
467 if (ch == BACKSPACE_CHAR)
468 return DELETE_CHAR;
469 }
470 else if (ch == DELETE_CHAR)
471 return BACKSPACE_CHAR;
472 if (sio_unit.flags & UNIT_SIO_UPPER)
473 return toupper(ch);
474 }
475 return ch;
476}
477
478/* I/O instruction handlers, called from the CPU module when an
479 IN or OUT instruction is issued.
480
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
484 to the device.
485
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 )
490*/
491
492typedef struct {
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 */
502} SIO_PORT_INFO;
503
504static 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 */
518};
519
520static SIO_PORT_INFO lookupPortInfo(const int32 port, int32 *position) {
521 int32 i = 0;
522 while ((port_table[i].port != -1) && (port_table[i].port != port)) i++;
523 *position = i;
524 return port_table[i];
525}
526
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) */
529static void checkSleep(void) {
530 if (sio_unit.flags & UNIT_SIO_SLEEP) {
531 if (sleepAllowedCounter) sleepAllowedCounter--;
532 else do_SIMH_sleep();
533 }
534}
535
536/* void sleep for next 'sleepAllowedStart' tests */
537static void voidSleep(void) {
538 sleepAllowedCounter = sleepAllowedStart;
539}
540
541/* generic status port for keyboard input / terminal output */
542int32 sio0s(const int32 port, const int32 io, const int32 data) {
543 int32 ch, result;
544 SIO_PORT_INFO spi = lookupPortInfo(port, &ch);
545 assert(spi.port == port);
546 pollConnection();
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;
555 else {
556 result = spi.sio_cannot_read;
557 checkSleep();
558 }
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
562 enabled */
563 }
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 */
569 stop_cpu = TRUE;
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 */
572 }
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;
576 }
577 checkSleep();
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 */
583}
584
585/* generic data port for keyboard input / terminal output */
586int32 sio0d(const int32 port, const int32 io, const int32 data) {
587 int32 ch;
588 SIO_PORT_INFO spi = lookupPortInfo(port, &ch);
589 assert(spi.port == port);
590 pollConnection();
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 */
596 }
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) */
600 }
601 return mapCharacter(ch); /* return mapped character */
602 }
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 */
608 if (spi.hasOUT) {
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)) {
611 voidSleep();
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 */
614 else
615 sim_putchar(ch);
616 }
617 }
618 return 0x00; /* ignored since OUT */
619}
620
621/* PTR/PTP status port */
622int32 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)) {
629 warnUnattachedPTR++;
630/*06*/ MESSAGE_2("Attempt to test status of unattached PTR[0x%02x]. 0x02 returned.", port);
631 }
632 return SIO_CAN_WRITE;
633 }
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);
637 } /* OUT follows */
638 if (data == SIO_RESET)
639 ptr_unit.u3 = FALSE; /* reset EOF indicator */
640 return 0x00; /* ignored since OUT */
641}
642
643/* PTR/PTP data port */
644int32 sio1d(const int32 port, const int32 io, const int32 data) {
645 int32 ch;
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)) {
649 warnPTREOF++;
650/*07*/ MESSAGE_2("PTR[0x%02x] attempted to read past EOF. 0x00 returned.", port);
651 }
652 return 0x00;
653 }
654 if ((ptr_unit.flags & UNIT_ATT) == 0) { /* not attached */
655 if ((sio_unit.flags & UNIT_SIO_VERBOSE) && (warnUnattachedPTR < warnLevelSIO)) {
656 warnUnattachedPTR++;
657/*08*/ MESSAGE_2("Attempt to read from unattached PTR[0x%02x]. 0x00 returned.", port);
658 }
659 return 0x00;
660 }
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 */
664 }
665 return ch & 0xff;
666 } /* OUT follows */
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)) {
671 warnUnattachedPTP++;
672/*09*/ MESSAGE_3("Attempt to output '0x%02x' to unattached PTP[0x%02x] - ignored.", data, port);
673 }
674 return 0x00; /* ignored since OUT */
675}
676
677static t_stat toBool(char tf, int *result) {
678 if (tf == 'T') {
679 *result = TRUE;
680 return SCPE_OK;
681 }
682 if (tf == 'F') {
683 *result = FALSE;
684 return SCPE_OK;
685 }
686 return SCPE_ARG;
687}
688
689static 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");
696 else
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");
701}
702
703static 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);
709}
710
711static 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);
721 return SCPE_ARG;
722 }
723 do {
724 port_table[position] = port_table[position + 1];
725 position++;
726 }
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);
732 }
733 return SCPE_OK;
734 }
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);
745 sip.port &= 0xff;
746 }
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);
752 printf("-> ");
753 show_sio_port_info(stdout, sip);
754 if (equalSIP(sip, old)) printf("[identical]");
755 }
756 }
757 else {
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);
762 }
763 }
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);
769 return SCPE_OK;
770}
771
772static 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]);
779 }
780 if (first) fprintf(st, "no extra port");
781 return SCPE_OK;
782}
783
784static 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 */
787}
788
789static t_stat sio_dev_set_interruptoff(UNIT *uptr, int32 value, char *cptr, void *desc) {
790 keyboardInterrupt = FALSE;
791 sim_cancel(&sio_unit);
792 return SCPE_OK;
793}
794
795static t_stat sio_svc(UNIT *uptr) {
796 if (sio0s(0, 0, 0) & KBD_HAS_CHAR) {
797 keyboardInterrupt = TRUE;
798 }
799 if (sio_unit.flags & UNIT_SIO_INTERRUPT)
800 sim_activate(&sio_unit, sio_unit.wait); /* activate unit */
801 return SCPE_OK;
802}
803
804int32 nulldev(const int32 port, const int32 io, const int32 data) {
805 if ((sio_unit.flags & UNIT_SIO_VERBOSE) && (warnUnassignedPort < warnLevelSIO)) {
806 warnUnassignedPort++;
807 if (io == 0) {
808 MESSAGE_2("Attempt to input from unassigned port 0x%04x - ignored.", port);
809 }
810 else {
811 MESSAGE_3("Attempt to output 0x%02x to unassigned port 0x%04x - ignored.", data, port);
812 }
813 }
814 return io == 0 ? 0xff : 0;
815}
816
817int32 sr_dev(const int32 port, const int32 io, const int32 data) {
818 return io == 0 ? SR : 0;
819}
820
821static int32 toBCD(const int32 x) {
822 return (x / 10) * 16 + (x % 10);
823}
824
825static int32 fromBCD(const int32 x) {
826 return 10 * ((0xf0 & x) >> 4) + (0x0f & x);
827}
828
829/* Z80 or 8080 programs communicate with the SIMH pseudo device via port 0xfe.
830 The following principles apply:
831
832 1) For commands that do not require parameters and do not return results
833 ld a,<cmd>
834 out (0feh),a
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.
837
838 2) For commands that require parameters and do not return results
839 ld a,<cmd>
840 out (0feh),a
841 ld a,<p1>
842 out (0feh),a
843 ld a,<p2>
844 out (0feh),a
845 ...
846 Note: The calling program must send all parameter bytes. Otherwise
847 the pseudo device is left in an undefined state.
848
849 3) For commands that do not require parameters and return results
850 ld a,<cmd>
851 out (0feh),a
852 in a,(0feh) ; <A> contains first byte of result
853 in a,(0feh) ; <A> contains second byte of result
854 ...
855 Note: The calling program must request all bytes of the result. Otherwise
856 the pseudo device is left in an undefined state.
857
858 4) Commands requiring parameters and returning results do not exist currently.
859
860*/
861
862enum 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 */
893};
894
895#define CPM_COMMAND_LINE_LENGTH 128
896#define TIMER_STACK_LIMIT 10 /* stack depth of timer stack */
897static uint32 markTime[TIMER_STACK_LIMIT]; /* timer stack */
898static struct tm currentTime;
899static int32 currentTimeValid = FALSE;
900static char version[] = "SIMH003";
901
902static t_stat simh_dev_reset(DEVICE *dptr) {
903 currentTimeValid = FALSE;
904 ClockZSDOSDelta = 0;
905 setClockZSDOSPos = 0;
906 getClockZSDOSPos = 0;
907 ClockCPM3Delta = 0;
908 setClockCPM3Pos = 0;
909 getClockCPM3Pos = 0;
910 getStopWatchDeltaPos = 0;
911 getCommonPos = 0;
912 setTimerDeltaPos = 0;
913 setTimerInterruptAdrPos = 0;
914 markTimeSP = 0;
915 versionPos = 0;
916 lastCommand = 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);
921 return SCPE_OK;
922}
923
924static void warnNoRealTimeClock(void) {
925 if (simh_unit.flags & UNIT_SIMH_VERBOSE) {
926 MESSAGE_1("Sorry - no real time clock available.");
927 }
928}
929
930static t_stat simh_dev_set_timeron(UNIT *uptr, int32 value, char *cptr, void *desc) {
931 if (rtc_avail) {
932 timeOfNextInterrupt = sim_os_msec() + timerDelta;
933 return sim_activate(&simh_unit, simh_unit.wait); /* activate unit */
934 }
935 warnNoRealTimeClock();
936 return SCPE_ARG;
937}
938
939static t_stat simh_dev_set_timeroff(UNIT *uptr, int32 value, char *cptr, void *desc) {
940 timerInterrupt = FALSE;
941 sim_cancel(&simh_unit);
942 return SCPE_OK;
943}
944
945static 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! */
952 }
953 if (simh_unit.flags & UNIT_SIMH_TIMERON)
954 sim_activate(&simh_unit, simh_unit.wait); /* activate unit */
955 return SCPE_OK;
956}
957
958static char cpmCommandLine[CPM_COMMAND_LINE_LENGTH];
959static 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 */
964}
965
966/* The CP/M command line is used as the name of a file and UNIT* uptr is attached to it. */
967static 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 */
978 }
979}
980
981/* setClockZSDOSAdr points to 6 byte block in M: YY MM DD HH MM SS in BCD notation */
982static void setClockZSDOS(void) {
983 struct tm newTime;
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);
992}
993
994#define SECONDS_PER_MINUTE 60
995#define SECONDS_PER_HOUR (60 * SECONDS_PER_MINUTE)
996#define SECONDS_PER_DAY (24 * SECONDS_PER_HOUR)
997static time_t mkCPM3Origin(void) {
998 struct tm date;
999 date.tm_year = 77;
1000 date.tm_mon = 11;
1001 date.tm_mday = 31;
1002 date.tm_hour = 0;
1003 date.tm_min = 0;
1004 date.tm_sec = 0;
1005 return mktime(&date);
1006}
1007
1008/* setClockCPM3Adr points to 5 byte block in M:
1009 0 - 1 int16: days since 31 Dec 77
1010 2 BCD byte: HH
1011 3 BCD byte: MM
1012 4 BCD byte: SS */
1013static 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);
1019}
1020
1021static int32 simh_in(const int32 port) {
1022 int32 result = 0;
1023 switch(lastCommand) {
1024
1025 case getHostFilenames:
1026#if UNIX_PLATFORM
1027 if (globValid)
1028 if (globPosNameList < globS.gl_pathc) {
1029 if (!(result = globS.gl_pathv[globPosNameList][globPosName++])) {
1030 globPosNameList++;
1031 globPosName = 0;
1032 }
1033 }
1034 else {
1035 globValid = FALSE;
1036 lastCommand = 0;
1037 globfree(&globS);
1038 }
1039#elif defined (_WIN32)
1040 if (globValid)
1041 if (globFinished)
1042 globValid = FALSE;
1043 else if (!(result = FindFileData.cFileName[globPosName++])) {
1044 globPosName = 0;
1045 if (!FindNextFile(hFind, &FindFileData)) {
1046 globFinished = TRUE;
1047 FindClose(hFind);
1048 hFind = INVALID_HANDLE_VALUE;
1049 }
1050 }
1051#else
1052 lastCommand = 0;
1053#endif
1054 break;
1055
1056 case attachPTRCmd:
1057
1058 case attachPTPCmd:
1059 result = lastCPMStatus;
1060 lastCommand = 0;
1061 break;
1062
1063 case getClockZSDOSCmd:
1064 if (currentTimeValid)
1065 switch(getClockZSDOSPos) {
1066
1067 case 0:
1068 result = toBCD(currentTime.tm_year > 99 ?
1069 currentTime.tm_year - 100 : currentTime.tm_year);
1070 getClockZSDOSPos = 1;
1071 break;
1072
1073 case 1:
1074 result = toBCD(currentTime.tm_mon + 1);
1075 getClockZSDOSPos = 2;
1076 break;
1077
1078 case 2:
1079 result = toBCD(currentTime.tm_mday);
1080 getClockZSDOSPos = 3;
1081 break;
1082
1083 case 3:
1084 result = toBCD(currentTime.tm_hour);
1085 getClockZSDOSPos = 4;
1086 break;
1087
1088 case 4:
1089 result = toBCD(currentTime.tm_min);
1090 getClockZSDOSPos = 5;
1091 break;
1092
1093 case 5:
1094 result = toBCD(currentTime.tm_sec);
1095 getClockZSDOSPos = lastCommand = 0;
1096 break;
1097 }
1098 else
1099 result = getClockZSDOSPos = lastCommand = 0;
1100 break;
1101
1102 case getClockCPM3Cmd:
1103 if (currentTimeValid)
1104 switch(getClockCPM3Pos) {
1105 case 0:
1106 result = daysCPM3SinceOrg & 0xff;
1107 getClockCPM3Pos = 1;
1108 break;
1109
1110 case 1:
1111 result = (daysCPM3SinceOrg >> 8) & 0xff;
1112 getClockCPM3Pos = 2;
1113 break;
1114
1115 case 2:
1116 result = toBCD(currentTime.tm_hour);
1117 getClockCPM3Pos = 3;
1118 break;
1119
1120 case 3:
1121 result = toBCD(currentTime.tm_min);
1122 getClockCPM3Pos = 4;
1123 break;
1124
1125 case 4:
1126 result = toBCD(currentTime.tm_sec);
1127 getClockCPM3Pos = lastCommand = 0;
1128 break;
1129 }
1130 else
1131 result = getClockCPM3Pos = lastCommand = 0;
1132 break;
1133
1134 case getSIMHVersionCmd:
1135 result = version[versionPos++];
1136 if (result == 0)
1137 versionPos = lastCommand = 0;
1138 break;
1139
1140 case getBankSelectCmd:
1141 if (cpu_unit.flags & UNIT_CPU_BANKED)
1142 result = getBankSelect();
1143 else {
1144 result = 0;
1145 if (simh_unit.flags & UNIT_SIMH_VERBOSE) {
1146 MESSAGE_1("Get selected bank ignored for non-banked memory.");
1147 }
1148 }
1149 lastCommand = 0;
1150 break;
1151
1152 case getCommonCmd:
1153 if (getCommonPos == 0) {
1154 result = getCommon() & 0xff;
1155 getCommonPos = 1;
1156 }
1157 else {
1158 result = (getCommon() >> 8) & 0xff;
1159 getCommonPos = lastCommand = 0;
1160 }
1161 break;
1162
1163 case hasBankedMemoryCmd:
1164 result = cpu_unit.flags & UNIT_CPU_BANKED ? MAXBANKS : 0;
1165 lastCommand = 0;
1166 break;
1167
1168 case readStopWatchCmd:
1169 if (getStopWatchDeltaPos == 0) {
1170 result = stopWatchDelta & 0xff;
1171 getStopWatchDeltaPos = 1;
1172 }
1173 else {
1174 result = (stopWatchDelta >> 8) & 0xff;
1175 getStopWatchDeltaPos = lastCommand = 0;
1176 }
1177 break;
1178
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 */
1184#else
1185 result = '/'; /* slash in UNIX */
1186#endif
1187 break;
1188
1189 default:
1190 if (simh_unit.flags & UNIT_SIMH_VERBOSE) {
1191 MESSAGE_2("Undefined IN from SIMH pseudo device on port %03xh ignored.",
1192 port);
1193 }
1194 result = lastCommand = 0;
1195 }
1196 return result;
1197}
1198
1199void 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);
1203#else
1204 if (SIMHSleep && !sio_unit.u4) /* time to sleep and SIO not attached to a file */
1205 usleep(SIMHSleep);
1206#endif
1207}
1208
1209static int32 simh_out(const int32 port, const int32 data) {
1210 time_t now;
1211 switch(lastCommand) {
1212
1213 case setClockZSDOSCmd:
1214 if (setClockZSDOSPos == 0) {
1215 setClockZSDOSAdr = data;
1216 setClockZSDOSPos = 1;
1217 }
1218 else {
1219 setClockZSDOSAdr |= (data << 8);
1220 setClockZSDOS();
1221 setClockZSDOSPos = lastCommand = 0;
1222 }
1223 break;
1224
1225 case setClockCPM3Cmd:
1226 if (setClockCPM3Pos == 0) {
1227 setClockCPM3Adr = data;
1228 setClockCPM3Pos = 1;
1229 }
1230 else {
1231 setClockCPM3Adr |= (data << 8);
1232 setClockCPM3();
1233 setClockCPM3Pos = lastCommand = 0;
1234 }
1235 break;
1236
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);
1242 }
1243 lastCommand = 0;
1244 break;
1245
1246 case setTimerDeltaCmd:
1247 if (setTimerDeltaPos == 0) {
1248 timerDelta = data;
1249 setTimerDeltaPos = 1;
1250 }
1251 else {
1252 timerDelta |= (data << 8);
1253 setTimerDeltaPos = lastCommand = 0;
1254 }
1255 break;
1256
1257 case setTimerInterruptAdrCmd:
1258 if (setTimerInterruptAdrPos == 0) {
1259 timerInterruptHandler = data;
1260 setTimerInterruptAdrPos = 1;
1261 }
1262 else {
1263 timerInterruptHandler |= (data << 8);
1264 setTimerInterruptAdrPos = lastCommand = 0;
1265 }
1266 break;
1267
1268 default:
1269 lastCommand = data;
1270 switch(data) {
1271
1272 case getHostFilenames:
1273#if UNIX_PLATFORM
1274 if (!globValid) {
1275 globValid = TRUE;
1276 globPosNameList = globPosName = 0;
1277 createCPMCommandLine();
1278 globError = glob(cpmCommandLine, GLOB_ERR, NULL, &globS);
1279 if (globError) {
1280 if (simh_unit.flags & UNIT_SIMH_VERBOSE) {
1281 MESSAGE_3("Cannot expand '%s'. Error is %i.", cpmCommandLine, globError);
1282 }
1283 globfree(&globS);
1284 globValid = FALSE;
1285 }
1286 }
1287#elif defined (_WIN32)
1288 if (!globValid) {
1289 globValid = TRUE;
1290 globPosName = 0;
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());
1297 }
1298 globValid = FALSE;
1299 }
1300 }
1301#endif
1302 break;
1303
1304 case SIMHSleepCmd:
1305 do_SIMH_sleep();
1306 break;
1307
1308 case printTimeCmd: /* print time */
1309 if (rtc_avail) {
1310 MESSAGE_2("Current time in milliseconds = %d.", sim_os_msec());
1311 }
1312 else {
1313 warnNoRealTimeClock();
1314 }
1315 break;
1316
1317 case startTimerCmd: /* create a new timer on top of stack */
1318 if (rtc_avail)
1319 if (markTimeSP < TIMER_STACK_LIMIT)
1320 markTime[markTimeSP++] = sim_os_msec();
1321 else {
1322 MESSAGE_1("Timer stack overflow.");
1323 }
1324 else warnNoRealTimeClock();
1325 break;
1326
1327 case stopTimerCmd: /* stop timer on top of stack and show time difference */
1328 if (rtc_avail)
1329 if (markTimeSP > 0) {
1330 uint32 delta = sim_os_msec() - markTime[--markTimeSP];
1331 MESSAGE_2("Timer stopped. Elapsed time in milliseconds = %d.", delta);
1332 }
1333 else {
1334 MESSAGE_1("No timer active.");
1335 }
1336 else warnNoRealTimeClock();
1337 break;
1338
1339 case resetPTRCmd: /* reset ptr device */
1340 ptr_reset(NULL);
1341 break;
1342
1343 case attachPTRCmd: /* attach ptr to the file with name at beginning of CP/M command line */
1344 attachCPM(&ptr_unit);
1345 break;
1346
1347 case detachPTRCmd: /* detach ptr */
1348 detach_unit(&ptr_unit);
1349 break;
1350
1351 case getSIMHVersionCmd:
1352 versionPos = 0;
1353 break;
1354
1355 case getClockZSDOSCmd:
1356 time(&now);
1357 now += ClockZSDOSDelta;
1358 currentTime = *localtime(&now);
1359 currentTimeValid = TRUE;
1360 getClockZSDOSPos = 0;
1361 break;
1362
1363 case setClockZSDOSCmd:
1364 setClockZSDOSPos = 0;
1365 break;
1366
1367 case getClockCPM3Cmd:
1368 time(&now);
1369 now += ClockCPM3Delta;
1370 currentTime = *localtime(&now);
1371 currentTimeValid = TRUE;
1372 daysCPM3SinceOrg = (int32) ((now - mkCPM3Origin()) / SECONDS_PER_DAY);
1373 getClockCPM3Pos = 0;
1374 break;
1375
1376 case setClockCPM3Cmd:
1377 setClockCPM3Pos = 0;
1378 break;
1379
1380 case getBankSelectCmd:
1381 case setBankSelectCmd:
1382 case getCommonCmd:
1383 case hasBankedMemoryCmd:
1384 case getHostOSPathSeparator:
1385 break;
1386
1387 case resetSIMHInterfaceCmd:
1388 markTimeSP = 0;
1389 lastCommand = 0;
1390#if UNIX_PLATFORM
1391 if (globValid) {
1392 globValid = FALSE;
1393 globfree(&globS);
1394 }
1395#elif defined (_WIN32)
1396 if (globValid) {
1397 globValid = FALSE;
1398 if (hFind != INVALID_HANDLE_VALUE) {
1399 FindClose(hFind);
1400 }
1401 }
1402#endif
1403 break;
1404
1405 case showTimerCmd: /* show time difference to timer on top of stack */
1406 if (rtc_avail)
1407 if (markTimeSP > 0) {
1408 uint32 delta = sim_os_msec() - markTime[markTimeSP - 1];
1409 MESSAGE_2("Timer running. Elapsed in milliseconds = %d.", delta);
1410 }
1411 else {
1412 MESSAGE_1("No timer active.");
1413 }
1414 else warnNoRealTimeClock();
1415 break;
1416
1417 case attachPTPCmd: /* attach ptp to the file with name at beginning of CP/M command line */
1418 attachCPM(&ptp_unit);
1419 break;
1420
1421 case detachPTPCmd: /* detach ptp */
1422 detach_unit(&ptp_unit);
1423 break;
1424
1425 case setZ80CPUCmd:
1426 chiptype = CHIP_TYPE_Z80;
1427 break;
1428
1429 case set8080CPUCmd:
1430 chiptype = CHIP_TYPE_8080;
1431 break;
1432
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;
1437 }
1438 break;
1439
1440 case stopTimerInterruptsCmd:
1441 simh_unit.flags &= ~UNIT_SIMH_TIMERON;
1442 simh_dev_set_timeroff(NULL, 0, NULL, NULL);
1443 break;
1444
1445 case setTimerDeltaCmd:
1446 setTimerDeltaPos = 0;
1447 break;
1448
1449 case setTimerInterruptAdrCmd:
1450 setTimerInterruptAdrPos = 0;
1451 break;
1452
1453 case resetStopWatchCmd:
1454 stopWatchNow = rtc_avail ? sim_os_msec() : 0;
1455 break;
1456
1457 case readStopWatchCmd:
1458 getStopWatchDeltaPos = 0;
1459 stopWatchDelta = rtc_avail ? sim_os_msec() - stopWatchNow : 0;
1460 break;
1461
1462 default:
1463 if (simh_unit.flags & UNIT_SIMH_VERBOSE) {
1464 MESSAGE_3("Unknown command (%i) to SIMH pseudo device on port %03xh ignored.",
1465 data, port);
1466 }
1467 }
1468 }
1469 return 0x00; /* ignored, since OUT */
1470}
1471
1472/* port 0xfe is a device for communication SIMH <--> Altair machine */
1473int32 simh_dev(const int32 port, const int32 io, const int32 data) {
1474 return io == 0 ? simh_in(port) : simh_out(port, data);
1475}