blob: 5f72ba690238f3dbec35f6809233c840bf36390c [file] [log] [blame] [raw]
/* scp.c: simulator control program
Copyright (c) 1993-2012, Robert M Supnik
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of Robert M Supnik shall not be
used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization from Robert M Supnik.
20-Mar-12 MP Fixes to "SHOW <x> SHOW" commands
06-Jan-12 JDB Fixed "SHOW DEVICE" with only one enabled unit (Dave Bryan)
25-Sep-11 MP Added the ability for a simulator built with
SIM_ASYNCH_IO to change whether I/O is actually done
asynchronously by the new scp command SET ASYNCH and
SET NOASYNCH
22-Sep-11 MP Added signal catching of SIGHUP and SIGTERM to cause
simulator STOP. This allows an externally signalled
event (i.e. system shutdown, or logoff) to signal a
running simulator of these events and to allow
reasonable actions to be taken. This will facilitate
running a simulator as a 'service' on *nix platforms,
given a sufficiently flexible simulator .ini file.
20-Apr-11 MP Added expansion of %STATUS% and %TSTATUS% in do command
arguments. STATUS is the numeric value of the last
command error status and TSTATUS is the text message
relating to the last command error status
17-Apr-11 MP Changed sim_rest to defer attaching devices until after
device register contents have been restored since some
attach activities may reference register contained info.
29-Jan-11 MP Adjusted sim_debug to:
- include the simulator timestamp (sim_gtime)
as part of the prefix for each line of output
- write complete lines at a time (avoid asynch I/O issues).
05-Jan-11 MP Added Asynch I/O support
22-Jan-11 MP Added SET ON, SET NOON, ON, GOTO and RETURN command support
13-Jan-11 MP Added "SHOW SHOW" and "SHOW <dev> SHOW" commands
05-Jan-11 RMS Fixed bug in deposit stride for numeric input (John Dundas)
23-Dec-10 RMS Clarified some help messages (Mark Pizzolato)
08-Nov-10 RMS Fixed handling of DO with no arguments (Dave Bryan)
22-May-10 RMS Added *nix READLINE support (Mark Pizzolato)
08-Feb-09 RMS Fixed warnings in help printouts
29-Dec-08 RMS Fixed implementation of MTAB_NC
24-Nov-08 RMS Revised RESTORE unit logic for consistency
05-Sep-08 JDB "detach_all" ignores error status returns if shutting down
17-Aug-08 RMS Revert RUN/BOOT to standard, rather than powerup, reset
25-Jul-08 JDB DO cmd missing params now default to null string
29-Jun-08 JDB DO cmd sub_args now allows "\\" to specify literal backslash
04-Jun-08 JDB label the patch delta more clearly
31-Mar-08 RMS Fixed bug in local/global register search (Mark Pizzolato)
Fixed bug in restore of RO units (Mark Pizzolato)
06-Feb-08 RMS Added SET/SHO/NO BR with default argument
18-Jul-07 RMS Modified match_ext for VMS ext;version support
28-Apr-07 RMS Modified sim_instr invocation to call sim_rtcn_init_all
Fixed bug in get_sim_opt
Fixed bug in restoration with changed memory size
08-Mar-07 JDB Fixed breakpoint actions in DO command file processing
30-Jan-07 RMS Fixed bugs in get_ipaddr
17-Oct-06 RMS Added idle support
04-Oct-06 JDB DO cmd failure now echoes cmd unless -q
30-Aug-06 JDB detach_unit returns SCPE_UNATT if not attached
14-Jul-06 RMS Added sim_activate_abs
02-Jun-06 JDB Fixed do_cmd to exit nested files on assertion failure
Added -E switch to do_cmd to exit on any error
14-Feb-06 RMS Upgraded save file format to V3.5
18-Jan-06 RMS Added fprint_stopped_gen
Added breakpoint spaces
Fixed unaligned register access (Doug Carman)
22-Sep-05 RMS Fixed declarations (Sterling Garwood)
30-Aug-05 RMS Revised to trim trailing spaces on file names
25-Aug-05 RMS Added variable default device support
23-Aug-05 RMS Added Linux line history support
16-Aug-05 RMS Fixed C++ declaration and cast problems
01-May-05 RMS Revised syntax for SET DEBUG (Dave Bryan)
22-Mar-05 JDB Modified DO command to allow ten-level nesting
18-Mar-05 RMS Moved DETACH tests into detach_unit (Dave Bryan)
Revised interface to fprint_sym, fparse_sym
13-Mar-05 JDB ASSERT now requires a conditional operator
07-Feb-05 RMS Added ASSERT command (Dave Bryan)
02-Feb-05 RMS Fixed bug in global register search
26-Dec-04 RMS Qualified SAVE examine, RESTORE deposit with SIM_SW_REST
10-Nov-04 JDB Fixed logging of errors from cmds in "do" file
05-Nov-04 RMS Moved SET/SHOW DEBUG under CONSOLE hierarchy
Renamed unit OFFLINE/ONLINE to DISABLED/ENABLED (Dave Bryan)
Revised to flush output files after simulation stop (Dave Bryan)
15-Oct-04 RMS Fixed HELP to suppress duplicate descriptions
27-Sep-04 RMS Fixed comma-separation options in set (David Bryan)
09-Sep-04 RMS Added -p option for RESET
13-Aug-04 RMS Qualified RESTORE detach with SIM_SW_REST
17-Jul-04 JDB DO cmd file open failure retries with ".sim" appended
17-Jul-04 RMS Added ECHO command (Dave Bryan)
12-Jul-04 RMS Fixed problem ATTACHing to read only files
(John Dundas)
28-May-04 RMS Added SET/SHOW CONSOLE
14-Feb-04 RMS Updated SAVE/RESTORE (V3.2)
RMS Added debug print routines (Dave Hittner)
RMS Added sim_vm_parse_addr and sim_vm_fprint_addr
RMS Added REG_VMAD support
RMS Split out libraries
RMS Moved logging function to SCP
RMS Exposed step counter interface(s)
RMS Fixed double logging of SHOW BREAK (Mark Pizzolato)
RMS Fixed implementation of REG_VMIO
RMS Added SET/SHOW DEBUG, SET/SHOW <device> DEBUG,
SHOW <device> MODIFIERS, SHOW <device> RADIX
RMS Changed sim_fsize to take uptr argument
29-Dec-03 RMS Added Telnet console output stall support
01-Nov-03 RMS Cleaned up implicit detach on attach/restore
Fixed bug in command line read while logging (Mark Pizzolato)
01-Sep-03 RMS Fixed end-of-file problem in dep, idep
Fixed error on trailing spaces in dep, idep
15-Jul-03 RMS Removed unnecessary test in reset_all
15-Jun-03 RMS Added register flag REG_VMIO
25-Apr-03 RMS Added extended address support (V3.0)
Fixed bug in SAVE (Peter Schorn)
Added u5, u6 fields
Added logical name support
03-Mar-03 RMS Added sim_fsize
27-Feb-03 RMS Fixed bug in multiword deposits to files
08-Feb-03 RMS Changed sim_os_sleep to void, match_ext to char*
Added multiple actions, .ini file support
Added multiple switch evaluations per line
07-Feb-03 RMS Added VMS support for ! (Mark Pizzolato)
01-Feb-03 RMS Added breakpoint table extension, actions
14-Jan-03 RMS Added missing function prototypes
10-Jan-03 RMS Added attach/restore flag, dynamic memory size support,
case sensitive SET options
22-Dec-02 RMS Added ! (OS command) feature (Mark Pizzolato)
17-Dec-02 RMS Added get_ipaddr
02-Dec-02 RMS Added EValuate command
16-Nov-02 RMS Fixed bug in register name match algorithm
13-Oct-02 RMS Fixed Borland compiler warnings (Hans Pufal)
05-Oct-02 RMS Fixed bugs in set_logon, ssh_break (David Hittner)
Added support for fixed buffer devices
Added support for Telnet console, removed VT support
Added help <command>
Added VMS file optimizations (Robert Alan Byer)
Added quiet mode, DO with parameters, GUI interface,
extensible commands (Brian Knittel)
Added device enable/disable commands
14-Jul-02 RMS Fixed exit bug in do, added -v switch (Brian Knittel)
17-May-02 RMS Fixed bug in fxread/fxwrite error usage (found by
Norm Lastovic)
02-May-02 RMS Added VT emulation interface, changed {NO}LOG to SET {NO}LOG
22-Apr-02 RMS Fixed laptop sleep problem in clock calibration, added
magtape record length error (Jonathan Engdahl)
26-Feb-02 RMS Fixed initialization bugs in do_cmd, get_aval
(Brian Knittel)
10-Feb-02 RMS Fixed problem in clock calibration
06-Jan-02 RMS Moved device enable/disable to simulators
30-Dec-01 RMS Generalized timer packaged, added circular arrays
19-Dec-01 RMS Fixed DO command bug (John Dundas)
07-Dec-01 RMS Implemented breakpoint package
05-Dec-01 RMS Fixed bug in universal register logic
03-Dec-01 RMS Added read-only units, extended SET/SHOW, universal registers
24-Nov-01 RMS Added unit-based registers
16-Nov-01 RMS Added DO command
28-Oct-01 RMS Added relative range addressing
08-Oct-01 RMS Added SHOW VERSION
30-Sep-01 RMS Relaxed attach test in BOOT
27-Sep-01 RMS Added queue count routine, fixed typo in ex/mod
17-Sep-01 RMS Removed multiple console support
07-Sep-01 RMS Removed conditional externs on function prototypes
Added special modifier print
31-Aug-01 RMS Changed int64 to t_int64 for Windoze (V2.7)
18-Jul-01 RMS Minor changes for Macintosh port
12-Jun-01 RMS Fixed bug in big-endian I/O (Dave Conroy)
27-May-01 RMS Added multiple console support
16-May-01 RMS Added logging
15-May-01 RMS Added features from Tim Litt
12-May-01 RMS Fixed missing return in disable_cmd
25-Mar-01 RMS Added ENABLE/DISABLE
14-Mar-01 RMS Revised LOAD/DUMP interface (again)
05-Mar-01 RMS Added clock calibration support
05-Feb-01 RMS Fixed bug, DETACH buffered unit with hwmark = 0
04-Feb-01 RMS Fixed bug, RESTORE not using device's attach routine
21-Jan-01 RMS Added relative time
22-Dec-00 RMS Fixed find_device for devices ending in numbers
08-Dec-00 RMS V2.5a changes
30-Oct-00 RMS Added output file option to examine
11-Jul-99 RMS V2.5 changes
13-Apr-99 RMS Fixed handling of 32b addresses
04-Oct-98 RMS V2.4 changes
20-Aug-98 RMS Added radix commands
05-Jun-98 RMS Fixed bug in ^D handling for UNIX
10-Apr-98 RMS Added switches to all commands
26-Oct-97 RMS Added search capability
25-Jan-97 RMS Revised data types
23-Jan-97 RMS Added bi-endian I/O
06-Sep-96 RMS Fixed bug in variable length IEXAMINE
16-Jun-96 RMS Changed interface to parse/print_sym
06-Apr-96 RMS Added error checking in reset all
07-Jan-96 RMS Added register buffers in save/restore
11-Dec-95 RMS Fixed ordering bug in save/restore
22-May-95 RMS Added symbolic input
13-Apr-95 RMS Added symbolic printouts
*/
/* Macros and data structures */
#define NOT_MUX_USING_CODE /* sim_tmxr library provider or agnostic */
#include "sim_defs.h"
#include "sim_rev.h"
#include "sim_disk.h"
#include "sim_tape.h"
#include "sim_ether.h"
#include "sim_serial.h"
#if defined (USE_SIM_VIDEO)
#include "sim_video.h"
#endif
#include "sim_sock.h"
#include <signal.h>
#include <ctype.h>
#include <time.h>
#if defined(_WIN32)
#include <direct.h>
#include <io.h>
#include <fcntl.h>
#else
#include <unistd.h>
#endif
#include <sys/stat.h>
#include <setjmp.h>
#if defined(HAVE_DLOPEN) /* Dynamic Readline support */
#include <dlfcn.h>
#endif
#ifndef MAX
#define MAX(a,b) (((a) >= (b)) ? (a) : (b))
#endif
/* search logical and boolean ops */
#define SCH_OR 0 /* search logicals */
#define SCH_AND 1
#define SCH_XOR 2
#define SCH_E 0 /* search booleans */
#define SCH_N 1
#define SCH_G 2
#define SCH_L 3
#define SCH_EE 4
#define SCH_NE 5
#define SCH_GE 6
#define SCH_LE 7
#define MAX_DO_NEST_LVL 20 /* DO cmd nesting level */
#define SRBSIZ 1024 /* save/restore buffer */
#define SIM_BRK_INILNT 4096 /* bpt tbl length */
#define SIM_BRK_ALLTYP 0xFFFFFFFB
#define UPDATE_SIM_TIME \
if (1) { \
int32 _x; \
AIO_LOCK; \
if (sim_clock_queue == QUEUE_LIST_END) \
_x = noqueue_time; \
else \
_x = sim_clock_queue->time; \
sim_time = sim_time + (_x - sim_interval); \
sim_rtime = sim_rtime + ((uint32) (_x - sim_interval)); \
if (sim_clock_queue == QUEUE_LIST_END) \
noqueue_time = sim_interval; \
else \
sim_clock_queue->time = sim_interval; \
AIO_UNLOCK; \
} \
else \
(void)0 \
#define SZ_D(dp) (size_map[((dp)->dwidth + CHAR_BIT - 1) / CHAR_BIT])
#define SZ_R(rp) \
(size_map[((rp)->width + (rp)->offset + CHAR_BIT - 1) / CHAR_BIT])
#if defined (USE_INT64)
#define SZ_LOAD(sz,v,mb,j) \
if (sz == sizeof (uint8)) v = *(((uint8 *) mb) + ((uint32) j)); \
else if (sz == sizeof (uint16)) v = *(((uint16 *) mb) + ((uint32) j)); \
else if (sz == sizeof (uint32)) v = *(((uint32 *) mb) + ((uint32) j)); \
else v = *(((t_uint64 *) mb) + ((uint32) j));
#define SZ_STORE(sz,v,mb,j) \
if (sz == sizeof (uint8)) *(((uint8 *) mb) + j) = (uint8) v; \
else if (sz == sizeof (uint16)) *(((uint16 *) mb) + ((uint32) j)) = (uint16) v; \
else if (sz == sizeof (uint32)) *(((uint32 *) mb) + ((uint32) j)) = (uint32) v; \
else *(((t_uint64 *) mb) + ((uint32) j)) = v;
#else
#define SZ_LOAD(sz,v,mb,j) \
if (sz == sizeof (uint8)) v = *(((uint8 *) mb) + ((uint32) j)); \
else if (sz == sizeof (uint16)) v = *(((uint16 *) mb) + ((uint32) j)); \
else v = *(((uint32 *) mb) + ((uint32) j));
#define SZ_STORE(sz,v,mb,j) \
if (sz == sizeof (uint8)) *(((uint8 *) mb) + ((uint32) j)) = (uint8) v; \
else if (sz == sizeof (uint16)) *(((uint16 *) mb) + ((uint32) j)) = (uint16) v; \
else *(((uint32 *) mb) + ((uint32) j)) = v;
#endif
#define GET_SWITCHES(cp) \
if ((cp = get_sim_sw (cp)) == NULL) return SCPE_INVSW
#define GET_RADIX(val,dft) \
if (sim_switches & SWMASK ('O')) val = 8; \
else if (sim_switches & SWMASK ('D')) val = 10; \
else if (sim_switches & SWMASK ('H')) val = 16; \
else val = dft;
/* Asynch I/O support */
#if defined (SIM_ASYNCH_IO)
pthread_mutex_t sim_asynch_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t sim_asynch_wake = PTHREAD_COND_INITIALIZER;
pthread_mutex_t sim_timer_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t sim_timer_wake = PTHREAD_COND_INITIALIZER;
pthread_mutex_t sim_tmxr_poll_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t sim_tmxr_poll_cond = PTHREAD_COND_INITIALIZER;
int32 sim_tmxr_poll_count;
pthread_t sim_asynch_main_threadid;
UNIT * volatile sim_asynch_queue;
UNIT * volatile sim_wallclock_queue;
UNIT * volatile sim_wallclock_entry;
UNIT * volatile sim_clock_cosched_queue[SIM_NTIMERS];
t_bool sim_asynch_enabled = TRUE;
int32 sim_asynch_check;
int32 sim_asynch_latency = 4000; /* 4 usec interrupt latency */
int32 sim_asynch_inst_latency = 20; /* assume 5 mip simulator */
#else
t_bool sim_asynch_enabled = FALSE;
#endif
/* The per-simulator init routine is a weak global that defaults to NULL
The other per-simulator pointers can be overrriden by the init routine */
void (*sim_vm_init) (void);
char* (*sim_vm_read) (char *ptr, int32 size, FILE *stream) = NULL;
void (*sim_vm_post) (t_bool from_scp) = NULL;
CTAB *sim_vm_cmd = NULL;
void (*sim_vm_fprint_addr) (FILE *st, DEVICE *dptr, t_addr addr) = NULL;
t_addr (*sim_vm_parse_addr) (DEVICE *dptr, char *cptr, char **tptr) = NULL;
t_value (*sim_vm_pc_value) (void) = NULL;
t_bool (*sim_vm_is_subroutine_call) (t_addr **ret_addrs) = NULL;
t_bool (*sim_vm_fprint_stopped) (FILE *st, t_stat reason) = NULL;
/* Prototypes */
/* Set and show command processors */
t_stat set_dev_radix (DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat set_dev_enbdis (DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat set_dev_debug (DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat set_unit_enbdis (DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat ssh_break (FILE *st, char *cptr, int32 flg);
t_stat show_cmd_fi (FILE *ofile, int32 flag, char *cptr);
t_stat show_config (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_queue (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_time (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_mod_names (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_show_commands (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_log_names (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_dev_radix (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_dev_debug (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_dev_logicals (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_dev_modifiers (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_dev_show_commands (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_version (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_default (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_break (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_on (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat sim_show_send (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat sim_show_expect (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_device (FILE *st, DEVICE *dptr, int32 flag);
t_stat show_unit (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag);
t_stat show_all_mods (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flg, int32 *toks);
t_stat show_one_mod (FILE *st, DEVICE *dptr, UNIT *uptr, MTAB *mptr, char *cptr, int32 flag);
t_stat sim_check_console (int32 sec);
t_stat sim_save (FILE *sfile);
t_stat sim_rest (FILE *rfile);
/* Breakpoint package */
t_stat sim_brk_init (void);
t_stat sim_brk_set (t_addr loc, int32 sw, int32 ncnt, char *act);
t_stat sim_brk_clr (t_addr loc, int32 sw);
t_stat sim_brk_clrall (int32 sw);
t_stat sim_brk_show (FILE *st, t_addr loc, int32 sw);
t_stat sim_brk_showall (FILE *st, int32 sw);
char *sim_brk_getact (char *buf, int32 size);
void sim_brk_npc (uint32 cnt);
BRKTAB *sim_brk_new (t_addr loc);
FILE *stdnul;
/* Command support routines */
SCHTAB *get_rsearch (const char *cptr, int32 radix, SCHTAB *schptr);
SCHTAB *get_asearch (const char *cptr, int32 radix, SCHTAB *schptr);
int32 test_search (t_value *val, SCHTAB *schptr);
static const char *get_glyph_gen (const char *iptr, char *optr, char mchar, t_bool uc, t_bool quote, char escape_char);
int32 get_switches (char *cptr);
char *get_sim_sw (char *cptr);
t_stat get_aval (t_addr addr, DEVICE *dptr, UNIT *uptr);
t_value get_rval (REG *rptr, uint32 idx);
void put_rval (REG *rptr, uint32 idx, t_value val);
void fprint_help (FILE *st);
void fprint_stopped (FILE *st, t_stat r);
void fprint_capac (FILE *st, DEVICE *dptr, UNIT *uptr);
void fprint_sep (FILE *st, int32 *tokens);
char *read_line (char *ptr, int32 size, FILE *stream);
char *read_line_p (const char *prompt, char *ptr, int32 size, FILE *stream);
REG *find_reg_glob (const char *ptr, const char **optr, DEVICE **gdptr);
char *sim_trim_endspc (char *cptr);
/* Forward references */
t_stat scp_attach_unit (DEVICE *dptr, UNIT *uptr, char *cptr);
t_stat scp_detach_unit (DEVICE *dptr, UNIT *uptr);
t_bool qdisable (DEVICE *dptr);
t_stat attach_err (UNIT *uptr, t_stat stat);
t_stat detach_all (int32 start_device, t_bool shutdown);
t_stat assign_device (DEVICE *dptr, char *cptr);
t_stat deassign_device (DEVICE *dptr);
t_stat ssh_break_one (FILE *st, int32 flg, t_addr lo, int32 cnt, char *aptr);
t_stat exdep_reg_loop (FILE *ofile, SCHTAB *schptr, int32 flag, char *cptr,
REG *lowr, REG *highr, uint32 lows, uint32 highs);
t_stat ex_reg (FILE *ofile, t_value val, int32 flag, REG *rptr, uint32 idx);
t_stat dep_reg (int32 flag, char *cptr, REG *rptr, uint32 idx);
t_stat exdep_addr_loop (FILE *ofile, SCHTAB *schptr, int32 flag, char *cptr,
t_addr low, t_addr high, DEVICE *dptr, UNIT *uptr);
t_stat ex_addr (FILE *ofile, int32 flag, t_addr addr, DEVICE *dptr, UNIT *uptr);
t_stat dep_addr (int32 flag, char *cptr, t_addr addr, DEVICE *dptr,
UNIT *uptr, int32 dfltinc);
void fprint_fields (FILE *stream, t_value before, t_value after, BITFIELD* bitdefs);
t_stat step_svc (UNIT *ptr);
t_stat expect_svc (UNIT *ptr);
t_stat shift_args (char *do_arg[], size_t arg_count);
t_stat set_on (int32 flag, char *cptr);
t_stat set_verify (int32 flag, char *cptr);
t_stat set_message (int32 flag, char *cptr);
t_stat set_quiet (int32 flag, char *cptr);
t_stat set_asynch (int32 flag, char *cptr);
t_stat sim_show_asynch (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat do_cmd_label (int32 flag, char *cptr, char *label);
void int_handler (int signal);
t_stat set_prompt (int32 flag, char *cptr);
t_stat sim_set_asynch (int32 flag, char *cptr);
t_stat sim_set_environment (int32 flag, char *cptr);
static char *get_dbg_verb (uint32 dbits, DEVICE* dptr);
/* Global data */
DEVICE *sim_dflt_dev = NULL;
UNIT *sim_clock_queue = QUEUE_LIST_END;
int32 sim_interval = 0;
int32 sim_switches = 0;
FILE *sim_ofile = NULL;
SCHTAB *sim_schrptr = FALSE;
SCHTAB *sim_schaptr = FALSE;
DEVICE *sim_dfdev = NULL;
UNIT *sim_dfunit = NULL;
DEVICE **sim_internal_devices = NULL;
uint32 sim_internal_device_count = 0;
int32 sim_opt_out = 0;
int32 sim_is_running = 0;
uint32 sim_brk_summ = 0;
uint32 sim_brk_types = 0;
uint32 sim_brk_dflt = 0;
char *sim_brk_act[MAX_DO_NEST_LVL];
char *sim_brk_act_buf[MAX_DO_NEST_LVL];
BRKTAB *sim_brk_tab = NULL;
int32 sim_brk_ent = 0;
int32 sim_brk_lnt = 0;
int32 sim_brk_ins = 0;
t_bool sim_brk_pend[SIM_BKPT_N_SPC] = { FALSE };
t_addr sim_brk_ploc[SIM_BKPT_N_SPC] = { 0 };
int32 sim_quiet = 0;
int32 sim_step = 0;
static double sim_time;
static uint32 sim_rtime;
static int32 noqueue_time;
volatile int32 stop_cpu = 0;
static char **sim_argv;
t_value *sim_eval = NULL;
FILE *sim_log = NULL; /* log file */
FILEREF *sim_log_ref = NULL; /* log file file reference */
FILE *sim_deb = NULL; /* debug file */
FILEREF *sim_deb_ref = NULL; /* debug file file reference */
int32 sim_deb_switches = 0; /* debug switches */
struct timespec sim_deb_basetime; /* debug timestamp relative base time */
char *sim_prompt = NULL; /* prompt string */
static FILE *sim_gotofile; /* the currently open do file */
static int32 sim_goto_line[MAX_DO_NEST_LVL+1]; /* the current line number in the currently open do file */
static int32 sim_do_echo = 0; /* the echo status of the currently open do file */
static int32 sim_show_message = 1; /* the message display status of the currently open do file */
static int32 sim_on_inherit = 0; /* the inherit status of on state and conditions when executing do files */
int32 sim_do_depth = 0;
static int32 sim_on_check[MAX_DO_NEST_LVL+1];
static char *sim_on_actions[MAX_DO_NEST_LVL+1][SCPE_MAX_ERR+1];
static char sim_do_filename[MAX_DO_NEST_LVL+1][CBUFSIZE];
static char *sim_do_ocptr[MAX_DO_NEST_LVL+1];
static char *sim_do_label[MAX_DO_NEST_LVL+1];
static t_stat sim_last_cmd_stat; /* Command Status */
static SCHTAB sim_stabr; /* Register search specifier */
static SCHTAB sim_staba; /* Memory search specifier */
static UNIT sim_step_unit = { UDATA (&step_svc, 0, 0) };
static UNIT sim_expect_unit = { UDATA (&expect_svc, 0, 0) };
#if defined USE_INT64
static const char *sim_si64 = "64b data";
#else
static const char *sim_si64 = "32b data";
#endif
#if defined USE_ADDR64
static const char *sim_sa64 = "64b addresses";
#else
static const char *sim_sa64 = "32b addresses";
#endif
/* Tables and strings */
const char save_vercur[] = "V3.5";
const char save_ver32[] = "V3.2";
const char save_ver30[] = "V3.0";
const struct scp_error {
char *code;
char *message;
} scp_errors[1+SCPE_MAX_ERR-SCPE_BASE] =
{{"NXM", "Address space exceeded"},
{"UNATT", "Unit not attached"},
{"IOERR", "I/O error"},
{"CSUM", "Checksum error"},
{"FMT", "Format error"},
{"NOATT", "Unit not attachable"},
{"OPENERR", "File open error"},
{"MEM", "Memory exhausted"},
{"ARG", "Invalid argument"},
{"STEP", "Step expired"},
{"UNK", "Unknown command"},
{"RO", "Read only argument"},
{"INCOMP", "Command not completed"},
{"STOP", "Simulation stopped"},
{"EXIT", "Goodbye"},
{"TTIERR", "Console input I/O error"},
{"TTOERR", "Console output I/O error"},
{"EOF", "End of file"},
{"REL", "Relocation error"},
{"NOPARAM", "No settable parameters"},
{"ALATT", "Unit already attached"},
{"TIMER", "Hardware timer error"},
{"SIGERR", "SIGINT handler setup error"},
{"TTYERR", "Console terminal setup error"},
{"SUB", "Subscript out of range"},
{"NOFNC", "Command not allowed"},
{"UDIS", "Unit disabled"},
{"NORO", "Read only operation not allowed"},
{"INVSW", "Invalid switch"},
{"MISVAL", "Missing value"},
{"2FARG", "Too few arguments"},
{"2MARG", "Too many arguments"},
{"NXDEV", "Non-existent device"},
{"NXUN", "Non-existent unit"},
{"NXREG", "Non-existent register"},
{"NXPAR", "Non-existent parameter"},
{"NEST", "Nested DO command limit exceeded"},
{"IERR", "Internal error"},
{"MTRLNT", "Invalid magtape record length"},
{"LOST", "Console Telnet connection lost"},
{"TTMO", "Console Telnet connection timed out"},
{"STALL", "Console Telnet output stall"},
{"AFAIL", "Assertion failed"},
{"INVREM", "Invalid remote console command"},
{"NOTATT", "Not attached"},
{"EXPECT", "Expect matched"},
};
const size_t size_map[] = { sizeof (int8),
sizeof (int8), sizeof (int16), sizeof (int32), sizeof (int32)
#if defined (USE_INT64)
, sizeof (t_int64), sizeof (t_int64), sizeof (t_int64), sizeof (t_int64)
#endif
};
const t_value width_mask[] = { 0,
0x1, 0x3, 0x7, 0xF,
0x1F, 0x3F, 0x7F, 0xFF,
0x1FF, 0x3FF, 0x7FF, 0xFFF,
0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF,
0x1FFFF, 0x3FFFF, 0x7FFFF, 0xFFFFF,
0x1FFFFF, 0x3FFFFF, 0x7FFFFF, 0xFFFFFF,
0x1FFFFFF, 0x3FFFFFF, 0x7FFFFFF, 0xFFFFFFF,
0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF
#if defined (USE_INT64)
, 0x1FFFFFFFF, 0x3FFFFFFFF, 0x7FFFFFFFF, 0xFFFFFFFFF,
0x1FFFFFFFFF, 0x3FFFFFFFFF, 0x7FFFFFFFFF, 0xFFFFFFFFFF,
0x1FFFFFFFFFF, 0x3FFFFFFFFFF, 0x7FFFFFFFFFF, 0xFFFFFFFFFFF,
0x1FFFFFFFFFFF, 0x3FFFFFFFFFFF, 0x7FFFFFFFFFFF, 0xFFFFFFFFFFFF,
0x1FFFFFFFFFFFF, 0x3FFFFFFFFFFFF, 0x7FFFFFFFFFFFF, 0xFFFFFFFFFFFFF,
0x1FFFFFFFFFFFFF, 0x3FFFFFFFFFFFFF, 0x7FFFFFFFFFFFFF, 0xFFFFFFFFFFFFFF,
0x1FFFFFFFFFFFFFF, 0x3FFFFFFFFFFFFFF,
0x7FFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFF,
0x1FFFFFFFFFFFFFFF, 0x3FFFFFFFFFFFFFFF,
0x7FFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF
#endif
};
static const char simh_help[] =
/***************** 80 character line width template *************************/
"1Commands\n"
#define HLP_RESET "*Commands Resetting Devices"
/***************** 80 character line width template *************************/
"2Resetting Devices\n"
" The RESET command (abbreviation RE) resets a device or the entire simulator\n"
" to a predefined condition. If switch -p is specified, the device is reset\n"
" to its power-up state:\n\n"
"++RESET reset all devices\n"
"++RESET -p powerup all devices\n"
"++RESET ALL reset all devices\n"
"++RESET <device> reset specified device\n\n"
" Typically, RESET stops any in-progress I/O operation, clears any interrupt\n"
" request, and returns the device to a quiescent state. It does not clear\n"
" main memory or affect I/O connections.\n"
#define HLP_EXAMINE "*Commands Examining_and_Changing_State"
#define HLP_IEXAMINE "*Commands Examining_and_Changing_State"
#define HLP_DEPOSIT "*Commands Examining_and_Changing_State"
#define HLP_IDEPOSIT "*Commands Examining_and_Changing_State"
/***************** 80 character line width template *************************/
"2Examining and Changing State\n"
" There are four commands to examine and change state:\n\n"
"++EXAMINE (abbreviated E) examines state\n"
"++DEPOSIT (abbreviated D) changes state\n"
"++IEXAMINE (interactive examine, abbreviated IE) examines state and allows\n"
"++++the user to interactively change it\n"
"++IDEPOSIT (interactive deposit, abbreviated ID) allows the user to\n"
"++++interactively change state\n\n"
" All four commands take the form\n\n"
"++command {modifiers} <object list>\n\n"
" Deposit must also include a deposit value at the end of the command.\n\n"
" There are four kinds of modifiers: switches, device/unit name, search\n"
" specifier, and for EXAMINE, output file. Switches have been described\n"
" previously. A device/unit name identifies the device and unit whose\n"
" address space is to be examined or modified. If no device is specified,\n"
" the CPU (main memory)is selected; if a device but no unit is specified,\n"
" unit 0 of the device is selected.\n\n"
" The search specifier provides criteria for testing addresses or registers\n"
" to see if they should be processed. A specifier consists of a logical\n"
" operator, a relational operator, or both, optionally separated by spaces.\n\n"
"++{<logical op> <value>} <relational op> <value>\n\n"
/***************** 80 character line width template *************************/
" where the logical operator is & (and), | (or), or ^ (exclusive or), and the\n"
" relational operator is = or == (equal), ! or != (not equal), >= (greater\n"
" than or equal), > (greater than), <= (less than or equal), or < (less than).\n"
" If a logical operator is specified without a relational operator, it is\n"
" ignored. If a relational operator is specified without a logical operator,\n"
" no logical operation is performed. All comparisons are unsigned.\n\n"
" The output file modifier redirects command output to a file instead of the\n"
" console. An output file modifier consists of @ followed by a valid file\n"
" name.\n\n"
" Modifiers may be specified in any order. If multiple modifiers of the\n"
" same type are specified, later modifiers override earlier modifiers. Note\n"
" that if the device/unit name comes after the search specifier, the search\n"
" values will interpreted in the radix of the CPU, rather than of the\n"
" device/unit.\n\n"
" The \"object list\" consists of one or more of the following, separated by\n"
" commas:\n\n"
/***************** 80 character line width template *************************/
"++register the specified register\n"
"++register[sub1-sub2] the specified register array locations,\n"
"++ starting at location sub1 up to and\n"
"++ including location sub2\n"
"++register[sub1/length] the specified register array locations,\n"
"++ starting at location sub1 up to but\n"
"++ not including sub1+length\n"
"++register[ALL] all locations in the specified register\n"
"++ array\n"
"++register1-register2 all the registers starting at register1\n"
"++ up to and including register2\n"
"++address the specified location\n"
"++address1-address2 all locations starting at address1 up to\n"
"++ and including address2\n"
"++address/length all location starting at address up to\n"
"++ but not including address+length\n"
"++STATE all registers in the device\n"
"++ALL all locations in the unit\n"
"3Switches\n"
" Switches can be used to control the format of display information:\n\n"
/***************** 80 character line width template *************************/
"++-a display as ASCII\n"
"++-c display as character string\n"
"++-m display as instruction mnemonics\n"
"++-o display as octal\n"
"++-d display as decimal\n"
"++-h display as hexadecimal\n\n"
" The simulators typically accept symbolic input (see documentation with each\n"
" simulator).\n\n"
"3Examples\n"
" Examples:\n\n"
"++ex 1000-1100 examine 1000 to 1100\n"
"++de PC 1040 set PC to 1040\n"
"++ie 40-50 interactively examine 40:50\n"
"++ie >1000 40-50 interactively examine the subset\n"
"++ of locations 40:50 that are >1000\n"
"++ex rx0 50060 examine 50060, RX unit 0\n"
"++ex rx sbuf[3-6] examine SBUF[3] to SBUF[6] in RX\n"
"++de all 0 set main memory to 0\n"
"++de &77>0 0 set all addresses whose low order\n"
"++ bits are non-zero to 0\n"
"++ex -m @memdump.txt 0-7777 dump memory to file\n\n"
" Note: to terminate an interactive command, simply type a bad value\n"
" (eg, XYZ) when input is requested.\n"
#define HLP_EVALUATE "*Commands Evaluating_Instructions"
/***************** 80 character line width template *************************/
"2Evaluating Instructions\n"
" The EVAL command evaluates a symbolic expression and returns the equivalent\n"
" numeric value. This is useful for obtaining numeric arguments for a search\n"
" command:\n\n"
"++EVAL <expression>\n"
/***************** 80 character line width template *************************/
"2Loading and Saving Programs\n"
#define HLP_LOAD "*Commands Loading_and_Saving_Programs LOAD"
"3LOAD\n"
" The LOAD command (abbreviation LO) loads a file in binary loader format:\n\n"
"++LOAD <filename> {implementation options}\n\n"
" The types of formats supported are implementation specific. Options (such\n"
" as load within range) are also implementation specific.\n\n"
#define HLP_DUMP "*Commands Loading_and_Saving_Programs DUMP"
"3DUMP\n"
" The DUMP command (abbreviation DU) dumps memory in binary loader format:\n\n"
"++DUMP <filename> {implementation options}\n\n"
" The types of formats supported are implementation specific. Options (such\n"
" as dump within range) are also implementation specific.\n"
/***************** 80 character line width template *************************/
"2Saving and Restoring State\n"
#define HLP_SAVE "*Commands Saving_and_Restoring_State SAVE"
"3SAVE\n"
" The SAVE command (abbreviation SA) save the complete state of the simulator\n"
" to a file. This includes the contents of main memory and all registers,\n"
" and the I/O connections of devices:\n\n"
"++SAVE <filename>\n\n"
#define HLP_RESTORE "*Commands Saving_and_Restoring_State RESTORE"
"3RESTORE\n"
" The RESTORE command (abbreviation REST, alternately GET) restores a\n"
" previously saved simulator state:\n\n"
"++RESTORE <filename>\n"
"3Notes:\n"
" 1) SAVE file format compresses zeroes to minimize file size.\n"
" 2) The simulator can't restore active incoming telnet sessions to\n"
" multiplexer devices, but the listening ports will be restored across a\n"
" save/restore.\n"
/***************** 80 character line width template *************************/
"2Running A Simulated Program\n"
#define HLP_RUN "*Commands Running_A_Simulated_Program RUN"
"3RUN\n"
" The RUN command (abbreviated RU) resets all devices, deposits its argument\n"
" (if given) in the PC, and starts execution. If no argument is given,\n"
" execution starts at the current PC.\n"
#define HLP_GO "*Commands Running_A_Simulated_Program GO"
"3GO\n"
" The GO command does not reset devices, deposits its argument (if given)\n"
" in the PC, and starts execution. If no argument is given, execution\n"
" starts at the current PC.\n"
#define HLP_CONTINUE "*Commands Running_A_Simulated_Program CONTINUE"
"3CONTINUE\n"
" The CONT command (abbreviated CO) does not reset devices and resumes\n"
" execution at the current PC.\n"
#define HLP_STEP "*Commands Running_A_Simulated_Program STEP"
"3STEP\n"
" The STEP command (abbreviated S) resumes execution at the current PC for\n"
" the number of instructions given by its argument. If no argument is\n"
" supplied, one instruction is executed.\n"
"4Switches\n"
" If the STEP command is invoked with the -T switch, the step command will\n"
" cause execution to run for microseconds rather than instructions.\n"
#define HLP_NEXT "*Commands Running_A_Simulated_Program NEXT"
"3NEXT\n"
" The NEXT command (abbreviated N) resumes execution at the current PC for\n"
" one instruction, attempting to execute through a subroutine calls.\n"
" If the next instruction to be executed is not a subroutine call,\n"
" one instruction is executed.\n"
#define HLP_BOOT "*Commands Running_A_Simulated_Program BOOT"
"3BOOT\n"
" The BOOT command (abbreviated BO) resets all devices and bootstraps the\n"
" device and unit given by its argument. If no unit is supplied, unit 0 is\n"
" bootstrapped. The specified unit must be attached.\n"
/***************** 80 character line width template *************************/
"2Stopping The Simulator\n"
" Programs run until the simulator detects an error or stop condition, or\n"
" until the user forces a stop condition.\n"
"3Simulator Detected Stop Conditions\n"
" These simulator-detected conditions stop simulation:\n\n"
"++- HALT instruction. If a HALT instruction is decoded, simulation stops.\n"
"++- Breakpoint. The simulator may support breakpoints (see below).\n"
"++- I/O error. If an I/O error occurs during simulation of an I/O\n"
"+++operation, and the device stop-on-I/O-error flag is set, simulation\n"
"+++usually stops.\n\n"
"++- Processor condition. Certain processor conditions can stop\n"
"+++simulation; these are described with the individual simulators.\n"
"3User Specified Stop Conditions\n"
" Typing the interrupt character stops simulation. The interrupt character\n"
" is defined by the WRU (where are you) console option and is initially set\n"
" to 005 (^E).\n\n"
/***************** 80 character line width template *************************/
#define HLP_BREAK "*Commands Stopping_The_Simulator User_Specified_Stop_Conditions BREAK"
#define HLP_NOBREAK "*Commands Stopping_The_Simulator User_Specified_Stop_Conditions BREAK"
"4Breakpoints\n"
" A simulator may offer breakpoint capability. A simulator may define\n"
" breakpoints of different types, identified by letter (for example, E for\n"
" execution, R for read, W for write, etc). At the moment, most simulators\n"
" support only E (execution) breakpoints.\n\n"
" Associated with a breakpoint are a count and, optionally, one or more\n"
" actions. Each time the breakpoint is taken, the associated count is\n"
" decremented. If the count is less than or equal to 0, the breakpoint\n"
" occurs; otherwise, it is deferred. When the breakpoint occurs, the\n"
" optional actions are automatically executed.\n\n"
" A breakpoint is set by the BREAK or the SET BREAK commands:\n\n"
"++BREAK {-types} {<addr range>{[count]},{addr range...}}{;action;action...}\n"
"++SET BREAK {-types} {<addr range>{[count]},{addr range...}}{;action;action...}\n\n"
" If no type is specified, the simulator-specific default breakpoint type\n"
" (usually E for execution) is used. If no address range is specified, the\n"
" current PC is used. As with EXAMINE and DEPOSIT, an address range may be a\n"
" single address, a range of addresses low-high, or a relative range of\n"
" address/length.\n"
/***************** 80 character line width template *************************/
"5Displaying Breakpoints\n"
" Currently set breakpoints can be displayed with the SHOW BREAK command:\n\n"
"++SHOW {-C} {-types} BREAK {ALL|<addr range>{,<addr range>...}}\n\n"
" Locations with breakpoints of the specified type are displayed.\n\n"
" The -C switch displays the selected breakpoint(s) formatted as commands\n"
" which may be subsequently used to establish the same breakpoint(s).\n\n"
"5Removing Breakpoints\n"
" Breakpoints can be cleared by the NOBREAK or the SET NOBREAK commands.\n"
"5Examples\n"
"++BREAK set E break at current PC\n"
"++BREAK -e 200 set E break at 200\n"
"++BREAK 2000/2[2] set E breaks at 2000,2001 with count = 2\n"
"++BREAK 100;EX AC;D MQ 0 set E break at 100 with actions EX AC and\n"
"+++++++++D MQ 0\n"
"++BREAK 100; delete action on break at 100\n\n"
/***************** 80 character line width template *************************/
"2Connecting and Disconnecting Devices\n"
" Except for main memory and network devices, units are simulated as\n"
" unstructured binary disk files in the host file system. Before using a\n"
" simulated unit, the user must specify the file to be accessed by that unit.\n"
#define HLP_ATTACH "*Commands Connecting_and_Disconnecting_Devices ATTACH"
"3ATTACH\n"
" The ATTACH (abbreviation AT) command associates a unit and a file:\n"
"++ATTACH <unit> <filename>\n\n"
"4Switches\n"
"5-n\n"
" If the -n switch is specified when an attach is executed, a new file is\n"
" created, and an appropriate message is printed.\n"
"5-e\n"
" If the file does not exist, and the -e switch was not specified, a new\n"
" file is created, and an appropriate message is printed. If the -e switch\n"
" was specified, a new file is not created, and an error message is printed.\n"
"5-r\n"
" If the -r switch is specified, or the file is write protected, ATTACH tries\n"
" to open the file read only. If the file does not exist, or the unit does\n"
" not support read only operation, an error occurs. Input-only devices, such\n"
" as paper-tape readers, and devices with write lock switches, such as disks\n"
" and tapes, support read only operation; other devices do not. If a file is\n"
" attached read only, its contents can be examined but not modified.\n"
"5-q\n"
" If the -q switch is specified when creating a new file (-n) or opening one\n"
" read only (-r), the message announcing this fact is suppressed.\n"
"5-f\n"
" For simulated magnetic tapes, the ATTACH command can specify the format of\n"
" the attached tape image file:\n\n"
"++ATTACH -f <tape_unit> <format> <filename>\n\n"
" The currently supported tape image file formats are:\n\n"
"++SIMH SIMH simulator format\n"
"++E11 E11 simulator format\n"
"++TPC TPC format\n"
"++P7B Pierce simulator 7-track format\n\n"
/***************** 80 character line width template *************************/
" For some simulated disk devices, the ATTACH command can specify the format\n"
" of the attached disk image file:\n\n"
"++ATTACH -f <disk_unit> <format> <filename>\n\n"
" The currently supported disk image file formats are:\n\n"
"++SIMH SIMH simulator format\n"
"++VHD Virtual Disk format\n"
"++RAW platform specific access to physical disk or\n"
"++ CDROM drives\n"
" The disk format can also be set with the SET command prior to ATTACH:\n\n"
"++SET <disk_unit> FORMAT=<format>\n"
"++ATT <disk_unit> <filename>\n\n"
/***************** 80 character line width template *************************/
" The format of an attached tape or disk file can be displayed with the SHOW\n"
" command:\n"
"++SHOW <unit> FORMAT\n"
" For Telnet-based terminal emulation devices, the ATTACH command associates\n"
" the master unit with a TCP/IP listening port:\n\n"
"++ATTACH <unit> <port>\n\n"
" The port is a decimal number between 1 and 65535 that is not already used\n"
" other TCP/IP applications.\n"
" For Ethernet emulators, the ATTACH command associates the simulated Ethernet\n"
" with a physical Ethernet device:\n\n"
"++ATTACH <unit> <physical device name>\n"
/***************** 80 character line width template *************************/
#define HLP_DETACH "*Commands Connecting_and_Disconnecting_Devices DETACH"
"3DETACH\n"
" The DETACH (abbreviation DET) command breaks the association between a unit\n"
" and a file, port, or network device:\n\n"
"++DETACH ALL detach all units\n"
"++DETACH <unit> detach specified unit\n"
" The EXIT command performs an automatic DETACH ALL.\n"
"2Controlling Simulator Operating Environment\n"
"3Working Directory\n"
#define HLP_CD "*Commands Controlling_Simulator_Operating_Environment Working_Directory CD"
"4CD\n"
" Set the current working directory:\n"
"++CD path\n"
"4SET_DEFAULT\n"
" Set the current working directory:\n"
"++SET DEFAULT path\n"
#define HLP_PWD "*Commands Controlling_Simulator_Operating_Environment Working_Directory PWD"
"4PWD\n"
"++PWD\n"
" Display the current working directory:\n"
"2Listing Files\n"
#define HLP_DIR "*Commands Listing_Files DIR"
"3DIR\n"
"++DIR {path} list directory files\n"
#define HLP_LS "*Commands Listing_Files LS"
"3LS\n"
"++LS {path} list directory files\n"
#define HLP_SET "*Commands SET"
"2SET\n"
/***************** 80 character line width template *************************/
#define HLP_SET_CONSOLE "*Commands SET CONSOLE"
"3Console\n"
"+set console arg{,arg...} set console options\n"
"+set console WRU specify console drop to simh character\n"
"+set console BRK specify console Break character\n"
"+set console DEL specify console delete character\n"
"+set console PCHAR specify console printable characters\n"
"+set console TELNET=port specify console telnet port\n"
"+set console TELNET=LOG=log_file\n"
"++++++++ specify console telnet logging to the\n"
"++++++++ specified destination {LOG,STDOUT,STDERR,\n"
"++++++++ DEBUG or filename)\n"
"+set console TELNET=NOLOG disables console telnet logging\n"
"+set console TELNET=BUFFERED[=bufsize]\n"
"++++++++ specify console telnet buffering\n"
"+set console TELNET=NOBUFFERED\n"
"++++++++ disables console telnet buffering\n"
"+set console TELNET=UNBUFFERED\n"
"++++++++ disables console telnet buffering\n"
"+set console NOTELNET disable console telnet\n"
"+set console SERIAL=serialport[;config]\n"
"++++++++ specify console serial port and optionally\n"
"++++++++ the port config (i.e. ;9600-8n1)\n"
"+set console NOSERIAL disable console serial session\n"
"+set console LOG=log_file enable console logging to the\n"
"++++++++ specified destination {STDOUT,STDERR,DEBUG\n"
"++++++++ or filename)\n"
"+set console NOLOG disable console logging\n"
/***************** 80 character line width template *************************/
#define HLP_SET_REMOTE "*Commands SET REMOTE"
"3Remote\n"
"+set remote TELNET=port specify remote console telnet port\n"
"+set remote NOTELNET disables remote console\n"
"+set remote CONNECTIONS=n specify number of concurrent remote\n"
"++++++++ console sessions\n"
"+set remote TIMEOUT=n specify number of seconds without input\n"
"++++++++ before automatic continue\n"
#define HLP_SET_DEFAULT "*Commands SET Working_Directory"
"3Working Directory\n"
"+set default <dir> set the current directory\n"
"+cd <dir> set the current directory\n"
#define HLP_SET_LOG "*Commands SET Log"
"3Log\n"
"+set log log_file specify the log destination\n"
"++++++++ (STDOUT,DEBUG or filename)\n"
"+set nolog disables any currently active logging\n"
"4Switches\n"
" By default, log output is written at the end of the specified log file.\n"
" A new log file can created if the -N switch is used on the command line.\n"
#define HLP_SET_DEBUG "*Commands SET Debug"
/***************** 80 character line width template *************************/
"3Debug\n"
"+set debug debug_file specify the debug destination\n"
"++++++++ (STDOUT,STDERR,LOG or filename)\n"
"+set nodebug disables any currently active debug output\n"
"4Switches\n"
" Debug message output contains a timestamp which indicates the number of\n"
" simulated instructions which have been executed prior to the debug event.\n\n"
" Debug message output can be enhanced to contain additional, potentially\n"
" useful information.\n"
"5-T\n"
" The -T switch causes debug output to contain a time of day displayed\n"
" as hh:mm:ss.msec.\n"
"5-A\n"
" The -A switch causes debug output to contain a time of day displayed\n"
" as seconds.msec.\n"
"5-R\n"
" The -R switch causes the time of day displayed due to the -T or -A\n"
" switches to be relative to the start time of debugging. If neither\n"
" -T or -A is explicitly specified, -T is implied.\n"
"5-P\n"
" The -P switch adds the output of the PC (Program Counter) to each debug\n"
" message.\n"
"5-N\n"
" The -N switch causes a new/empty file to be written to. The default\n"
" is to append to an existing debug log file.\n"
#define HLP_SET_BREAK "*Commands SET Breakpoints"
"3Breakpoints\n"
"+set break <list> set breakpoints\n"
"+set nobreak <list> clear breakpoints\n"
/***************** 80 character line width template *************************/
#define HLP_SET_THROTTLE "*Commands SET Throttle"
"3Throttle\n"
"+set throttle {x{M|K|%%}}|{x/t}\n"
"++++++++ set simulation rate\n"
"+set nothrottle set simulation rate to maximum\n"
#define HLP_SET_ASYNCH "*Commands SET Asynch"
"3Asynch\n"
"+set asynch enable asynchronous I/O\n"
"+set noasynch disable asynchronous I/O\n"
#define HLP_SET_ENVIRON "*Commands SET Asynch"
"3Environment\n"
"+set environment name=val set environment variable\n"
"+set environment name clear environment variable\n"
#define HLP_SET_ON "*Commands SET Command_Status_Trap_Dispatching"
"3Command Status Trap Dispatching\n"
"+set on enables error checking after command\n"
"++++++++ execution\n"
"+set noon disables error checking after command\n"
"++++++++ execution\n"
"+set on inherit enables inheritance of ON state and\n"
"++++++++ actions into do command files\n"
"+set on noinherit disables inheritance of ON state and\n"
"++++++++ actions into do command files\n"
#define HLP_SET_VERIFY "*Commands SET Command_Execution_Display"
#define HLP_SET_VERIFY "*Commands SET Command_Execution_Display"
"3Command Execution Display\n"
"+set verify re-enables display of command file\n"
"++++++++ processed commands\n"
"+set verbose re-enables display of command file\n"
"++++++++ processed commands\n"
"+set noverify disables display of command file processed\n"
"++++++++ commands\n"
"+set noverbose disables display of command file processed\n"
"++++++++ commands\n"
#define HLP_SET_MESSAGE "*Commands SET Command_Error_Status_Display"
"3Command Error Status Display\n"
"+set message re-enables display of command file error\n"
"++++++++ messages\n"
"+set nomessage disables display of command file error\n"
"++++++++ messages\n"
#define HLP_SET_QUIET "*Commands SET Command_Output_Display"
"3Command Output Display\n"
"+set quiet disables suppression of some output and\n"
"++++++++ messages\n"
"+set noquiet re-enables suppression of some output and\n"
"++++++++ messages\n"
#define HLP_SET_PROMPT "*Commands SET Command_Prompt"
"3Command Prompt\n"
"+set prompt \"string\" sets an alternate simulator prompt string\n"
"3Device and Unit\n"
"+set <dev> OCT|DEC|HEX set device display radix\n"
"+set <dev> ENABLED enable device\n"
"+set <dev> DISABLED disable device\n"
"+set <dev> DEBUG{=arg} set device debug flags\n"
"+set <dev> NODEBUG={arg} clear device debug flags\n"
"+set <dev> arg{,arg...} set device parameters (see show modifiers)\n"
"+set <unit> ENABLED enable unit\n"
"+set <unit> DISABLED disable unit\n"
"+set <unit> arg{,arg...} set unit parameters (see show modifiers)\n"
"+help <dev> set displays the device specific set commands\n"
"++++++++ available\n"
/***************** 80 character line width template *************************/
#define HLP_SHOW "*Commands SHOW"
"2SHOW\n"
"+sh{ow} {-c} br{eak} <list> show breakpoints\n"
"+sh{ow} con{figuration} show configuration\n"
"+sh{ow} cons{ole} {arg} show console options\n"
"+sh{ow} dev{ices} show devices\n"
"+sh{ow} fea{tures} show system devices with descriptions\n"
"+sh{ow} m{odifiers} show modifiers for all devices\n"
"+sh{ow} s{how} show SHOW commands for all devices\n"
"+sh{ow} n{ames} show logical names\n"
"+sh{ow} q{ueue} show event queue\n"
"+sh{ow} ti{me} show simulated time\n"
"+sh{ow} th{rottle} show simulation rate\n"
"+sh{ow} a{synch} show asynchronouse I/O state\n"
"+sh{ow} ve{rsion} show simulator version\n"
"+sh{ow} def{ault} show current directory\n"
"+sh{ow} re{mote} show remote console configuration\n"
"+sh{ow} <dev> RADIX show device display radix\n"
"+sh{ow} <dev> DEBUG show device debug flags\n"
"+sh{ow} <dev> MODIFIERS show device modifiers\n"
"+sh{ow} <dev> NAMES show device logical name\n"
"+sh{ow} <dev> SHOW show device SHOW commands\n"
"+sh{ow} <dev> {arg,...} show device parameters\n"
"+sh{ow} <unit> {arg,...} show unit parameters\n"
"+sh{ow} ethernet show ethernet devices\n"
"+sh{ow} serial show serial devices\n"
"+sh{ow} multiplexer show open multiplexer devices\n"
"+sh{ow} clocks show calibrated timers\n"
"+sh{ow} throttle show throttle info\n"
"+sh{ow} on show on condition actions\n"
"+h{elp} <dev> show displays the device specific show commands\n"
"++++++++ available\n"
#define HLP_SHOW_CONFIG "*Commands SHOW"
#define HLP_SHOW_DEVICES "*Commands SHOW"
#define HLP_SHOW_FEATURES "*Commands SHOW"
#define HLP_SHOW_QUEUE "*Commands SHOW"
#define HLP_SHOW_TIME "*Commands SHOW"
#define HLP_SHOW_MODIFIERS "*Commands SHOW"
#define HLP_SHOW_NAMES "*Commands SHOW"
#define HLP_SHOW_SHOW "*Commands SHOW"
#define HLP_SHOW_VERSION "*Commands SHOW"
#define HLP_SHOW_DEFAULT "*Commands SHOW"
#define HLP_SHOW_CONSOLE "*Commands SHOW"
#define HLP_SHOW_REMOTE "*Commands SHOW"
#define HLP_SHOW_BREAK "*Commands SHOW"
#define HLP_SHOW_LOG "*Commands SHOW"
#define HLP_SHOW_DEBUG "*Commands SHOW"
#define HLP_SHOW_THROTTLE "*Commands SHOW"
#define HLP_SHOW_ASYNCH "*Commands SHOW"
#define HLP_SHOW_ETHERNET "*Commands SHOW"
#define HLP_SHOW_SERIAL "*Commands SHOW"
#define HLP_SHOW_MULTIPLEXER "*Commands SHOW"
#define HLP_SHOW_CLOCKS "*Commands SHOW"
#define HLP_SHOW_ON "*Commands SHOW"
#define HLP_SHOW_SEND "*Commands SHOW"
#define HLP_SHOW_EXPECT "*Commands SHOW"
#define HLP_HELP "*Commands HELP"
/***************** 80 character line width template *************************/
"2HELP\n"
"+h{elp} type this message\n"
"+h{elp} <command> type help for command\n"
"+h{elp} <dev> type help for device\n"
"+h{elp} <dev> registers type help for device register variables\n"
"+h{elp} <dev> attach type help for device specific ATTACH command\n"
"+h{elp} <dev> set type help for device specific SET commands\n"
"+h{elp} <dev> show type help for device specific SHOW commands\n"
"+h{elp} <dev> <command> type help for device specific <command> command\n"
/***************** 80 character line width template *************************/
"2Altering The Simulated Configuration\n"
" In most simulators, the SET <device> DISABLED command removes the\n"
" specified device from the configuration. A DISABLED device is invisible\n"
" to running programs. The device can still be RESET, but it cannot be\n"
" ATTAChed, DETACHed, or BOOTed. SET <device> ENABLED restores a disabled\n"
" device to a configuration.\n\n"
" Most multi-unit devices allow units to be enabled or disabled:\n\n"
"++SET <unit> ENABLED\n"
"++SET <unit> DISABLED\n\n"
" When a unit is disabled, it will not be displayed by SHOW DEVICE.\n\n"
#define HLP_ASSIGN "*Commands Logical_Names"
#define HLP_DEASSIGN "*Commands Logical_Names"
"2Logical Names\n"
" The standard device names can be supplemented with logical names. Logical\n"
" names must be unique within a simulator (that is, they cannot be the same\n"
" as an existing device name). To assign a logical name to a device:\n\n"
"++ASSIGN <device> <log-name> assign log-name to device\n\n"
" To remove a logical name:\n\n"
"++DEASSIGN <device> remove logical name\n\n"
" To show the current logical name assignment:\n\n"
"++SHOW <device> NAMES show logical name, if any\n\n"
" To show all logical names:\n\n"
"++SHOW NAMES\n\n"
/***************** 80 character line width template *************************/
#define HLP_DO "*Commands Executing_Command_Files"
"2Executing Command Files\n"
" The simulator can execute command files with the DO command:\n\n"
"++DO <filename> {arguments...} execute commands in file\n\n"
" The DO command allows command files to contain substitutable arguments.\n"
" The string %%n, where n is between 1 and 9, is replaced with argument n\n"
" from the DO command line. The string %%0 is replaced with <filename>.\n"
" The sequences \\%% and \\\\ are replaced with the literal characters %% and \\,\n"
" respectively. Arguments with spaces can be enclosed in matching single\n"
" or double quotation marks.\n\n"
" DO commands may be nested up to ten invocations deep.\n\n"
"3Switches\n"
" If the switch -v is specified, the commands in the file are echoed before\n"
" they are executed.\n\n"
" If the switch -e is specified, command processing (including nested command\n"
" invocations) will be aborted if a command error is encountered.\n"
" (Simulation stop never abort processing; use ASSERT to catch unexpected\n"
" stops.) Without the switch, all errors except ASSERT failures will be\n"
" ignored, and command processing will continue.\n\n"
" If the switch -o is specified, the on conditions and actions from the\n"
" calling command file will be inherited in the command file being invoked.\n"
" If the switch -q is specified, the quiet mode will be explicitly enabled\n"
" for the called command file, otherwise quiet mode is inherited from the\n"
" calling context.\n"
/***************** 80 character line width template *************************/
#define HLP_GOTO "*Commands Executing_Command_Files GOTO"
"3GOTO\n"
" Commands in a command file execute in sequence until either an error\n"
" trap occurs (when a command completes with an error status), or when an\n"
" explict request is made to start command execution elsewhere with the\n"
" GOTO command:\n\n"
"++GOTO <label>\n\n"
" Labels are lines in a command file which the first non whitespace\n"
" character is a \":\". The target of a goto is the first matching label\n"
" in the current do command file which is encountered. Since labels\n"
" don't do anything else besides being the targets of goto's, they could\n"
" also be used to provide comments in do command files.\n\n"
"4Examples\n\n"
"++:: This is a comment\n"
"++echo Some Message to Output\n"
"++:Target\n"
"++:: This is a comment\n"
"++GOTO Target\n\n"
#define HLP_RETURN "*Commands Executing_Command_Files RETURN"
/***************** 80 character line width template *************************/
"3RETURN\n"
" The RETURN command causes the current procedure call to be restored to the\n"
" calling context, possibly returning a specific return status.\n"
" If no return status is specified, the return status from the last command\n"
" executed will be returned. The calling context may have ON traps defined\n"
" which may redirect command flow in that context.\n\n"
"++return return from command file with last command status\n"
"++return {-Q} <status> return from command file with specific status\n\n"
" The status return can be any numeric value or one of the standard SCPE_\n"
" condition names.\n\n"
" The -Q switch on the RETURN command will cause the specified status to\n"
" be returned, but normal error status message printing to be suppressed.\n"
"4Condition Names\n"
" The available standard SCPE_ condition names are\n"
"5 NXM\n"
" Address space exceeded\n"
"5 UNATT\n"
" Unit not attached\n"
"5 IOERR\n"
" I/O error\n"
"5 CSUM\n"
" Checksum error\n"
"5 FMT\n"
" Format error\n"
"5 NOATT\n"
" Unit not attachable\n"
"5 OPENERR\n"
" File open error\n"
"5 MEM\n"
" Memory exhausted\n"
"5 ARG\n"
" Invalid argument\n"
"5 STEP\n"
" Step expired\n"
"5 UNK\n"
" Unknown command\n"
"5 RO\n"
" Read only argument\n"
"5 INCOMP\n"
" Command not completed\n"
"5 STOP\n"
" Simulation stopped\n"
"5 EXIT\n"
" Goodbye\n"
"5 TTIERR\n"
" Console input I/O error\n"
"5 TTOERR\n"
" Console output I/O error\n"
"5 EOF\n"
" End of file\n"
"5 REL\n"
" Relocation error\n"
"5 NOPARAM\n"
" No settable parameters\n"
"5 ALATT\n"
" Unit already attached\n"
"5 TIMER\n"
" Hardware timer error\n"
"5 SIGERR\n"
" SIGINT handler setup error\n"
"5 TTYERR\n"
" Console terminal setup error\n"
"5 NOFNC\n"
" Command not allowed\n"
"5 UDIS\n"
" Unit disabled\n"
"5 NORO\n"
" Read only operation not allowed\n"
"5 INVSW\n"
" Invalid switch\n"
"5 MISVAL\n"
" Missing value\n"
"5 2FARG\n"
" Too few arguments\n"
"5 2MARG\n"
" Too many arguments\n"
"5 NXDEV\n"
" Non-existent device\n"
"5 NXUN\n"
" Non-existent unit\n"
"5 NXREG\n"
" Non-existent register\n"
"5 NXPAR\n"
" Non-existent parameter\n"
"5 NEST\n"
" Nested DO command limit exceeded\n"
"5 IERR\n"
" Internal error\n"
"5 MTRLNT\n"
" Invalid magtape record length\n"
"5 LOST\n"
" Console Telnet connection lost\n"
"5 TTMO\n"
" Console Telnet connection timed out\n"
"5 STALL\n"
" Console Telnet output stall\n"
"5 AFAIL\n"
" Assertion failed\n"
"5 INVREM\n"
" Invalid remote console command\n"
#define HLP_SHIFT "*Commands Executing_Command_Files SHIFT"
"3SHIFT\n"
"++shift shift the command file's positional parameters\n"
#define HLP_CALL "*Commands Executing_Command_Files CALL"
"3CALL\n"
"++call transfer control to a labeled subroutine\n"
" a command file.\n"
#define HLP_ON "*Commands Executing_Command_Files ON"
"3ON\n"
"++on <condition> <action> perform action(s) after condition\n"
"++on <condition> clear action for specific condition\n"
#define HLP_PROCEED "*Commands Executing_Command_Files PROCEED"
#define HLP_IGNORE "*Commands Executing_Command_Files PROCEED"
/***************** 80 character line width template *************************/
"3PROCEED/IGNORE\n"
" The PROCEED or IGNORE commands do nothing. They are potentially useful\n"
" placeholders for an ON action condition which should be explicitly ignored\n"
"++proceed continue command file execution without doing anything\n"
"++ignore continue command file execution without doing anything\n"
#if 0
SET ON Enables error trapping for currently defined
traps (by ON commands)
SET NOON Disables error trapping for currently
defined traps (by ON commands)
ON <statusvalue> commandtoprocess{; additionalcommandtoprocess}
Sets the action(s) to take when the specific
error status is returned by a command in the
currently running do command file. Multiple
actions can be specified with each delimited
by a semicolon character (just like
breakpoint action commands).
ON ERROR commandtoprocess{; additionalcommandtoprocess}
Sets the default action(s) to take when any
otherwise unspecified error status is returned
by a command in the currently running do
command file. Multiple actions can be
specified with each delimited by a semicolon
character (just like breakpoint action
commands).
ON <statusvalue>
ON ERROR Clears the default actions to take when any
otherwise unspecified error status is
returned by a command in the currently
running do command file.
Error traps can be taken for any command which returns a status other than SCPE_STEP, SCPE_OK, and SCPE_EXIT.
ON Traps can specify any status value from the following list: NXM, UNATT, IOERR, CSUM, FMT, NOATT, OPENERR, MEM, ARG, STEP, UNK, RO, INCOMP, STOP, TTIERR, TTOERR, EOF, REL, NOPARAM, ALATT, TIMER, SIGERR, TTYERR, SUB, NOFNC, UDIS, NORO, INVSW, MISVAL, 2FARG, 2MARG, NXDEV, NXUN, NXREG, NXPAR, NEST, IERR, MTRLNT, LOST, TTMO, STALL, AFAIL. These values can be indicated by name or by their internal numeric value (not recommended).
Interactions with ASSERT command and "DO -e":
DO -e is equivalent to SET ON, which by itself it equivalent to "SET ON; ON ERROR RETURN".
ASSERT failure have several different actions:
If error trapping is not enabled then AFAIL causes exit from the current do command file.
If error trapping is enabled and an explicit "ON AFAIL" action is defined, then the specified action is performed.
If error trapping is enabled and no "ON AFAIL" action is defined, then an AFAIL causes exit from the current do command file.
#endif
#define HLP_ECHO "*Commands Executing_Command_Files Displaying_Arbitrary_Text"
/***************** 80 character line width template *************************/
"3Displaying Arbitrary Text\n"
" The ECHO command is a useful way of annotating command files. ECHO prints\n"
" out its arguments on the console (and log):\n\n"
"++ECHO <string> output string to console\n\n"
" If there is no argument, ECHO prints a blank line on the console. This\n"
" may be used to provide spacing in the console display or log.\n"
/***************** 80 character line width template *************************/
#define HLP_SEND "*Commands Executing_Command_Files Injecting_Console_Input"
/***************** 80 character line width template *************************/
"3Injecting Console Input\n"
" The SEND command provides a way to insert input into the console device of\n"
" a simulated system as if it was entered by a user.\n\n"
"++SEND {-t} {after=nn,}{delay=nn,}\"<string>\"\n\n"
" The string argument must be delimited by quote characters. Quotes may\n"
" be either single or double but the opening and closing quote characters\n"
" must match. Data in the string may contain escaped character strings.\n\n"
" The SEND command can also insert input into any serial device on a\n"
" simulated system as if it was entered by a user.\n\n"
"++SEND {-t} <dev>:line {after=nn,}{delay=nn,}\"<string>\"\n\n"
"4Delay\n"
" Specifies a positive integer representing a minimal instruction delay\n"
" between characters being sent. The value specified in a delay\n"
" argument persists across SEND commands to the same device (console or\n"
" serial device). The delay parameter can be set by itself with:\n\n"
"++SEND DELAY=n\n\n"
" The default value of the delay parameter is 1000.\n"
/***************** 80 character line width template *************************/
"4After\n"
" Specifies a positive integer representing a minimal number of instructions\n"
" which must execute before the first character in the string is sent.\n"
" The value specified as the after parameter persists across SEND commands\n"
" to the same device (console or serial device). The after parameter value\n"
" can be set by itself with:\n\n"
"++SEND AFTER=n\n\n"
" If the after parameter isn't explicitly set, it defaults to the value of\n"
" the delay parameter.\n"
"4Escaping String Data\n"
" The following character escapes are explicitly supported:\n"
"++\\r Sends the ASCII Carriage Return character (Decimal value 13)\n"
"++\\n Sends the ASCII Linefeed character (Decimal value 10)\n"
"++\\f Sends the ASCII Formfeed character (Decimal value 12)\n"
"++\\t Sends the ASCII Horizontal Tab character (Decimal value 9)\n"
"++\\v Sends the ASCII Vertical Tab character (Decimal value 11)\n"
"++\\b Sends the ASCII Backspace character (Decimal value 8)\n"
"++\\\\ Sends the ASCII Backslash character (Decimal value 92)\n"
"++\\' Sends the ASCII Single Quote character (Decimal value 39)\n"
"++\\\" Sends the ASCII Double Quote character (Decimal value 34)\n"
"++\\? Sends the ASCII Question Mark character (Decimal value 63)\n"
"++\\e Sends the ASCII Escape character (Decimal value 27)\n"
" as well as octal character values of the form:\n"
"++\\n{n{n}} where each n is an octal digit (0-7)\n"
" and hext character values of the form:\n"
"++\\xh{h} where each h is a hex digit (0-9A-Fa-f)\n"
"4Switches\n"
" Switches can be used to influence the behavior of SEND commands\n\n"
"5-t\n"
" The -t switch indicates that the Delay and After values are in\n"
" units of microseconds rather than instructions.\n"
/***************** 80 character line width template *************************/
#define HLP_EXPECT "*Commands Executing_Command_Files Reacting_To_Console_Output"
/***************** 80 character line width template *************************/
"3Reacting To Console Output\n"
" The EXPECT command provides a way to stop execution and take actions\n"
" when specific output has been generated by the simulated system.\n"
"++EXPECT {dev:line} {HALTAFTER=n,}\"<string>\" {actioncommand {; actioncommand}...}\n\n"
" The string argument must be delimited by quote characters. Quotes may\n"
" be either single or double but the opening and closing quote characters\n"
" must match. Data in the string may contain escaped character strings.\n\n"
" When expect rules are defined, they are evaluated agains recently\n"
" produced output as each character is output to the device. Since this\n"
" evaluation processing is done on each output character, rule matching\n"
" is not specifically line oriented. If line oriented matching is desired\n"
" then rules should be defined which contain the simulated system's line\n"
" ending character sequence (i.e. \"\\r\\n\").\n"
" Once data has matched any expect rule, that data is no longer eligible\n"
" to match other expect rules which may already be defined.\n"
" Data which is output prior to the definition of an expect rule is not\n"
" eligible to be matched against.\n"
/***************** 80 character line width template *************************/
"4Switches\n"
" Switches can be used to influence the behavior of EXPECT rules\n\n"
"5-p\n"
" EXPECT rules default to be one shot activities. That is a rule is\n"
" automatically removed when it matches unless it is designated as a\n"
" persistent rule by using a -p switch when the rule is defined.\n"
"5-c\n"
" If an expect rule is defined with the -c switch, it will cause all\n"
" pending expect rules on the current device to be cleared when the rule\n"
" matches data in the device output stream.\n"
"5-r\n"
" If an expect rule is defined with the -r switch, the string is interpreted\n"
" as a regular expression applied to the output data stream. This regular\n"
" expression may contain parentheses delimited sub-groups.\n\n"
/***************** 80 character line width template *************************/
#if defined (HAVE_PCREPOSIX_H)
" The syntax of the regular expressions available are those supported by\n"
" the Perl Compatible Regular Expression package (aka PCRE). As the name\n"
" implies, the syntax is generally the same as Perl regular expressions.\n"
" See http://perldoc.perl.org/perlre.html for more details\n"
#elif defined (HAVE_REGEX_H)
" The syntax of the regular expressions available are those supported by\n"
" your local system's Regular Expression library using the Extended POSIX\n"
" Regular Expressiona\n"
#else
" Regular expression support is not currently available on your environment.\n"
" This simulator could use regular expression support provided by the\n"
" Perl Compatible Regular Expression (PCRE) package if it was available\n"
" when you simulator was compiled.\n"
#endif
"5-i\n"
" If a regular expression expect rule is defined with the -i switch,\n"
" character matching for that expression will be case independent.\n"
" The -i switch is only valid for regular expression expect rules.\n"
"5-t\n"
" The -t switch indicates that the value specified by the HaltAfter\n"
" parameter are in units of microseconds rather than instructions.\n"
"4Determining Which Output Matched\n"
" When an expect rule matches data in the output stream, the rule which\n"
" matched is recorded in the environment variable _EXPECT_MATCH_PATTERN.\n"
" If the expect rule was a regular expression rule, then the environment\n"
" variable _EXPECT_MATCH_GROUP_0 is set to the whole string which matched\n"
" and if the match pattern had any parentheses delimited sub-groups, the\n"
" environment variables _EXPECT_MATCH_PATTERN_1 thru _EXPECT_MATCH_PATTERN_n\n"
" are set to the values within the string which matched the respective\n"
" sub-groups.\n"
/***************** 80 character line width template *************************/
"4Escaping String Data\n"
" The following character escapes are explicitly supported when NOT using\n"
" regular expression match patterns:\n"
"++\\r Expect the ASCII Carriage Return character (Decimal value 13)\n"
"++\\n Expect the ASCII Linefeed character (Decimal value 10)\n"
"++\\f Expect the ASCII Formfeed character (Decimal value 12)\n"
"++\\t Expect the ASCII Horizontal Tab character (Decimal value 9)\n"
"++\\v Expect the ASCII Vertical Tab character (Decimal value 11)\n"
"++\\b Expect the ASCII Backspace character (Decimal value 8)\n"
"++\\\\ Expect the ASCII Backslash character (Decimal value 92)\n"
"++\\' Expect the ASCII Single Quote character (Decimal value 39)\n"
"++\\\" Expect the ASCII Double Quote character (Decimal value 34)\n"
"++\\? Expect the ASCII Question Mark character (Decimal value 63)\n"
"++\\e Expect the ASCII Escape character (Decimal value 27)\n"
" as well as octal character values of the form:\n"
"++\\n{n{n}} where each n is an octal digit (0-7)\n"
" and hext character values of the form:\n"
"++\\xh{h} where each h is a hex digit (0-9A-Fa-f)\n"
"4HaltAfter\n"
" Specifies the number of instructions which should be executed before\n"
" simulator instruction execution should stop. The default is to stop\n"
" executing instructions immediately (i.e. HALTAFTER=0).\n"
" The HaltAfter delay, once set, persists for all expect behaviors for\n"
" that device.\n"
" The HaltAfter parameter value can be set by itself with:\n\n"
"++EXPECT HALTAFTER=n\n\n"
" To avoid potentially unpredictable system hehavior that will happen\n"
" if multiple expect rules are in effect and a haltafter value is large\n"
" enough for more than one expect rule to match before an earlier haltafter\n"
" delay has expired, only a single EXPECT rule can be defined if a non-zero\n"
" HaltAfter parameter has been set.\n"
/***************** 80 character line width template *************************/
#define HLP_ASSERT "*Commands Executing_Command_Files Testing_Simulator_State"
#define HLP_IF "*Commands Executing_Command_Files Testing_Simulator_State"
"3Testing Simulator State\n"
" There are two ways for a command file to examine simulator state and\n"
" then take action based on that state:\n"
"4ASSERT\n"
" The ASSERT command tests a simulator state condition and halts command\n"
" file execution if the condition is false:\n\n"
"++ASSERT <Simulator State Expressions>\n\n"
" If the indicated expression evaluates to false, the command completes\n"
" with an AFAIL condition. By default, when a command file encounters a\n"
" command which returns the AFAIL condition, it will exit the running\n"
" command file with the AFAIL status to the calling command file. This\n"
" behavior can be changed with the ON command as well as switches to the\n"
" invoking DO command.\n\n"
"5Examples:\n"
" A command file might be used to bootstrap an operating system that\n"
" halts after the initial load from disk. The ASSERT command is then\n"
" used to confirm that the load completed successfully by examining the\n"
" CPU's \"A\" register for the expected value:\n\n"
"++; OS bootstrap command file\n"
"++;\n"
"++ATTACH DS0 os.disk\n"
"++BOOT DS\n"
"++; A register contains error code; 0 = good boot\n"
"++ASSERT A=0\n"
"++ATTACH MT0 sys.tape\n"
"++ATTACH MT1 user.tape\n"
"++RUN\n\n"
/***************** 80 character line width template *************************/
" In the example, if the A register is not 0, the \"ASSERT A=0\" command will\n"
" be echoed, the command file will be aborted with an \"Assertion failed\"\n"
" message. Otherwise, the command file will continue to bring up the\n"
" operating system.\n"
"4IF\n"
" The IF command tests a simulator state condition and executes additional\n"
" commands if the condition is true:\n\n"
"++IF <Simulator State Expressions> commandtoprocess{; additionalcommandtoprocess}...\n\n"
"5Examples:\n"
" A command file might be used to bootstrap an operating system that\n"
" halts after the initial load from disk. The ASSERT command is then\n"
" used to confirm that the load completed successfully by examining the\n"
" CPU's \"A\" register for the expected value:\n\n"
"++; OS bootstrap command file\n"
"++;\n"
"++ATTACH DS0 os.disk\n"
"++BOOT DS\n"
"++; A register contains error code; 0 = good boot\n"
"++IF NOT A=0 echo Boot failed - Failure Code; EX A; exit AFAIL\n"
"++ATTACH MT0 sys.tape\n"
"++ATTACH MT1 user.tape\n"
"++RUN\n\n"
/***************** 80 character line width template *************************/
" In the example, if the A register is not 0, the message \"Boot failed -\n"
" Failure Code:\" command will be displayed, the contents of the A register\n"
" will be displayed and the command file will be aborted with an \"Assertion\n"
" failed\" message. Otherwise, the command file will continue to bring up\n"
" the operating system.\n"
"4Conditional Expressions\n"
" The IF and ASSERT commands evaluate two different forms of conditional\n"
" expressions.:\n\n"
"5Simulator State Expressions\n"
" The values of simulator registers can be evaluated with:\n\n"
"++{NOT} {<dev>} <reg>|<addr>{<logical-op><value>}<conditional-op><value>\n\n"
" If <dev> is not specified, CPU is assumed. <reg> is a register (scalar\n"
" or subscripted) belonging to the indicated device. <addr> is an address\n"
" in the address space of the indicated device. The <conditional-op>\n"
" and optional <logical-op> are the same as those used for \"search\n"
" specifiers\" by the EXAMINE and DEPOSIT commands. The <value>s are\n"
" expressed in the radix specified for <reg>, not in the radix for the\n"
" device when referencing a register and when an address is referenced\n"
" the device radix is used as the default.\n\n"
" If the <logical-op> and <value> are specified, the target register value\n"
" is first altered as indicated. The result is then compared to the\n"
" <value> via the <conditional-op>. If the result is true, the additional\n"
" command(s) are executed before proceeding to the next line in the command\n"
" file. Otherwise, the next command in the command file is processed.\n\n"
"5String Comparison Expressions\n"
" String Values can be compared with:\n"
"++{-i} {NOT} \"<string1>\" <compare-op> \"<string2>\"\n\n"
" The -i switch, if present, causes comparisons to be case insensitive.\n"
" <string1> and <string2> are quoted string values which may have\n"
" environment variables substituted as desired.\n"
" <compare-op> may be one of:\n\n"
"++== - equal\n"
"++EQU - equal\n"
"++!= - not equal\n"
"++NEQ - not equal\n"
"++< - less than\n"
"++LSS - less than\n"
"++<= - less than or equal\n"
"++LEQ - less than or equal\n"
"++> - greater than\n"
"++GTR - greater than\n"
"++>= - greater than or equal\n"
"++GEQ - greater than or equal\n\n"
" Comparisons are generic. This means that if both string1 and string2 are\n"
" comprised of all numeric digits, then the strings are converted to numbers\n"
" and a numeric comparison is performed. For example: \"+1\" EQU \"1\" will be\n"
" true.\n"
/***************** 80 character line width template *************************/
#define HLP_EXIT "*Commands Exiting_The_Simulator"
"2Exiting The Simulator\n"
" EXIT (synonyms QUIT and BYE) returns control to the operating system.\n"
#define HLP_SPAWN "*Commands Executing_System_Commands"
"2Executing System Commands\n"
" The simulator can execute operating system commands with the ! (spawn)\n"
" command:\n\n"
"++! execute local command interpreter\n"
"++! <command> execute local host command\n"
" If no operating system command is provided, the simulator attempts to\n"
" launch the host operating system's command shell.\n"
" The exit status from the command which was executed is set as the command\n"
" completion status for the ! command. This may influence any enabled ON\n"
" condition traps\n";
static CTAB cmd_table[] = {
{ "RESET", &reset_cmd, 0, HLP_RESET },
{ "EXAMINE", &exdep_cmd, EX_E, HLP_EXAMINE },
{ "IEXAMINE", &exdep_cmd, EX_E+EX_I, HLP_IEXAMINE },
{ "DEPOSIT", &exdep_cmd, EX_D, HLP_DEPOSIT },
{ "IDEPOSIT", &exdep_cmd, EX_D+EX_I, HLP_IDEPOSIT },
{ "EVALUATE", &eval_cmd, 0, HLP_EVALUATE },
{ "RUN", &run_cmd, RU_RUN, HLP_RUN, NULL, &run_cmd_message },
{ "GO", &run_cmd, RU_GO, HLP_GO, NULL, &run_cmd_message },
{ "STEP", &run_cmd, RU_STEP, HLP_STEP, NULL, &run_cmd_message },
{ "NEXT", &run_cmd, RU_NEXT, HLP_NEXT, NULL, &run_cmd_message },
{ "CONTINUE", &run_cmd, RU_CONT, HLP_CONTINUE, NULL, &run_cmd_message },
{ "BOOT", &run_cmd, RU_BOOT, HLP_BOOT, NULL, &run_cmd_message },
{ "BREAK", &brk_cmd, SSH_ST, HLP_BREAK },
{ "NOBREAK", &brk_cmd, SSH_CL, HLP_NOBREAK },
{ "ATTACH", &attach_cmd, 0, HLP_ATTACH },
{ "DETACH", &detach_cmd, 0, HLP_DETACH },
{ "ASSIGN", &assign_cmd, 0, HLP_ASSIGN },
{ "DEASSIGN", &deassign_cmd, 0, HLP_DEASSIGN },
{ "SAVE", &save_cmd, 0, HLP_SAVE },
{ "RESTORE", &restore_cmd, 0, HLP_RESTORE },
{ "GET", &restore_cmd, 0, NULL },
{ "LOAD", &load_cmd, 0, HLP_LOAD },
{ "DUMP", &load_cmd, 1, HLP_DUMP },
{ "EXIT", &exit_cmd, 0, HLP_EXIT },
{ "QUIT", &exit_cmd, 0, NULL },
{ "BYE", &exit_cmd, 0, NULL },
{ "CD", &set_default_cmd, 0, HLP_CD },
{ "PWD", &pwd_cmd, 0, HLP_PWD },
{ "DIR", &dir_cmd, 0, HLP_DIR },
{ "LS", &dir_cmd, 0, HLP_LS },
{ "SET", &set_cmd, 0, HLP_SET },
{ "SHOW", &show_cmd, 0, HLP_SHOW },
{ "DO", &do_cmd, 1, HLP_DO },
{ "GOTO", &goto_cmd, 1, HLP_GOTO },
{ "RETURN", &return_cmd, 0, HLP_RETURN },
{ "SHIFT", &shift_cmd, 0, HLP_SHIFT },
{ "CALL", &call_cmd, 0, HLP_CALL },
{ "ON", &on_cmd, 0, HLP_ON },
{ "IF", &assert_cmd, 0, HLP_IF },
{ "PROCEED", &noop_cmd, 0, HLP_PROCEED },
{ "IGNORE", &noop_cmd, 0, HLP_IGNORE },
{ "ECHO", &echo_cmd, 0, HLP_ECHO },
{ "ASSERT", &assert_cmd, 1, HLP_ASSERT },
{ "SEND", &send_cmd, 0, HLP_SEND },
{ "EXPECT", &expect_cmd, 1, HLP_EXPECT },
{ "NOEXPECT", &expect_cmd, 0, HLP_EXPECT },
{ "!", &spawn_cmd, 0, HLP_SPAWN },
{ "HELP", &help_cmd, 0, HLP_HELP },
{ NULL, NULL, 0 }
};
static CTAB set_glob_tab[] = {
{ "CONSOLE", &sim_set_console, 0, HLP_SET_CONSOLE },
{ "REMOTE", &sim_set_remote_console, 0, HLP_SET_REMOTE },
{ "BREAK", &brk_cmd, SSH_ST, HLP_SET_BREAK },
{ "NOBREAK", &brk_cmd, SSH_CL, HLP_SET_BREAK },
{ "DEFAULT", &set_default_cmd, 1, HLP_SET_DEFAULT },
{ "TELNET", &sim_set_telnet, 0 }, /* deprecated */
{ "NOTELNET", &sim_set_notelnet, 0 }, /* deprecated */
{ "LOG", &sim_set_logon, 0, HLP_SET_LOG },
{ "NOLOG", &sim_set_logoff, 0, HLP_SET_LOG },
{ "DEBUG", &sim_set_debon, 0, HLP_SET_DEBUG },
{ "NODEBUG", &sim_set_deboff, 0, HLP_SET_DEBUG },
{ "THROTTLE", &sim_set_throt, 1, HLP_SET_THROTTLE },
{ "NOTHROTTLE", &sim_set_throt, 0, HLP_SET_THROTTLE },
{ "ASYNCH", &sim_set_asynch, 1, HLP_SET_ASYNCH },
{ "NOASYNCH", &sim_set_asynch, 0, HLP_SET_ASYNCH },
{ "ENVIRONMENT", &sim_set_environment, 1, HLP_SET_ENVIRON },
{ "ON", &set_on, 1, HLP_SET_ON },
{ "NOON", &set_on, 0, HLP_SET_ON },
{ "VERIFY", &set_verify, 1, HLP_SET_VERIFY },
{ "VEBOSE", &set_verify, 1, HLP_SET_VERIFY },
{ "NOVERIFY", &set_verify, 0, HLP_SET_VERIFY },
{ "NOVEBOSE", &set_verify, 0, HLP_SET_VERIFY },
{ "MESSAGE", &set_message, 1, HLP_SET_MESSAGE },
{ "NOMESSAGE", &set_message, 0, HLP_SET_MESSAGE },
{ "QUIET", &set_quiet, 1, HLP_SET_QUIET },
{ "NOQUIET", &set_quiet, 0, HLP_SET_QUIET },
{ "PROMPT", &set_prompt, 0, HLP_SET_PROMPT },
{ NULL, NULL, 0 }
};
static C1TAB set_dev_tab[] = {
{ "OCTAL", &set_dev_radix, 8 },
{ "DECIMAL", &set_dev_radix, 10 },
{ "HEX", &set_dev_radix, 16 },
{ "ENABLED", &set_dev_enbdis, 1 },
{ "DISABLED", &set_dev_enbdis, 0 },
{ "DEBUG", &set_dev_debug, 1 },
{ "NODEBUG", &set_dev_debug, 0 },
{ NULL, NULL, 0 }
};
static C1TAB set_unit_tab[] = {
{ "ENABLED", &set_unit_enbdis, 1 },
{ "DISABLED", &set_unit_enbdis, 0 },
{ NULL, NULL, 0 }
};
static SHTAB show_glob_tab[] = {
{ "CONFIGURATION", &show_config, 0, HLP_SHOW_CONFIG },
{ "DEVICES", &show_config, 1, HLP_SHOW_DEVICES },
{ "FEATURES", &show_config, 2, HLP_SHOW_FEATURES },
{ "QUEUE", &show_queue, 0, HLP_SHOW_QUEUE },
{ "TIME", &show_time, 0, HLP_SHOW_TIME },
{ "MODIFIERS", &show_mod_names, 0, HLP_SHOW_MODIFIERS },
{ "NAMES", &show_log_names, 0, HLP_SHOW_NAMES },
{ "SHOW", &show_show_commands, 0, HLP_SHOW_SHOW },
{ "VERSION", &show_version, 1, HLP_SHOW_VERSION },
{ "DEFAULT", &show_default, 0, HLP_SHOW_DEFAULT },
{ "CONSOLE", &sim_show_console, 0, HLP_SHOW_CONSOLE },
{ "REMOTE", &sim_show_remote_console, 0, HLP_SHOW_REMOTE },
{ "BREAK", &show_break, 0, HLP_SHOW_BREAK },
{ "LOG", &sim_show_log, 0, HLP_SHOW_LOG },
{ "TELNET", &sim_show_telnet, 0 }, /* deprecated */
{ "DEBUG", &sim_show_debug, 0, HLP_SHOW_DEBUG },
{ "THROTTLE", &sim_show_throt, 0, HLP_SHOW_THROTTLE },
{ "ASYNCH", &sim_show_asynch, 0, HLP_SHOW_ASYNCH },
{ "ETHERNET", &eth_show_devices, 0, HLP_SHOW_ETHERNET },
{ "SERIAL", &sim_show_serial, 0, HLP_SHOW_SERIAL },
{ "MULTIPLEXER", &tmxr_show_open_devices, 0, HLP_SHOW_MULTIPLEXER },
{ "MUX", &tmxr_show_open_devices, 0, HLP_SHOW_MULTIPLEXER },
{ "CLOCKS", &sim_show_timers, 0, HLP_SHOW_CLOCKS },
{ "SEND", &sim_show_send, 0, HLP_SHOW_SEND },
{ "EXPECT", &sim_show_expect, 0, HLP_SHOW_EXPECT },
{ "ON", &show_on, 0, HLP_SHOW_ON },
{ NULL, NULL, 0 }
};
static SHTAB show_dev_tab[] = {
{ "RADIX", &show_dev_radix, 0 },
{ "DEBUG", &show_dev_debug, 0 },
{ "MODIFIERS", &show_dev_modifiers, 0 },
{ "NAMES", &show_dev_logicals, 0 },
{ "SHOW", &show_dev_show_commands, 0 },
{ NULL, NULL, 0 }
};
static SHTAB show_unit_tab[] = {
{ NULL, NULL, 0 }
};
#if defined(_WIN32) || defined(__hpux)
static
int setenv(const char *envname, const char *envval, int overwrite)
{
char *envstr = (char *)malloc(strlen(envname)+strlen(envval)+2);
int r;
sprintf(envstr, "%s=%s", envname, envval);
#if defined(_WIN32)
r = _putenv(envstr);
free(envstr);
#else
r = putenv(envstr);
#endif
return r;
}
static
int unsetenv(const char *envname)
{
setenv(envname, "", 1);
return 0;
}
#endif
t_stat process_stdin_commands (t_stat stat, char *argv[]);
/* Main command loop */
int main (int argc, char *argv[])
{
char cbuf[4*CBUFSIZE], *cptr, *cptr2;
char nbuf[PATH_MAX + 7];
char **targv = NULL;
int32 i, sw;
t_bool lookswitch;
t_stat stat;
#if defined (__MWERKS__) && defined (macintosh)
argc = ccommand (&argv);
#endif
/* Make sure that argv has at least 10 elements and that it ends in a NULL pointer */
targv = calloc (1+MAX(10, argc), sizeof(*targv));
for (i=0; i<argc; i++)
targv[i] = argv[i];
argv = targv;
set_prompt (0, "sim>"); /* start with set standard prompt */
*cbuf = 0; /* init arg buffer */
sim_switches = 0; /* init switches */
lookswitch = TRUE;
stdnul = fopen(NULL_DEVICE,"wb");
for (i = 1; i < argc; i++) { /* loop thru args */
if (argv[i] == NULL) /* paranoia */
continue;
if ((*argv[i] == '-') && lookswitch) { /* switch? */
if ((sw = get_switches (argv[i])) < 0) {
fprintf (stderr, "Invalid switch %s\n", argv[i]);
return 0;
}
sim_switches = sim_switches | sw;
}
else {
if ((strlen (argv[i]) + strlen (cbuf) + 3) >= sizeof(cbuf)) {
fprintf (stderr, "Argument string too long\n");
return 0;
}
if (*cbuf) /* concat args */
strcat (cbuf, " ");
sprintf(&cbuf[strlen(cbuf)], "%s%s%s", strchr(argv[i], ' ') ? "\"" : "", argv[i], strchr(argv[i], ' ') ? "\"" : "");
lookswitch = FALSE; /* no more switches */
}
} /* end for */
sim_quiet = sim_switches & SWMASK ('Q'); /* -q means quiet */
sim_on_inherit = sim_switches & SWMASK ('O'); /* -o means inherit on state */
sim_init_sock (); /* init socket capabilities */
AIO_INIT; /* init Asynch I/O */
if (sim_vm_init != NULL) /* call once only */
(*sim_vm_init)();
sim_finit (); /* init fio package */
setenv ("SIM_NAME", sim_name, 1); /* Publish simulator name */
stop_cpu = 0;
sim_interval = 0;
sim_time = sim_rtime = 0;
noqueue_time = 0;
sim_clock_queue = QUEUE_LIST_END;
sim_is_running = 0;
sim_log = NULL;
if (sim_emax <= 0)
sim_emax = 1;
sim_timer_init ();
if ((stat = sim_ttinit ()) != SCPE_OK) {
fprintf (stderr, "Fatal terminal initialization error\n%s\n",
sim_error_text (stat));
return 0;
}
if ((sim_eval = (t_value *) calloc (sim_emax, sizeof (t_value))) == NULL) {
fprintf (stderr, "Unable to allocate examine buffer\n");
return 0;
};
if ((stat = reset_all_p (0)) != SCPE_OK) {
fprintf (stderr, "Fatal simulator initialization error\n%s\n",
sim_error_text (stat));
return 0;
}
if ((stat = sim_brk_init ()) != SCPE_OK) {
fprintf (stderr, "Fatal breakpoint table initialization error\n%s\n",
sim_error_text (stat));
return 0;
}
if (!sim_quiet) {
printf ("\n");
show_version (stdout, NULL, NULL, 0, NULL);
}
if (sim_dflt_dev == NULL) /* if no default */
sim_dflt_dev = sim_devices[0];
sim_argv = argv;
cptr = getenv("HOME");
if (cptr == NULL) {
cptr = getenv("HOMEPATH");
cptr2 = getenv("HOMEDRIVE");
}
else
cptr2 = NULL;
if (cptr && sizeof (nbuf) > strlen (cptr) + strlen ("/simh.ini") + 1) {
sprintf(nbuf, "\"%s%s%ssimh.ini\"", cptr2 ? cptr2 : "", cptr, strchr (cptr, '/') ? "/" : "\\");
stat = do_cmd (-1, nbuf) & ~SCPE_NOMESSAGE; /* simh.ini proc cmd file */
}
if (stat == SCPE_OPENERR)
stat = do_cmd (-1, "simh.ini"); /* simh.ini proc cmd file */
if (*cbuf) /* cmd file arg? */
stat = do_cmd (0, cbuf); /* proc cmd file */
else if (*argv[0]) { /* sim name arg? */
char *np; /* "path.ini" */
nbuf[0] = '"'; /* starting " */
strncpy (nbuf + 1, argv[0], PATH_MAX + 1); /* copy sim name */
if ((np = match_ext (nbuf, "EXE"))) /* remove .exe */
*np = 0;
strcat (nbuf, ".ini\""); /* add .ini" */
stat = do_cmd (-1, nbuf) & ~SCPE_NOMESSAGE; /* proc default cmd file */
if (stat == SCPE_OPENERR) { /* didn't exist/can't open? */
np = strrchr (nbuf, '/'); /* stript path and try again in cwd */
if (np == NULL)
np = strrchr (nbuf, '\\'); /* windows path separator */
if (np == NULL)
np = strrchr (nbuf, ']'); /* VMS path separator */
if (np != NULL) {
*np = '"';
stat = do_cmd (-1, np) & ~SCPE_NOMESSAGE; /* proc default cmd file */
}
}
}
stat = process_stdin_commands (SCPE_BARE_STATUS(stat), argv);
detach_all (0, TRUE); /* close files */
sim_set_deboff (0, NULL); /* close debug */
sim_set_logoff (0, NULL); /* close log */
sim_set_notelnet (0, NULL); /* close Telnet */
sim_ttclose (); /* close console */
AIO_CLEANUP; /* Asynch I/O */
sim_cleanup_sock (); /* cleanup sockets */
fclose (stdnul); /* close bit bucket file handle */
free (targv); /* release any argv copy that was made */
return 0;
}
t_stat process_stdin_commands (t_stat stat, char *argv[])
{
char cbuf[4*CBUFSIZE], gbuf[CBUFSIZE], *cptr;
t_stat stat_nomessage;
CTAB *cmdp;
stat = SCPE_BARE_STATUS(stat); /* remove possible flag */
while (stat != SCPE_EXIT) { /* in case exit */
if ((cptr = sim_brk_getact (cbuf, sizeof(cbuf)))) /* pending action? */
printf ("%s%s\n", sim_prompt, cptr); /* echo */
else if (sim_vm_read != NULL) { /* sim routine? */
printf ("%s", sim_prompt); /* prompt */
cptr = (*sim_vm_read) (cbuf, sizeof(cbuf), stdin);
}
else cptr = read_line_p (sim_prompt, cbuf, sizeof(cbuf), stdin);/* read with prmopt*/
if (cptr == NULL) { /* EOF? */
if (sim_ttisatty()) continue; /* ignore tty EOF */
else break; /* otherwise exit */
}
if (*cptr == 0) /* ignore blank */
continue;
sim_sub_args (cbuf, sizeof(cbuf), argv);
if (sim_log) /* log cmd */
fprintf (sim_log, "%s%s\n", sim_prompt, cptr);
if (sim_deb && (sim_deb != sim_log) && (sim_deb != stdout))
fprintf (sim_deb, "%s%s\n", sim_prompt, cptr);
cptr = get_glyph (cptr, gbuf, 0); /* get command glyph */
sim_switches = 0; /* init switches */
if ((cmdp = find_cmd (gbuf))) /* lookup command */
stat = cmdp->action (cmdp->arg, cptr); /* if found, exec */
else stat = SCPE_UNK;
stat_nomessage = stat & SCPE_NOMESSAGE; /* extract possible message supression flag */
stat_nomessage = stat_nomessage || (!sim_show_message);/* Apply global suppression */
stat = SCPE_BARE_STATUS(stat); /* remove possible flag */
sim_last_cmd_stat = stat; /* save command error status */
if (!stat_nomessage) { /* displaying message status? */
if (cmdp && (cmdp->message)) /* special message handler? */
cmdp->message (NULL, stat); /* let it deal with display */
else
if (stat >= SCPE_BASE) /* error? */
sim_printf ("%s\n", sim_error_text (stat));
}
if (sim_vm_post != NULL)
(*sim_vm_post) (TRUE);
} /* end while */
return stat;
}
/* Set prompt routine */
t_stat set_prompt (int32 flag, char *cptr)
{
char gbuf[CBUFSIZE];
if ((!cptr) || (*cptr == '\0'))
return SCPE_ARG;
cptr = get_glyph_nc (cptr, gbuf, '"'); /* get quote delimited token */
if (gbuf[0] == '\0') { /* Token started with quote */
gbuf[sizeof (gbuf)-1] = '\0';
strncpy (gbuf, cptr, sizeof (gbuf)-1);
cptr = strchr (gbuf, '"');
if (cptr)
*cptr = '\0';
}
sim_prompt = (char *)realloc (sim_prompt, strlen (gbuf) + 2); /* nul terminator and trailing blank */
sprintf (sim_prompt, "%s ", gbuf);
return SCPE_OK;
}
/* Find command routine */
CTAB *find_cmd (const char *gbuf)
{
CTAB *cmdp = NULL;
if (sim_vm_cmd) /* try ext commands */
cmdp = find_ctab (sim_vm_cmd, gbuf);
if (cmdp == NULL) /* try regular cmds */
cmdp = find_ctab (cmd_table, gbuf);
return cmdp;
}
/* Exit command */
t_stat exit_cmd (int32 flag, char *cptr)
{
return SCPE_EXIT;
}
/* Help command */
/* Used when sorting a list of command names */
static int _cmd_name_compare (const void *pa, const void *pb)
{
CTAB **a = (CTAB **)pa;
CTAB **b = (CTAB **)pb;
return strcmp((*a)->name, (*b)->name);
}
void fprint_help (FILE *st)
{
CTAB *cmdp;
CTAB **hlp_cmdp = NULL;
int cmd_cnt = 0;
int cmd_size = 0;
size_t max_cmdname_size = 0;
int i, line_offset;
for (cmdp = sim_vm_cmd; cmdp && (cmdp->name != NULL); cmdp++) {
if (cmdp->help) {
if (cmd_cnt >= cmd_size) {
cmd_size += 20;
hlp_cmdp = realloc (hlp_cmdp, sizeof(*hlp_cmdp)*cmd_size);
}
hlp_cmdp[cmd_cnt] = cmdp;
++cmd_cnt;
if (strlen(cmdp->name) > max_cmdname_size)
max_cmdname_size = strlen(cmdp->name);
}
}
for (cmdp = cmd_table; cmdp && (cmdp->name != NULL); cmdp++) {
if (cmdp->help && (!sim_vm_cmd || !find_ctab (sim_vm_cmd, cmdp->name))) {
if (cmd_cnt >= cmd_size) {
cmd_size += 20;
hlp_cmdp = realloc (hlp_cmdp, sizeof(*hlp_cmdp)*cmd_size);
}
hlp_cmdp[cmd_cnt] = cmdp;
++cmd_cnt;
if (strlen (cmdp->name) > max_cmdname_size)
max_cmdname_size = strlen(cmdp->name);
}
}
fprintf (st, "Help is available for the following commands:\n\n ");
qsort (hlp_cmdp, cmd_cnt, sizeof(*hlp_cmdp), _cmd_name_compare);
line_offset = 4;
for (i=0; i<cmd_cnt; ++i) {
fputs (hlp_cmdp[i]->name, st);
line_offset += 5 + max_cmdname_size;
if (line_offset + max_cmdname_size > 79) {
line_offset = 4;
fprintf (st, "\n ");
}
else
fprintf (st, "%*s", (int)(max_cmdname_size + 5 - strlen (hlp_cmdp[i]->name)), "");
}
free (hlp_cmdp);
fprintf (st, "\n");
return;
}
static void fprint_header (FILE *st, t_bool *pdone, char *context)
{
if (!*pdone)
fprintf (st, "%s", context);
*pdone = TRUE;
}
void fprint_reg_help_ex (FILE *st, DEVICE *dptr, t_bool silent)
{
REG *rptr, *trptr;
t_bool found = FALSE;
t_bool all_unique = TRUE;
size_t max_namelen = 0;
DEVICE *tdptr;
const char *tptr;
char *namebuf;
char rangebuf[32];
if (dptr->registers)
for (rptr = dptr->registers; rptr->name != NULL; rptr++) {
if (rptr->flags & REG_HIDDEN)
continue;
if (rptr->depth > 1)
sprintf (rangebuf, "[%d:%d]", 0, rptr->depth-1);
else
strcpy (rangebuf, "");
if (max_namelen < (strlen(rptr->name) + strlen (rangebuf)))
max_namelen = strlen(rptr->name) + strlen (rangebuf);
found = TRUE;
trptr = find_reg_glob (rptr->name, &tptr, &tdptr);
if ((trptr == NULL) || (tdptr != dptr))
all_unique = FALSE;
}
if (!found) {
if (!silent)
fprintf (st, "No register help is available for the %s device\n", dptr->name);
}
else {
namebuf = (char *)calloc (max_namelen + 1, sizeof (*namebuf));
fprintf (st, "\nThe %s device implements these registers:\n\n", dptr->name);
for (rptr = dptr->registers; rptr->name != NULL; rptr++) {
if (rptr->flags & REG_HIDDEN)
continue;
if (rptr->depth <= 1)
sprintf (namebuf, "%*s", -((int)max_namelen), rptr->name);
else {
sprintf (rangebuf, "[%d:%d]", 0, rptr->depth-1);
sprintf (namebuf, "%s%*s", rptr->name, (int)(strlen(rptr->name))-((int)max_namelen), rangebuf);
}
if (all_unique) {
fprintf (st, " %s %4d %s\n", namebuf, rptr->width, rptr->desc ? rptr->desc : "");
continue;
}
trptr = find_reg_glob (rptr->name, &tptr, &tdptr);
if ((trptr == NULL) || (tdptr != dptr))
fprintf (st, " %s %s %4d %s\n", dptr->name, namebuf, rptr->width, rptr->desc ? rptr->desc : "");
else
fprintf (st, " %*s %s %4d %s\n", (int)strlen(dptr->name), "", namebuf, rptr->width, rptr->desc ? rptr->desc : "");
}
free (namebuf);
}
}
void fprint_reg_help (FILE *st, DEVICE *dptr)
{
fprint_reg_help_ex (st, dptr, TRUE);
}
void fprint_attach_help_ex (FILE *st, DEVICE *dptr, t_bool silent)
{
if (dptr->attach_help) {
fprintf (st, "\n%s device attach commands:\n\n", dptr->name);
dptr->attach_help (st, dptr, NULL, 0, NULL);
return;
}
if (DEV_TYPE(dptr) == DEV_MUX) {
fprintf (st, "\n%s device attach commands:\n\n", dptr->name);
tmxr_attach_help (st, dptr, NULL, 0, NULL);
return;
}
if (DEV_TYPE(dptr) == DEV_DISK) {
fprintf (st, "\n%s device attach commands:\n\n", dptr->name);
sim_disk_attach_help (st, dptr, NULL, 0, NULL);
return;
}
if (DEV_TYPE(dptr) == DEV_TAPE) {
fprintf (st, "\n%s device attach commands:\n\n", dptr->name);
sim_tape_attach_help (st, dptr, NULL, 0, NULL);
return;
}
if (DEV_TYPE(dptr) == DEV_ETHER) {
fprintf (st, "\n%s device attach commands:\n\n", dptr->name);
eth_attach_help (st, dptr, NULL, 0, NULL);
return;
}
if (!silent) {
fprintf (st, "No ATTACH help is available for the %s device\n", dptr->name);
if (dptr->help)
dptr->help (st, dptr, NULL, 0, NULL);
}
}
void fprint_set_help_ex (FILE *st, DEVICE *dptr, t_bool silent)
{
MTAB *mptr;
DEBTAB *dep;
t_bool found = FALSE;
char buf[CBUFSIZE], header[CBUFSIZE];
sprintf (header, "\n%s device SET commands:\n\n", dptr->name);
if (dptr->modifiers) {
for (mptr = dptr->modifiers; mptr->mask != 0; mptr++) {
if (!MODMASK(mptr,MTAB_VDV) && MODMASK(mptr,MTAB_VUN) && (dptr->numunits != 1))
continue; /* skip unit only extended modifiers */
if ((dptr->numunits != 1) && !(mptr->mask & MTAB_XTD))
continue; /* skip unit only simple modifiers */
if (mptr->mstring) {
fprint_header (st, &found, header);
sprintf (buf, "set %s %s%s", sim_dname (dptr), mptr->mstring, (strchr(mptr->mstring, '=')) ? "" : (MODMASK(mptr,MTAB_VALR) ? "=val" : (MODMASK(mptr,MTAB_VALO) ? "{=val}" : "")));
fprintf (st, "%-30s\t%s\n", buf, (strchr(mptr->mstring, '=')) ? "" : (mptr->help ? mptr->help : ""));
}
}
}
if (dptr->flags & DEV_DISABLE) {
fprint_header (st, &found, header);
sprintf (buf, "set %s ENABLE", sim_dname (dptr));
fprintf (st, "%-30s\tEnables device %s\n", buf, sim_dname (dptr));
sprintf (buf, "set %s DISABLE", sim_dname (dptr));
fprintf (st, "%-30s\tDisables device %s\n", buf, sim_dname (dptr));
}
if (dptr->flags & DEV_DEBUG) {
fprint_header (st, &found, header);
sprintf (buf, "set %s DEBUG", sim_dname (dptr));
fprintf (st, "%-30s\tEnables debugging for device %s\n", buf, sim_dname (dptr));
sprintf (buf, "set %s NODEBUG", sim_dname (dptr));
fprintf (st, "%-30s\tDisables debugging for device %s\n", buf, sim_dname (dptr));
if (dptr->debflags) {
strcpy (buf, "");
fprintf (st, "set %s DEBUG=", sim_dname (dptr));
for (dep = dptr->debflags; dep->name != NULL; dep++)
fprintf (st, "%s%s", ((dep == dptr->debflags) ? "" : ";"), dep->name);
fprintf (st, "\n");
fprintf (st, "%-30s\tEnables specific debugging for device %s\n", buf, sim_dname (dptr));
fprintf (st, "set %s NODEBUG=", sim_dname (dptr));
for (dep = dptr->debflags; dep->name != NULL; dep++)
fprintf (st, "%s%s", ((dep == dptr->debflags) ? "" : ";"), dep->name);
fprintf (st, "\n");
fprintf (st, "%-30s\tDisables specific debugging for device %s\n", buf, sim_dname (dptr));
}
}
if ((dptr->modifiers) && (dptr->units) && (dptr->numunits != 1)) {
if (dptr->units->flags & UNIT_DISABLE) {
fprint_header (st, &found, header);
sprintf (buf, "set %sn ENABLE", sim_dname (dptr));
fprintf (st, "%-30s\tEnables unit %sn\n", buf, sim_dname (dptr));
sprintf (buf, "set %sn DISABLE", sim_dname (dptr));
fprintf (st, "%-30s\tDisables unit %sn\n", buf, sim_dname (dptr));
}
for (mptr = dptr->modifiers; mptr->mask != 0; mptr++) {
if ((!MODMASK(mptr,MTAB_VUN)) && MODMASK(mptr,MTAB_XTD))
continue; /* skip device only modifiers */
if ((!mptr->valid) && MODMASK(mptr,MTAB_XTD))
continue; /* skip show only modifiers */
if (mptr->mstring) {
fprint_header (st, &found, header);
sprintf (buf, "set %s%s %s%s", sim_dname (dptr), (dptr->numunits > 1) ? "n" : "0", mptr->mstring, (strchr(mptr->mstring, '=')) ? "" : (MODMASK(mptr,MTAB_VALR) ? "=val" : (MODMASK(mptr,MTAB_VALO) ? "{=val}": "")));
fprintf (st, "%-30s\t%s\n", buf, (strchr(mptr->mstring, '=')) ? "" : (mptr->help ? mptr->help : ""));
}
}
}
if (!found && !silent)
fprintf (st, "No SET help is available for the %s device\n", dptr->name);
}
void fprint_set_help (FILE *st, DEVICE *dptr)
{
fprint_set_help_ex (st, dptr, TRUE);
}
void fprint_show_help_ex (FILE *st, DEVICE *dptr, t_bool silent)
{
MTAB *mptr;
t_bool found = FALSE;
char buf[CBUFSIZE], header[CBUFSIZE];
sprintf (header, "\n%s device SHOW commands:\n\n", dptr->name);
if (dptr->modifiers) {
for (mptr = dptr->modifiers; mptr->mask != 0; mptr++) {
if (!MODMASK(mptr,MTAB_VDV) && MODMASK(mptr,MTAB_VUN) && (dptr->numunits != 1))
continue; /* skip unit only extended modifiers */
if ((dptr->numunits != 1) && !(mptr->mask & MTAB_XTD))
continue; /* skip unit only simple modifiers */
if ((!mptr->disp) || (!mptr->pstring) || !(*mptr->pstring))
continue;
fprint_header (st, &found, header);
sprintf (buf, "show %s %s%s", sim_dname (dptr), mptr->pstring, MODMASK(mptr,MTAB_SHP) ? "=arg" : "");
fprintf (st, "%-30s\t%s\n", buf, mptr->help ? mptr->help : "");
}
}
if (dptr->flags & DEV_DEBUG) {
fprint_header (st, &found, header);
sprintf (buf, "show %s DEBUG", sim_dname (dptr));
fprintf (st, "%-30s\tDisplays debugging status for device %s\n", buf, sim_dname (dptr));
}
if ((dptr->modifiers) && (dptr->units) && (dptr->numunits != 1)) {
for (mptr = dptr->modifiers; mptr->mask != 0; mptr++) {
if ((!MODMASK(mptr,MTAB_VUN)) && MODMASK(mptr,MTAB_XTD))
continue; /* skip device only modifiers */
if ((!mptr->disp) || (!mptr->pstring))
continue;
fprint_header (st, &found, header);
sprintf (buf, "show %s%s %s%s", sim_dname (dptr), (dptr->numunits > 1) ? "n" : "0", mptr->pstring, MODMASK(mptr,MTAB_SHP) ? "=arg" : "");
fprintf (st, "%-30s\t%s\n", buf, mptr->help ? mptr->help : "");
}
}
if (!found && !silent)
fprintf (st, "No SHOW help is available for the %s device\n", dptr->name);
}
void fprint_show_help (FILE *st, DEVICE *dptr)
{
fprint_show_help_ex (st, dptr, TRUE);
}
t_stat help_dev_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
char gbuf[CBUFSIZE];
CTAB *cmdp;
if (*cptr) {
char *gptr = get_glyph (cptr, gbuf, 0);
if ((cmdp = find_cmd (gbuf))) {
if (cmdp->action == &exdep_cmd) {
if (dptr->help) /* Shouldn't this pass cptr so the device knows which command invoked? */
return dptr->help (st, dptr, uptr, flag, gptr);
else
fprintf (st, "No help available for the %s %s command\n", cmdp->name, sim_dname(dptr));
return SCPE_OK;
}
if (cmdp->action == &set_cmd) {
fprint_set_help_ex (st, dptr, FALSE);
return SCPE_OK;
}
if (cmdp->action == &show_cmd) {
fprint_show_help_ex (st, dptr, FALSE);
return SCPE_OK;
}
if (cmdp->action == &attach_cmd) {
fprint_attach_help_ex (st, dptr, FALSE);
return SCPE_OK;
}
if (dptr->help)
return dptr->help (st, dptr, uptr, flag, cptr);
fprintf (st, "No %s help is available for the %s device\n", cmdp->name, dptr->name);
return SCPE_OK;
}
if (MATCH_CMD (gbuf, "REGISTERS") == 0) {
fprint_reg_help_ex (st, dptr, FALSE);
return SCPE_OK;
}
if (dptr->help)
return dptr->help (st, dptr, uptr, flag, cptr);
fprintf (st, "No %s help is available for the %s device\n", gbuf, dptr->name);
return SCPE_OK;
}
if (dptr->help) {
return dptr->help (st, dptr, uptr, flag, cptr);
}
if (dptr->description)
fprintf (st, "%s %s help\n", dptr->description (dptr), dptr->name);
else
fprintf (st, "%s help\n", dptr->name);
fprint_set_help_ex (st, dptr, TRUE);
fprint_show_help_ex (st, dptr, TRUE);
fprint_attach_help_ex (st, dptr, TRUE);
fprint_reg_help_ex (st, dptr, TRUE);
return SCPE_OK;
}
t_stat help_cmd_output (int32 flag, const char *help, const char *help_base)
{
switch (help[0]) {
case '*':
scp_help (stdout, NULL, NULL, flag, help_base ? help_base : simh_help, help+1);
if (sim_log)
scp_help (sim_log, NULL, NULL, flag | SCP_HELP_FLAT, help_base ? help_base : simh_help, help+1);
break;
default:
fputs (help, stdout);
if (sim_log)
fputs (help, sim_log);
break;
}
return SCPE_OK;
}
t_stat help_cmd (int32 flag, char *cptr)
{
char gbuf[CBUFSIZE];
CTAB *cmdp;
GET_SWITCHES (cptr);
if (sim_switches & SWMASK ('F'))
flag = flag | SCP_HELP_FLAT;
if (*cptr) {
cptr = get_glyph (cptr, gbuf, 0);
if ((cmdp = find_cmd (gbuf))) {
if (*cptr) {
if ((cmdp->action == &set_cmd) || (cmdp->action == &show_cmd)) {
DEVICE *dptr;
UNIT *uptr;
t_stat r;
cptr = get_glyph (cptr, gbuf, 0);
dptr = find_unit (gbuf, &uptr);
if (dptr == NULL)
dptr = find_dev (gbuf);
if (dptr != NULL) {
r = help_dev_help (stdout, dptr, uptr, flag, (cmdp->action == &set_cmd) ? "SET" : "SHOW");
if (sim_log)
help_dev_help (sim_log, dptr, uptr, flag | SCP_HELP_FLAT, (cmdp->action == &set_cmd) ? "SET" : "SHOW");
return r;
}
if (cmdp->action == &set_cmd) { /* HELP SET xxx (not device or unit) */
if ((cmdp = find_ctab (set_glob_tab, gbuf)) &&
(cmdp->help))
return help_cmd_output (flag, cmdp->help, cmdp->help_base);
}
else { /* HELP SHOW xxx (not device or unit) */
SHTAB *shptr = find_shtab (show_glob_tab, gbuf);
if ((shptr == NULL) || (shptr->help == NULL) || (*shptr->help == '\0'))
return SCPE_ARG;
return help_cmd_output (flag, shptr->help, NULL);
}
return SCPE_ARG;
}
else
return SCPE_2MARG;
}
if (cmdp->help) {
if (strcmp (cmdp->name, "HELP") == 0) {
DEVICE *dptr;
int i;
for (i = 0; (dptr = sim_devices[i]) != NULL; i++) {
if (dptr->help)
sim_printf ("h{elp} %-17s display help for device %s\n", dptr->name, dptr->name);
if (dptr->attach_help ||
(DEV_TYPE(dptr) == DEV_MUX) ||
(DEV_TYPE(dptr) == DEV_DISK) ||
(DEV_TYPE(dptr) == DEV_TAPE)) {
sim_printf ("h{elp} %s ATTACH\t display help for device %s ATTACH command\n", dptr->name, dptr->name);
}
if (dptr->registers) {
if (dptr->registers->name != NULL)
sim_printf ("h{elp} %s REGISTERS\t display help for device %s register variables\n", dptr->name, dptr->name);
}
if (dptr->modifiers) {
MTAB *mptr;
for (mptr = dptr->modifiers; mptr->pstring != NULL; mptr++) {
if (mptr->help) {
sim_printf ("h{elp} %s SET\t\t display help for device %s SET commands (modifiers)\n", dptr->name, dptr->name);
break;
}
}
}
}
}
else {
if (((cmdp->action == &exdep_cmd) || (0 == strcmp(cmdp->name, "BOOT"))) &&
sim_dflt_dev->help) {
sim_dflt_dev->help (stdout, sim_dflt_dev, sim_dflt_dev->units, 0, cmdp->name);
if (sim_log)
sim_dflt_dev->help (sim_log, sim_dflt_dev, sim_dflt_dev->units, 0, cmdp->name);
}
}
help_cmd_output (flag, cmdp->help, cmdp->help_base);
}
else { /* no help so it is likely a command alias */
CTAB *cmdpa;
for (cmdpa=cmd_table; cmdpa->name != NULL; cmdpa++)
if ((cmdpa->action == cmdp->action) && (cmdpa->help)) {
sim_printf ("%s is an alias for the %s command:\n%s",
cmdp->name, cmdpa->name, cmdpa->help);
break;
}
if (cmdpa->name == NULL) /* not found? */
sim_printf ("No help available for the %s command\n", cmdp->name);
}
}
else {
DEVICE *dptr;
UNIT *uptr;
t_stat r;
dptr = find_unit (gbuf, &uptr);
if (dptr == NULL) {
dptr = find_dev (gbuf);
if (dptr == NULL)
return SCPE_ARG;
if (dptr->flags & DEV_DISABLE)
sim_printf ("Device %s is currently disabled\n", dptr->name);
}
r = help_dev_help (stdout, dptr, uptr, flag, cptr);
if (sim_log)
help_dev_help (sim_log, dptr, uptr, flag | SCP_HELP_FLAT, cptr);
return r;
}
}
else {
fprint_help (stdout);
if (sim_log)
fprint_help (sim_log);
}
return SCPE_OK;
}
/* Spawn command */
t_stat spawn_cmd (int32 flag, char *cptr)
{
t_stat status;
if ((cptr == NULL) || (strlen (cptr) == 0))
cptr = getenv("SHELL");
if ((cptr == NULL) || (strlen (cptr) == 0))
cptr = getenv("ComSpec");
#if defined (VMS)
if ((cptr == NULL) || (strlen (cptr) == 0))
cptr = "SPAWN/INPUT=SYS$COMMAND:";
#endif
fflush(stdout); /* flush stdout */
if (sim_log) /* flush log if enabled */
fflush (sim_log);
if (sim_deb) /* flush debug if enabled */
fflush (sim_deb);
status = system (cptr);
#if defined (VMS)
printf ("\n");
#endif
return status;
}
/* Echo command */
t_stat echo_cmd (int32 flag, char *cptr)
{
sim_printf ("%s\n", cptr);
return SCPE_OK;
}
/* Do command
Syntax: DO {-E} {-V} <filename> {<arguments>...}
-E causes all command errors to be fatal; without it, only EXIT and ASSERT
failure will stop a command file.
-V causes commands to be echoed before execution.
Note that SCPE_STEP ("Step expired") is considered a note and not an error
and so does not abort command execution when using -E.
Inputs:
flag = caller and nesting level indicator
fcptr = filename and optional arguments, space-separated
Outputs:
status = error status
The "flag" input value indicates the source of the call, as follows:
-1 = initialization file (no error if not found)
0 = command line file
1 = "DO" command
>1 = nested "DO" command
*/
t_stat do_cmd (int32 flag, char *fcptr)
{
return do_cmd_label (flag, fcptr, NULL);
}
static char *do_position(void)
{
static char cbuf[CBUFSIZE];
sprintf (cbuf, "%s%s%s-%d", sim_do_filename[sim_do_depth], sim_do_label[sim_do_depth] ? "::" : "", sim_do_label[sim_do_depth] ? sim_do_label[sim_do_depth] : "", sim_goto_line[sim_do_depth]);
return cbuf;
}
t_stat do_cmd_label (int32 flag, char *fcptr, char *label)
{
char *cptr, cbuf[4*CBUFSIZE], gbuf[CBUFSIZE], *c, quote, *do_arg[11];
FILE *fpin;
CTAB *cmdp = NULL;
int32 echo, nargs, errabort, i;
int32 saved_sim_do_echo = sim_do_echo,
saved_sim_show_message = sim_show_message,
saved_sim_on_inherit = sim_on_inherit,
saved_sim_quiet = sim_quiet;
t_bool staying;
t_stat stat, stat_nomessage;
stat = SCPE_OK;
staying = TRUE;
if (flag > 0) /* need switches? */
GET_SWITCHES (fcptr); /* get switches */
echo = (sim_switches & SWMASK ('V')) || sim_do_echo; /* -v means echo */
sim_quiet = (sim_switches & SWMASK ('Q')) || sim_quiet; /* -q means quiet */
sim_on_inherit =(sim_switches & SWMASK ('O')) || sim_on_inherit; /* -o means inherit ON condition actions */
errabort = sim_switches & SWMASK ('E'); /* -e means abort on error */
c = fcptr;
do_arg[10] = NULL; /* make sure the argument list always ends with a NULL */
for (nargs = 0; nargs < 10; ) { /* extract arguments */
while (sim_isspace (*c)) /* skip blanks */
c++;
if (*c == 0) /* all done? */
do_arg [nargs++] = NULL; /* null argument */
else {
if (*c == '\'' || *c == '"') /* quoted string? */
quote = *c++;
else quote = 0;
do_arg[nargs++] = c; /* save start */
while (*c && (quote ? (*c != quote) : !sim_isspace (*c)))
c++;
if (*c) /* term at quote/spc */
*c++ = 0;
}
} /* end for */
if (do_arg [0] == NULL) /* need at least 1 */
return SCPE_2FARG;
if ((fpin = fopen (do_arg[0], "r")) == NULL) { /* file failed to open? */
strcat (strcpy (cbuf, do_arg[0]), ".sim"); /* try again with .sim extension */
if ((fpin = fopen (cbuf, "r")) == NULL) { /* failed a second time? */
if (flag == 0) /* cmd line file? */
fprintf (stderr, "Can't open file %s\n", do_arg[0]);
return SCPE_OPENERR; /* return failure */
}
}
if (flag >= 0) { /* Only bump nesting from command or nested */
++sim_do_depth;
if (sim_on_inherit) { /* inherit ON condition actions? */
sim_on_check[sim_do_depth] = sim_on_check[sim_do_depth-1]; /* inherit On mode */
for (i=0; i<SCPE_MAX_ERR; i++) { /* replicate any on commands */
if (sim_on_actions[sim_do_depth-1][i]) {
sim_on_actions[sim_do_depth][i] = (char *)malloc(1+strlen(sim_on_actions[sim_do_depth-1][i]));
if (NULL == sim_on_actions[sim_do_depth][i]) {
while (--i >= 0) {
free(sim_on_actions[sim_do_depth][i]);
sim_on_actions[sim_do_depth][i] = NULL;
}
sim_on_check[sim_do_depth] = 0;
sim_brk_clract (); /* defang breakpoint actions */
--sim_do_depth; /* unwind nesting */
return SCPE_MEM;
}
strcpy(sim_on_actions[sim_do_depth][i], sim_on_actions[sim_do_depth-1][i]);
}
}
}
}
strcpy( sim_do_filename[sim_do_depth], do_arg[0]); /* stash away do file name for possible use by 'call' command */
sim_do_label[sim_do_depth] = label; /* stash away do label for possible use in messages */
sim_goto_line[sim_do_depth] = 0;
if (label) {
sim_gotofile = fpin;
sim_do_echo = echo;
stat = goto_cmd (0, label);
if (stat != SCPE_OK) {
strcpy(cbuf, "RETURN SCPE_ARG");
cptr = get_glyph (cbuf, gbuf, 0); /* get command glyph */
cmdp = find_cmd (gbuf); /* return the errorStage things to the stat will be returned */
goto Cleanup_Return;
}
}
if (errabort) /* -e flag? */
set_on (1, NULL); /* equivalent to ON ERROR RETURN */
do {
sim_do_ocptr[sim_do_depth] = cptr = sim_brk_getact (cbuf, sizeof(cbuf)); /* get bkpt action */
if (!sim_do_ocptr[sim_do_depth]) { /* no pending action? */
sim_do_ocptr[sim_do_depth] = cptr = read_line (cbuf, sizeof(cbuf), fpin);/* get cmd line */
sim_goto_line[sim_do_depth] += 1;
}
sim_sub_args (cbuf, sizeof(cbuf), do_arg); /* substitute args */
if (cptr == NULL) { /* EOF? */
stat = SCPE_OK; /* set good return */
break;
}
if (*cptr == 0) /* ignore blank */
continue;
if (echo) /* echo if -v */
sim_printf("%s> %s\n", do_position(), cptr);
if (*cptr == ':') /* ignore label */
continue;
cptr = get_glyph (cptr, gbuf, 0); /* get command glyph */
sim_switches = 0; /* init switches */
sim_gotofile = fpin;
sim_do_echo = echo;
if ((cmdp = find_cmd (gbuf))) { /* lookup command */
if (cmdp->action == &return_cmd) /* RETURN command? */
break; /* done! */
if (cmdp->action == &do_cmd) { /* DO command? */
if (sim_do_depth >= MAX_DO_NEST_LVL) /* nest too deep? */
stat = SCPE_NEST;
else
stat = do_cmd (sim_do_depth+1, cptr); /* exec DO cmd */
}
else
if (cmdp->action == &shift_cmd) /* SHIFT command */
stat = shift_args(do_arg, sizeof(do_arg)/sizeof(do_arg[0]));
else
stat = cmdp->action (cmdp->arg, cptr); /* exec other cmd */
}
else stat = SCPE_UNK; /* bad cmd given */
echo = sim_do_echo; /* Allow for SET VERIFY */
stat_nomessage = stat & SCPE_NOMESSAGE; /* extract possible message supression flag */
stat_nomessage = stat_nomessage || (!sim_show_message);/* Apply global suppression */
stat = SCPE_BARE_STATUS(stat); /* remove possible flag */
if ((stat != SCPE_OK) ||
((cmdp->action != &return_cmd) &&
(cmdp->action != &goto_cmd) &&
(cmdp->action != &on_cmd) &&
(cmdp->action != &echo_cmd)))
sim_last_cmd_stat = stat; /* save command error status */
switch (stat) {
case SCPE_AFAIL:
staying = (sim_on_check[sim_do_depth] && /* if trap action defined */
sim_on_actions[sim_do_depth][stat]); /* use it, otherwise exit */
break;
case SCPE_EXIT:
staying = FALSE;
break;
case SCPE_OK:
case SCPE_STEP:
break;
default:
break;
}
if ((stat >= SCPE_BASE) && (stat != SCPE_EXIT) && /* error from cmd? */
(stat != SCPE_STEP)) {
if (!echo && !sim_quiet && /* report if not echoing */
!stat_nomessage && /* and not suppressing messages */
!(cmdp && cmdp->message)) { /* and not handling them specially */
sim_printf("%s> %s\n", do_position(), sim_do_ocptr[sim_do_depth]);
}
}
if (!stat_nomessage) { /* report error if not suppressed */
if (cmdp && cmdp->message) /* special message handler */
cmdp->message ((!echo && !sim_quiet) ? sim_do_ocptr[sim_do_depth] : NULL, stat);
else
if (stat >= SCPE_BASE) /* report error if not suppressed */
sim_printf ("%s\n", sim_error_text (stat));
}
if (staying &&
(sim_on_check[sim_do_depth]) &&
(stat != SCPE_OK) &&
(stat != SCPE_STEP)) {
if ((stat <= SCPE_MAX_ERR) && sim_on_actions[sim_do_depth][stat])
sim_brk_setact (sim_on_actions[sim_do_depth][stat]);
else
sim_brk_setact (sim_on_actions[sim_do_depth][0]);
}
if (sim_vm_post != NULL)
(*sim_vm_post) (TRUE);
} while (staying);
Cleanup_Return:
fclose (fpin); /* close file */
sim_gotofile = NULL;
if (flag >= 0) {
sim_do_echo = saved_sim_do_echo; /* restore echo state we entered with */
sim_show_message = saved_sim_show_message; /* restore message display state we entered with */
sim_on_inherit = saved_sim_on_inherit; /* restore ON inheritance state we entered with */
}
sim_quiet = saved_sim_quiet; /* restore quiet mode we entered with */
if ((flag >= 0) || (!sim_on_inherit)) {
for (i=0; i<SCPE_MAX_ERR; i++) { /* release any on commands */
free (sim_on_actions[sim_do_depth][i]);
sim_on_actions[sim_do_depth][i] = NULL;
}
sim_on_check[sim_do_depth] = 0; /* clear on mode */
}
if (flag >= 0)
--sim_do_depth; /* unwind nesting */
sim_brk_clract (); /* defang breakpoint actions */
if (cmdp && (cmdp->action == &return_cmd) && (0 != *cptr)) { /* return command with argument? */
sim_string_to_stat (cptr, &stat);
sim_last_cmd_stat = stat; /* save explicit status as command error status */
if (sim_switches & SWMASK ('Q'))
stat |= SCPE_NOMESSAGE; /* suppress error message display (in caller) if requested */
return stat; /* return with explicit return status */
}
return stat | SCPE_NOMESSAGE; /* suppress message since we've already done that here */
}
/* Substitute_args - replace %n tokens in 'instr' with the do command's arguments
and other enviroment variables
Calling sequence
instr = input string
instr_size = sizeof input string buffer
do_arg[10] = arguments
Token "%0" expands to the command file name.
Token %n (n being a single digit) expands to the n'th argument
Tonen %* expands to the whole set of arguments (%1 ... %9)
The input sequence "\%" represents a literal "%", and "\\" represents a
literal "\". All other character combinations are rendered literally.
Omitted parameters result in null-string substitutions.
A Tokens preceeded and followed by % characters are expanded as environment
variables, and if one isn't found then can be one of several special
variables:
%DATE% yyyy-mm-dd
%TIME% hh:mm:ss
%STIME% hh_mm_ss
%CTIME% Www Mmm dd hh:mm:ss yyyy
%STATUS% Status value from the last command executed
%TSTATUS% The text form of the last status value
%SIM_VERIFY% The Verify/Verbose mode of the current Do command file
%SIM_VERBOSE% The Verify/Verbose mode of the current Do command file
%SIM_QUIET% The Quiet mode of the current Do command file
%SIM_MESSAGE% The message display status of the current Do command file
Environment variable lookups are done first with the precise name between
the % characters and if that fails, then the name between the % characters
is upcased and a lookup of that valus is attempted.
The first Space delimited token on the line is extracted in uppercase and
then looked up as an environment variable. If found it the value is
supstituted for the original string before expanding everything else. If
it is not found, then the original beginning token on the line is left
untouched.
*/
void sim_sub_args (char *instr, size_t instr_size, char *do_arg[])
{
char gbuf[CBUFSIZE];
char *ip = instr, *op, *ap, *oend, *istart, *tmpbuf;
char rbuf[CBUFSIZE];
int i;
time_t now;
struct tm *tmnow;
time(&now);
tmnow = localtime(&now);
tmpbuf = (char *)malloc(instr_size);
op = tmpbuf;
oend = tmpbuf + instr_size - 2;
while (sim_isspace (*ip)) /* skip leading spaces */
*op++ = *ip++;
istart = ip;
for (; *ip && (op < oend); ) {
if ((ip [0] == '\\') && /* literal escape? */
((ip [1] == '%') || (ip [1] == '\\'))) { /* and followed by '%' or '\'? */
ip++; /* skip '\' */
*op++ = *ip++; /* copy escaped char */
}
else
if ((*ip == '%') &&
(sim_isalnum(ip[1]) || (ip[1] == '*') || (ip[1] == '_'))) {/* sub? */
if ((ip[1] >= '0') && (ip[1] <= ('9'))) { /* %n = sub */
ap = do_arg[ip[1] - '0'];
for (i=0; i<ip[1] - '0'; ++i) /* make sure we're not past the list end */
if (do_arg[i] == NULL) {
ap = NULL;
break;
}
ip = ip + 2;
}
else if (ip[1] == '*') { /* %1 ... %9 = sub */
memset (rbuf, '\0', sizeof(rbuf));
ap = rbuf;
for (i=1; i<=9; ++i)
if (do_arg[i] == NULL)
break;
else
if ((sizeof(rbuf)-strlen(rbuf)) < (2 + strlen(do_arg[i]))) {
if (strchr(do_arg[i], ' ')) { /* need to surround this argument with quotes */
char quote = '"';
if (strchr(do_arg[i], quote))
quote = '\'';
sprintf(&rbuf[strlen(rbuf)], "%s%c%s%c\"", (i != 1) ? " " : "", quote, do_arg[i], quote);
}
else
sprintf(&rbuf[strlen(rbuf)], "%s%s", (i != 1) ? " " : "", do_arg[i]);
}
else
break;
ip = ip + 2;
}
else { /* environment variable */
ap = NULL;
get_glyph_nc (ip+1, gbuf, '%'); /* first try using the literal name */
ap = getenv(gbuf);
if (!ap) {
get_glyph (ip+1, gbuf, '%'); /* now try using the upcased name */
ap = getenv(gbuf);
}
ip += 1 + strlen (gbuf);
if (*ip == '%') ++ip;
if (!ap) {
/* ISO 8601 format date/time info */
if (!strcmp ("DATE", gbuf)) {
sprintf (rbuf, "%4d-%02d-%02d", tmnow->tm_year+1900, tmnow->tm_mon+1, tmnow->tm_mday);
ap = rbuf;
}
else if (!strcmp ("TIME", gbuf)) {
sprintf (rbuf, "%02d:%02d:%02d", tmnow->tm_hour, tmnow->tm_min, tmnow->tm_sec);
ap = rbuf;
}
else if (!strcmp ("DATETIME", gbuf)) {
sprintf (rbuf, "%04d-%02d-%02dT%02d:%02d:%02d", tmnow->tm_year+1900, tmnow->tm_mon+1, tmnow->tm_mday, tmnow->tm_hour, tmnow->tm_min, tmnow->tm_sec);
ap = rbuf;
}
/* Locale oriented formatted date/time info */
if (!strcmp ("LDATE", gbuf)) {
strftime (rbuf, sizeof(rbuf), "%x", tmnow);
ap = rbuf;
}
else if (!strcmp ("LTIME", gbuf)) {
#if defined(HAVE_C99_STRFTIME)
strftime (rbuf, sizeof(rbuf), "%r", tmnow);
#else
strftime (rbuf, sizeof(rbuf), "%p", tmnow);
if (rbuf[0])
strftime (rbuf, sizeof(rbuf), "%I:%M:%S %p", tmnow);
else
strftime (rbuf, sizeof(rbuf), "%H:%M:%S", tmnow);
#endif
ap = rbuf;
}
else if (!strcmp ("CTIME", gbuf)) {
#if defined(HAVE_C99_STRFTIME)
strftime (rbuf, sizeof(rbuf), "%c", tmnow);
#else
strcpy (rbuf, ctime(&now));
rbuf[strlen (rbuf)-1] = '\0'; /* remove trailing \n */
#endif
ap = rbuf;
}
/* Separate Date/Time info */
else if (!strcmp ("DATE_YYYY", gbuf)) {/* Year (0000-9999) */
strftime (rbuf, sizeof(rbuf), "%Y", tmnow);
ap = rbuf;
}
else if (!strcmp ("DATE_YY", gbuf)) {/* Year (00-99) */
strftime (rbuf, sizeof(rbuf), "%y", tmnow);
ap = rbuf;
}
else if (!strcmp ("DATE_YC", gbuf)) {/* Century (year/100) */
sprintf (rbuf, "%d", (tmnow->tm_year + 1900)/100);
ap = rbuf;
}
else if (!strcmp ("DATE_MM", gbuf)) {/* Month number (01-12) */
strftime (rbuf, sizeof(rbuf), "%m", tmnow);
ap = rbuf;
}
else if (!strcmp ("DATE_DD", gbuf)) {/* Day of Month (01-31) */
strftime (rbuf, sizeof(rbuf), "%d", tmnow);
ap = rbuf;
}
else if (!strcmp ("DATE_D", gbuf)) { /* ISO 8601 weekday number (1-7) */
sprintf (rbuf, "%d", (tmnow->tm_wday ? tmnow->tm_wday : 7));
ap = rbuf;
}
else if ((!strcmp ("DATE_WW", gbuf)) || /* ISO 8601 week number (01-53) */
(!strcmp ("DATE_WYYYY", gbuf))) {/* ISO 8601 week year number (0000-9999) */
int iso_yr = tmnow->tm_year + 1900;
int iso_wk = (tmnow->tm_yday + 11 - (tmnow->tm_wday ? tmnow->tm_wday : 7))/7;;
if (iso_wk == 0) {
iso_yr = iso_yr - 1;
tmnow->tm_yday += 365 + (((iso_yr % 4) == 0) ? 1 : 0); /* Adjust for Leap Year (Correct thru 2099) */
iso_wk = (tmnow->tm_yday + 11 - (tmnow->tm_wday ? tmnow->tm_wday : 7))/7;
}
else
if ((iso_wk == 53) && (((31 - tmnow->tm_mday) + tmnow->tm_wday) < 4)) {
++iso_yr;
iso_wk = 1;
}
if (!strcmp ("DATE_WW", gbuf))
sprintf (rbuf, "%02d", iso_wk);
else
sprintf (rbuf, "%04d", iso_yr);
ap = rbuf;
}
else if (!strcmp ("DATE_JJJ", gbuf)) {/* day of year (001-366) */
strftime (rbuf, sizeof(rbuf), "%j", tmnow);
ap = rbuf;
}
else if (!strcmp ("TIME_HH", gbuf)) {/* Hour of day (00-23) */
strftime (rbuf, sizeof(rbuf), "%H", tmnow);
ap = rbuf;
}
else if (!strcmp ("TIME_MM", gbuf)) {/* Minute of hour (00-59) */
strftime (rbuf, sizeof(rbuf), "%M", tmnow);
ap = rbuf;
}
else if (!strcmp ("TIME_SS", gbuf)) {/* Second of minute (00-59) */
strftime (rbuf, sizeof(rbuf), "%S", tmnow);
ap = rbuf;
}
else if (!strcmp ("STATUS", gbuf)) {
sprintf (rbuf, "%08X", sim_last_cmd_stat);
ap = rbuf;
}
else if (!strcmp ("TSTATUS", gbuf)) {
sprintf (rbuf, "%s", sim_error_text (sim_last_cmd_stat));
ap = rbuf;
}
else if (!strcmp ("SIM_VERIFY", gbuf)) {
sprintf (rbuf, "%s", sim_do_echo ? "-V" : "");
ap = rbuf;
}
else if (!strcmp ("SIM_VERBOSE", gbuf)) {
sprintf (rbuf, "%s", sim_do_echo ? "-V" : "");
ap = rbuf;
}
else if (!strcmp ("SIM_QUIET", gbuf)) {
sprintf (rbuf, "%s", sim_quiet ? "-Q" : "");
ap = rbuf;
}
else if (!strcmp ("SIM_MESSAGE", gbuf)) {
sprintf (rbuf, "%s", sim_show_message ? "" : "-Q");
ap = rbuf;
}
}
}
if (ap) { /* non-null arg? */
while (*ap && (op < oend)) /* copy the argument */
*op++ = *ap++;
}
}
else
if (ip == istart) { /* at beginning of input? */
get_glyph (istart, gbuf, 0); /* substitute initial token */
ap = getenv(gbuf); /* if it is an environment variable name */
if (!ap) { /* nope? */
*op++ = *ip++; /* press on with literal character */
continue;
}
while (*ap && (op < oend)) /* copy the translation */
*op++ = *ap++;
ip += strlen(gbuf);
}
else
*op++ = *ip++; /* literal character */
}
*op = 0; /* term buffer */
strcpy (instr, tmpbuf);
free (tmpbuf);
return;
}
t_stat shift_args (char *do_arg[], size_t arg_count)
{
size_t i;
for (i=1; i<arg_count-1; ++i)
do_arg[i] = do_arg[i+1];
return SCPE_OK;
}
static
int sim_cmp_string (const char *s1, const char *s2)
{
long int v1, v2;
char *ep1, *ep2;
v1 = strtol(s1+1, &ep1, 0);
v2 = strtol(s2+1, &ep2, 0);
if ((ep1 != s1 + strlen (s1) - 1) ||
(ep2 != s2 + strlen (s2) - 1))
return strcmp (s1, s2);
if (v1 == v2)
return 0;
if (v1 < v2)
return -1;
return 1;
}
/* Assert command
If command
Syntax: ASSERT {NOT} {<dev>} <reg>{<logical-op><value>}<conditional-op><value>
Syntax: IF {NOT} {<dev>} <reg>{<logical-op><value>}<conditional-op><value> commandtoprocess{; additionalcommandtoprocess}...
If NOT is specified, the resulting expression value is inverted.
If <dev> is not specified, sim_dflt_dev (CPU) is assumed.
<value> is expressed in the radix specified for <reg>.
<logical-op> and <conditional-op> are the same as that
allowed for examine and deposit search specifications.
Syntax: ASSERT {-i} {NOT} "<string1>" <compare-op> "<string2>"
Syntax: IF {-i} {NOT} "<string1>" <compare-op> "<string2>" commandtoprocess{; additionalcommandtoprocess}...
If -i is specified, the comparisons are done in a case insensitive manner.
If NOT is specified, the resulting expression value is inverted.
"<string1>" and "<string2>" are quote delimited strings which include
expansion references to environment variables in the simulator.
<compare-op> can be any one of:
== - equal
EQU - equal
!= - not equal
NEQ - not equal
< - less than
LSS - less than
<= - less than or equal
LEQ - less than or equal
> - greater than
GTR - greater than
>= - greater than or equal
GEQ - greater than or equal
*/
t_stat assert_cmd (int32 flag, char *cptr)
{
char gbuf[CBUFSIZE], gbuf2[CBUFSIZE];
const char *tptr, *gptr;
REG *rptr;
uint32 idx;
t_value val;
t_stat r;
t_bool not = FALSE;
t_bool result;
t_addr addr;
t_stat reason;
cptr = get_sim_opt (CMD_OPT_SW|CMD_OPT_DFT, cptr, &r); /* get sw, default */
sim_stabr.boolop = sim_staba.boolop = -1; /* no relational op dflt */
if (*cptr == 0) /* must be more */
return SCPE_2FARG;
tptr = get_glyph (cptr, gbuf, 0); /* get token */
if (!strcmp (gbuf, "NOT")) { /* Conditional Inversion? */
not = TRUE; /* remember that, and */
cptr = (char *)tptr;
}
if (*cptr == '"') { /* quoted string comparison? */
char op[CBUFSIZE];
static struct {
char *op;
int aval;
int bval;
t_bool invert;
} *optr, compare_ops[] =
{
{"==", 0, 0, FALSE},
{"EQU", 0, 0, FALSE},
{"!=", 0, 0, TRUE},
{"NEQ", 0, 0, TRUE},
{"<", -1, -1, FALSE},
{"LSS", -1, -1, FALSE},
{"<=", 0, -1, FALSE},
{"LEQ", 0, -1, FALSE},
{">", 1, 1, FALSE},
{"GTR", 1, 1, FALSE},
{">=", 0, 1, FALSE},
{"GEQ", 0, 1, FALSE},
{NULL}};
tptr = (char *)get_glyph_gen (cptr, gbuf, '=', (sim_switches & SWMASK ('I')), TRUE, '\\');/* get first string */
if (!*tptr)
return SCPE_2FARG;
cptr += strlen (gbuf);
while (sim_isspace (*cptr)) /* skip spaces */
++cptr;
get_glyph (cptr, op, '"');
for (optr = compare_ops; optr->op; optr++)
if (0 == strcmp (op, optr->op))
break;
if (!optr->op)
return sim_messagef (SCPE_ARG, "Invalid operator: %s\n", op);
cptr += strlen (op);
while (sim_isspace (*cptr)) /* skip spaces */
++cptr;
cptr = (char *)get_glyph_gen (cptr, gbuf2, 0, (sim_switches & SWMASK ('I')), TRUE, '\\');/* get second string */
if (*cptr) { /* more? */
if (flag) /* ASSERT has no more args */
return SCPE_2MARG;
}
else {
if (!flag)
return SCPE_2FARG; /* IF needs actions! */
}
result = sim_cmp_string (gbuf, gbuf2);
result = ((result == optr->aval) || (result == optr->bval));
if (optr->invert)
result = !result;
}
else {
cptr = get_glyph (cptr, gbuf, 0); /* get register */
rptr = find_reg (gbuf, &gptr, sim_dfdev); /* parse register */
if (rptr) { /* got register? */
if (*gptr == '[') { /* subscript? */
if (rptr->depth <= 1) /* array register? */
return SCPE_ARG;
idx = (uint32) strtotv (++gptr, &tptr, 10); /* convert index */
if ((gptr == tptr) || (*tptr++ != ']'))
return SCPE_ARG;
gptr = tptr; /* update */
}
else idx = 0; /* not array */
if (idx >= rptr->depth) /* validate subscript */
return SCPE_SUB;
}
else { /* not reg, check for memory */
if (sim_dfdev && sim_vm_parse_addr) /* get addr */
addr = sim_vm_parse_addr (sim_dfdev, (char *)gbuf, (char **)&gptr);
else addr = (t_addr) strtotv (gbuf, &gptr, sim_dfdev->dradix);
if (gbuf == gptr) /* error? */
return SCPE_NXREG;
}
if (*gptr != 0) /* more? must be search */
get_glyph (gptr, gbuf, 0);
else {
if (*cptr == 0) /* must be more */
return SCPE_2FARG;
cptr = get_glyph (cptr, gbuf, 0); /* get search cond */
}
if (*cptr) { /* more? */
if (flag) /* ASSERT has no more args */
return SCPE_2MARG;
}
else {
if (!flag)
return SCPE_2FARG; /* IF needs actions! */
}
if (rptr) { /* Handle register case */
if (!get_rsearch (gbuf, rptr->radix, &sim_stabr) || /* parse condition */
(sim_stabr.boolop == -1)) /* relational op reqd */
return SCPE_MISVAL;
val = get_rval (rptr, idx); /* get register value */
result = test_search (&val, &sim_stabr); /* test condition */
}
else { /* Handle memory case */
if (!get_asearch (gbuf, sim_dfdev->dradix, &sim_staba) || /* parse condition */
(sim_staba.boolop == -1)) /* relational op reqd */
return SCPE_MISVAL;
reason = get_aval (addr, sim_dfdev, sim_dfunit);/* get data */
if (reason != SCPE_OK) /* return if error */
return reason;
result = test_search (sim_eval, &sim_staba); /* test condition */
}
}
if (not ^ result) {
if (!flag)
sim_brk_setact (cptr); /* set up IF actions */
}
else
if (flag)
return SCPE_AFAIL; /* return assert status */
return SCPE_OK;
}
/* Send command
Syntax: SEND {After=m},{Delay=n},"string-to-send"
After - is a positive integer representing a number of instruction delay
before the initial characters is sent. The value specified
in a after argument persists across SEND commands. The after
parameter can be set by itself with SEND AFTER=n
Delay - is a positive integer representing a minimal instruction delay
before and between characters being sent. The value specified
in a delay argument persists across SEND commands. The delay
parameter can be set by itself with SEND DELAY=n
String - must be quoted. Quotes may be either single or double but the
opening anc closing quote characters must match. Within quotes
C style character escapes are allowed.
The following character escapes are explicitly supported:
\r Sends the ASCII Carriage Return character (Decimal value 13)
\n Sends the ASCII Linefeed character (Decimal value 10)
\f Sends the ASCII Formfeed character (Decimal value 12)
\t Sends the ASCII Horizontal Tab character (Decimal value 9)
\v Sends the ASCII Vertical Tab character (Decimal value 11)
\b Sends the ASCII Backspace character (Decimal value 8)
\\ Sends the ASCII Backslash character (Decimal value 92)
\' Sends the ASCII Single Quote character (Decimal value 39)
\" Sends the ASCII Double Quote character (Decimal value 34)
\? Sends the ASCII Question Mark character (Decimal value 63)
\e Sends the ASCII Escape character (Decimal value 27)
as well as octal character values of the form:
\n{n{n}} where each n is an octal digit (0-7)
and hext character values of the form:
\xh{h} where each h is a hex digit (0-9A-Fa-f)
*/
t_stat send_cmd (int32 flag, char *cptr)
{
char gbuf[CBUFSIZE], *tptr;
uint8 dbuf[CBUFSIZE];
uint32 dsize = 0;
uint32 delay = 0;
uint32 after = 0;
t_stat r;
SEND *snd;
GET_SWITCHES (cptr); /* get switches */
tptr = get_glyph (cptr, gbuf, ',');
if (sim_isalpha(gbuf[0]) && (strchr (gbuf, ':'))) {
r = tmxr_locate_line_send (gbuf, &snd);
if (r != SCPE_OK)
return r;
cptr = tptr;
tptr = get_glyph (tptr, gbuf, ',');
}
else
snd = sim_cons_get_send ();
while (*cptr) {
if ((!strncmp(gbuf, "DELAY=", 6)) && (gbuf[6])) {
delay = (uint32)get_uint (&gbuf[6], 10, 10000000, &r);
if (r != SCPE_OK)
return sim_messagef (SCPE_ARG, "Invalid Delay Value\n");
cptr = tptr;
tptr = get_glyph (cptr, gbuf, ',');
continue;
}
if ((!strncmp(gbuf, "AFTER=", 6)) && (gbuf[6])) {
after = (uint32)get_uint (&gbuf[6], 10, 10000000, &r);
if (r != SCPE_OK)
return sim_messagef (SCPE_ARG, "Invalid After Value\n");
cptr = tptr;
tptr = get_glyph (cptr, gbuf, ',');
continue;
}
if ((*cptr == '"') || (*cptr == '\''))
break;
return SCPE_ARG;
}
if (*cptr) {
if ((*cptr != '"') && (*cptr != '\''))
return sim_messagef (SCPE_ARG, "String must be quote delimited\n");
cptr = get_glyph_quoted (cptr, gbuf, 0);
if (*cptr != '\0')
return SCPE_2MARG; /* No more arguments */
if (SCPE_OK != sim_decode_quoted_string (gbuf, dbuf, &dsize))
return sim_messagef (SCPE_ARG, "Invalid String\n");
}
if ((dsize == 0) && (delay == 0) && (after == 0))
return SCPE_2FARG;
return sim_send_input (snd, dbuf, dsize, after, delay);
}
t_stat sim_show_send (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
char gbuf[CBUFSIZE], *tptr;
t_stat r;
SEND *snd;
tptr = get_glyph (cptr, gbuf, ',');
if (sim_isalpha(gbuf[0]) && (strchr (gbuf, ':'))) {
r = tmxr_locate_line_send (gbuf, &snd);
if (r != SCPE_OK)
return r;
cptr = tptr;
}
else
snd = sim_cons_get_send ();
if (*cptr)
return SCPE_2MARG;
return sim_show_send_input (st, snd);
}
t_stat expect_cmd (int32 flag, char *cptr)
{
char gbuf[CBUFSIZE], *tptr;
t_stat r;
EXPECT *exp;
GET_SWITCHES (cptr); /* get switches */
tptr = get_glyph (cptr, gbuf, ',');
if (sim_isalpha(gbuf[0]) && (strchr (gbuf, ':'))) {
r = tmxr_locate_line_expect (gbuf, &exp);
if (r != SCPE_OK)
return r;
cptr = tptr;
}
else
exp = sim_cons_get_expect ();
if (flag)
return sim_set_expect (exp, cptr);
else
return sim_set_noexpect (exp, cptr);
}
t_stat sim_show_expect (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
char gbuf[CBUFSIZE], *tptr;
t_stat r;
EXPECT *exp;
tptr = get_glyph (cptr, gbuf, ',');
if (sim_isalpha(gbuf[0]) && (strchr (gbuf, ':'))) {
r = tmxr_locate_line_expect (gbuf, &exp);
if (r != SCPE_OK)
return r;
cptr = tptr;
}
else
exp = sim_cons_get_expect ();
if (*cptr && (*cptr != '"') && (*cptr != '\''))
return SCPE_ARG; /* String must be quote delimited */
tptr = get_glyph_quoted (cptr, gbuf, 0);
if (*tptr != '\0')
return SCPE_2MARG; /* No more arguments */
if (*cptr && (cptr[strlen(cptr)-1] != '"') && (cptr[strlen(cptr)-1] != '\''))
return SCPE_ARG; /* String must be quote delimited */
return sim_exp_show (st, exp, gbuf);
}
/* Goto command */
t_stat goto_cmd (int32 flag, char *fcptr)
{
char *cptr, cbuf[CBUFSIZE], gbuf[CBUFSIZE], gbuf1[CBUFSIZE];
long fpos;
int32 saved_do_echo = sim_do_echo;
int32 saved_goto_line = sim_goto_line[sim_do_depth];
if (NULL == sim_gotofile) return SCPE_UNK; /* only valid inside of do_cmd */
get_glyph (fcptr, gbuf1, 0);
if ('\0' == gbuf1[0]) return SCPE_ARG; /* unspecified goto target */
fpos = ftell(sim_gotofile); /* Save start position */
rewind(sim_gotofile); /* start search for label */
sim_goto_line[sim_do_depth] = 0; /* reset line number */
sim_do_echo = 0; /* Don't echo while searching for label */
while (1) {
cptr = read_line (cbuf, sizeof(cbuf), sim_gotofile);/* get cmd line */
if (cptr == NULL) break; /* exit on eof */
sim_goto_line[sim_do_depth] += 1; /* record line number */
if (*cptr == 0) continue; /* ignore blank */
if (*cptr != ':') continue; /* ignore non-labels */
++cptr; /* skip : */
while (sim_isspace (*cptr)) ++cptr; /* skip blanks */
cptr = get_glyph (cptr, gbuf, 0); /* get label glyph */
if (0 == strcmp(gbuf, gbuf1)) {
sim_brk_clract (); /* goto defangs current actions */
sim_do_echo = saved_do_echo; /* restore echo mode */
if (sim_do_echo) /* echo if -v */
sim_printf("%s> %s\n", do_position(), cbuf);
return SCPE_OK;
}
}
sim_do_echo = saved_do_echo; /* restore echo mode */
fseek(sim_gotofile, fpos, SEEK_SET); /* resture start position */
sim_goto_line[sim_do_depth] = saved_goto_line; /* restore start line number */
return SCPE_ARG;
}
/* Return command */
/* The return command is invalid unless encountered in a do_cmd context, */
/* and in that context, it is handled as a special case inside of do_cmd() */
/* and not dispatched here, so if we get here a return has been issued from */
/* interactive input */
t_stat return_cmd (int32 flag, char *fcptr)
{
return SCPE_UNK; /* only valid inside of do_cmd */
}
/* Shift command */
/* The shift command is invalid unless encountered in a do_cmd context, */
/* and in that context, it is handled as a special case inside of do_cmd() */
/* and not dispatched here, so if we get here a shift has been issued from */
/* interactive input (it is not valid interactively since it would have to */
/* mess with the program's argv which is owned by the C runtime library */
t_stat shift_cmd (int32 flag, char *fcptr)
{
return SCPE_UNK; /* only valid inside of do_cmd */
}
/* Call command */
/* The call command is invalid unless encountered in a do_cmd context, */
/* and in that context, it is handled as a special case inside of do_cmd() */
/* and not dispatched here, so if we get here a call has been issued from */
/* interactive input */
t_stat call_cmd (int32 flag, char *fcptr)
{
char *cptr, cbuf[CBUFSIZE], gbuf[CBUFSIZE];
if (NULL == sim_gotofile) return SCPE_UNK; /* only valid inside of do_cmd */
cptr = get_glyph (fcptr, gbuf, 0);
if ('\0' == gbuf[0]) return SCPE_ARG; /* unspecified goto target */
sprintf(cbuf, "%s %s", sim_do_filename[sim_do_depth], cptr);
sim_switches |= SWMASK ('O'); /* inherit ON state and actions */
return do_cmd_label (flag, cbuf, gbuf);
}
/* On command */
t_stat on_cmd (int32 flag, char *cptr)
{
char gbuf[CBUFSIZE];
t_stat cond;
cptr = get_glyph (cptr, gbuf, 0);
if ('\0' == gbuf[0]) return SCPE_ARG; /* unspecified condition */
if (0 == strcmp("ERROR", gbuf))
cond = 0;
else
if (SCPE_OK != sim_string_to_stat (gbuf, &cond))
return SCPE_ARG;
if ((NULL == cptr) || ('\0' == *cptr)) { /* Empty Action */
free(sim_on_actions[sim_do_depth][cond]); /* Clear existing condition */
sim_on_actions[sim_do_depth][cond] = NULL; }
else {
sim_on_actions[sim_do_depth][cond] =
(char *)realloc(sim_on_actions[sim_do_depth][cond], 1+strlen(cptr));
strcpy(sim_on_actions[sim_do_depth][cond], cptr);
}
return SCPE_OK;
}
/* noop command */
/* The noop command (IGNORE, PROCEED) does nothing */
t_stat noop_cmd (int32 flag, char *cptr)
{
if (cptr && (*cptr != 0)) /* now eol? */
return SCPE_2MARG;
return SCPE_OK; /* we're happy doing nothing */
}
/* Set on/noon routine */
t_stat set_on (int32 flag, char *cptr)
{
if ((flag) && (cptr) && (*cptr)) { /* Set ON with arg */
char gbuf[CBUFSIZE];
cptr = get_glyph (cptr, gbuf, 0); /* get command glyph */
if (((MATCH_CMD(gbuf,"INHERIT")) &&
(MATCH_CMD(gbuf,"NOINHERIT"))) ||
(*cptr))
return SCPE_2MARG;
if ((gbuf[0]) && (0 == MATCH_CMD(gbuf,"INHERIT")))
sim_on_inherit = 1;
if ((gbuf[0]) && (0 == MATCH_CMD(gbuf,"NOINHERIT")))
sim_on_inherit = 0;
return SCPE_OK;
}
if (cptr && (*cptr != 0)) /* now eol? */
return SCPE_2MARG;
sim_on_check[sim_do_depth] = flag;
if ((sim_do_depth != 0) &&
(NULL == sim_on_actions[sim_do_depth][0])) { /* default handler set? */
sim_on_actions[sim_do_depth][0] = /* No, so make "RETURN" */
(char *)malloc(1+strlen("RETURN")); /* be the default action */
strcpy(sim_on_actions[sim_do_depth][0], "RETURN");
}
if ((sim_do_depth != 0) &&
(NULL == sim_on_actions[sim_do_depth][SCPE_AFAIL])) {/* handler set for AFAIL? */
sim_on_actions[sim_do_depth][SCPE_AFAIL] = /* No, so make "RETURN" */
(char *)malloc(1+strlen("RETURN")); /* be the action */
strcpy(sim_on_actions[sim_do_depth][SCPE_AFAIL], "RETURN");
}
return SCPE_OK;
}
/* Set verify/noverify routine */
t_stat set_verify (int32 flag, char *cptr)
{
if (cptr && (*cptr != 0)) /* now eol? */
return SCPE_2MARG;
if (flag == sim_do_echo) /* already set correctly? */
return SCPE_OK;
sim_do_echo = flag;
return SCPE_OK;
}
/* Set message/nomessage routine */
t_stat set_message (int32 flag, char *cptr)
{
if (cptr && (*cptr != 0)) /* now eol? */
return SCPE_2MARG;
if (flag == sim_show_message) /* already set correctly? */
return SCPE_OK;
sim_show_message = flag;
return SCPE_OK;
}
/* Set quiet/noquiet routine */
t_stat set_quiet (int32 flag, char *cptr)
{
if (cptr && (*cptr != 0)) /* now eol? */
return SCPE_2MARG;
if (flag == sim_quiet) /* already set correctly? */
return SCPE_OK;
sim_quiet = flag;
return SCPE_OK;
}
/* Set asynch/noasynch routine */
t_stat sim_set_asynch (int32 flag, char *cptr)
{
if (cptr && (*cptr != 0)) /* now eol? */
return SCPE_2MARG;
#ifdef SIM_ASYNCH_IO
if (flag == sim_asynch_enabled) /* already set correctly? */
return SCPE_OK;
sim_asynch_enabled = flag;
tmxr_change_async ();
sim_timer_change_asynch ();
if (1) {
uint32 i, j;
DEVICE *dptr;
UNIT *uptr;
/* Call unit flush routines to report asynch status change to device layer */
for (i = 1; (dptr = sim_devices[i]) != NULL; i++) { /* flush attached files */
for (j = 0; j < dptr->numunits; j++) { /* if not buffered in mem */
uptr = dptr->units + j;
if ((uptr->flags & UNIT_ATT) && /* attached, */
(uptr->io_flush)) /* unit specific flush routine */
uptr->io_flush (uptr);
}
}
}
if (!sim_quiet)
printf ("Asynchronous I/O %sabled\n", sim_asynch_enabled ? "en" : "dis");
if (sim_log)
fprintf (sim_log, "Asynchronous I/O %sabled\n", sim_asynch_enabled ? "en" : "dis");
return SCPE_OK;
#else
if (!sim_quiet)
printf ("Asynchronous I/O is not available in this simulator\n");
if (sim_log)
fprintf (sim_log, "Asynchronous I/O is not available in this simulator\n");
return SCPE_NOFNC;
#endif
}
/* Show asynch routine */
t_stat sim_show_asynch (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
if (cptr && (*cptr != 0))
return SCPE_2MARG;
#ifdef SIM_ASYNCH_IO
fprintf (st, "Asynchronous I/O is %sabled, %s\n", (sim_asynch_enabled) ? "en" : "dis", AIO_QUEUE_MODE);
#if defined(SIM_ASYNCH_MUX)
fprintf (st, "Asynchronous Multiplexer support is available\n");
#endif
#if defined(SIM_ASYNCH_CLOCKS)
fprintf (st, "Asynchronous Clock is %sabled\n", (sim_asynch_timer) ? "en" : "dis");
#endif
#else
fprintf (st, "Asynchronous I/O is not available in this simulator\n");
#endif
return SCPE_OK;
}
/* Set environment routine */
t_stat sim_set_environment (int32 flag, char *cptr)
{
char varname[CBUFSIZE];
if ((!cptr) || (*cptr == 0)) /* now eol? */
return SCPE_2FARG;
cptr = get_glyph (cptr, varname, '='); /* get environment variable name */
setenv(varname, cptr, 1);
return SCPE_OK;
}
/* Set command */
t_stat set_cmd (int32 flag, char *cptr)
{
uint32 lvl = 0;
t_stat r;
char gbuf[CBUFSIZE], *cvptr, *svptr;
DEVICE *dptr;
UNIT *uptr;
MTAB *mptr;
CTAB *gcmdp;
C1TAB *ctbr = NULL, *glbr;
GET_SWITCHES (cptr); /* get switches */
if (*cptr == 0) /* must be more */
return SCPE_2FARG;
cptr = get_glyph (svptr = cptr, gbuf, 0); /* get glob/dev/unit */
if ((dptr = find_dev (gbuf))) { /* device match? */
uptr = dptr->units; /* first unit */
ctbr = set_dev_tab; /* global table */
lvl = MTAB_VDV; /* device match */
GET_SWITCHES (cptr); /* get more switches */
}
else if ((dptr = find_unit (gbuf, &uptr))) { /* unit match? */
if (uptr == NULL) /* invalid unit */
return SCPE_NXUN;
ctbr = set_unit_tab; /* global table */
lvl = MTAB_VUN; /* unit match */
GET_SWITCHES (cptr); /* get more switches */
}
else if ((gcmdp = find_ctab (set_glob_tab, gbuf))) { /* global? */
GET_SWITCHES (cptr); /* get more switches */
return gcmdp->action (gcmdp->arg, cptr); /* do the rest */
}
else {
if (sim_dflt_dev->modifiers) {
if ((cvptr = strchr (gbuf, '='))) /* = value? */
*cvptr++ = 0;
for (mptr = sim_dflt_dev->modifiers; mptr->mask != 0; mptr++) {
if (mptr->mstring && (MATCH_CMD (gbuf, mptr->mstring) == 0)) {
dptr = sim_dflt_dev;
cptr = svptr;
while (sim_isspace(*cptr))
++cptr;
break;
}
}
}
if (!dptr)
return SCPE_NXDEV; /* no match */
lvl = MTAB_VDV; /* device match */
uptr = dptr->units; /* first unit */
}
if (*cptr == 0) /* must be more */
return SCPE_2FARG;
GET_SWITCHES (cptr); /* get more switches */
while (*cptr != 0) { /* do all mods */
cptr = get_glyph (svptr = cptr, gbuf, ','); /* get modifier */
if ((cvptr = strchr (gbuf, '='))) /* = value? */
*cvptr++ = 0;
for (mptr = dptr->modifiers; mptr && (mptr->mask != 0); mptr++) {
if ((mptr->mstring) && /* match string */
(MATCH_CMD (gbuf, mptr->mstring) == 0)) { /* matches option? */
if (mptr->mask & MTAB_XTD) { /* extended? */
if (((lvl & mptr->mask) & ~MTAB_XTD) == 0)
return SCPE_ARG;
if ((lvl == MTAB_VUN) && (uptr->flags & UNIT_DIS))
return SCPE_UDIS; /* unit disabled? */
if (mptr->valid) { /* validation rtn? */
if (cvptr && MODMASK(mptr,MTAB_QUOTE)) {
get_glyph_quoted (svptr, gbuf, ',');
if ((cvptr = strchr (gbuf, '=')))
*cvptr++ = 0;
}
else {
if (cvptr && MODMASK(mptr,MTAB_NC)) {
get_glyph_nc (svptr, gbuf, ',');
if ((cvptr = strchr (gbuf, '=')))
*cvptr++ = 0;
}
}
r = mptr->valid (uptr, mptr->match, cvptr, mptr->desc);
if (r != SCPE_OK)
return r;
}
else if (!mptr->desc) /* value desc? */
break;
// else if (mptr->mask & MTAB_VAL) { /* take a value? */
// if (!cvptr) return SCPE_MISVAL; /* none? error */
// r = dep_reg (0, cvptr, (REG *) mptr->desc, 0);
// if (r != SCPE_OK) return r;
// }
else if (cvptr) /* = value? */
return SCPE_ARG;
else *((int32 *) mptr->desc) = mptr->match;
} /* end if xtd */
else { /* old style */
if (cvptr) /* = value? */
return SCPE_ARG;
if (uptr->flags & UNIT_DIS) /* disabled? */
return SCPE_UDIS;
if ((mptr->valid) && /* invalid? */
((r = mptr->valid (uptr, mptr->match, cvptr, mptr->desc)) != SCPE_OK))
return r;
uptr->flags = (uptr->flags & ~(mptr->mask)) |
(mptr->match & mptr->mask); /* set new value */
} /* end else xtd */
break; /* terminate for */
} /* end if match */
} /* end for */
if (!mptr || (mptr->mask == 0)) { /* no match? */
if ((glbr = find_c1tab (ctbr, gbuf))) { /* global match? */
r = glbr->action (dptr, uptr, glbr->arg, cvptr); /* do global */
if (r != SCPE_OK)
return r;
}
else if (!dptr->modifiers) /* no modifiers? */
return SCPE_NOPARAM;
else return SCPE_NXPAR;
} /* end if no mat */
} /* end while */
return SCPE_OK; /* done all */
}
/* Match CTAB/CTAB1 name */
CTAB *find_ctab (CTAB *tab, const char *gbuf)
{
if (!tab)
return NULL;
for (; tab->name != NULL; tab++) {
if (MATCH_CMD (gbuf, tab->name) == 0)
return tab;
}
return NULL;
}
C1TAB *find_c1tab (C1TAB *tab, const char *gbuf)
{
if (!tab)
return NULL;
for (; tab->name != NULL; tab++) {
if (MATCH_CMD (gbuf, tab->name) == 0)
return tab;
}
return NULL;
}
/* Set device data radix routine */
t_stat set_dev_radix (DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
if (cptr)
return SCPE_ARG;
dptr->dradix = flag & 037;
return SCPE_OK;
}
/* Set device enabled/disabled routine */
t_stat set_dev_enbdis (DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
UNIT *up;
uint32 i;
if (cptr)
return SCPE_ARG;
if ((dptr->flags & DEV_DISABLE) == 0) /* allowed? */
return SCPE_NOFNC;
if (flag) { /* enable? */
if ((dptr->flags & DEV_DIS) == 0) /* already enb? ok */
return SCPE_OK;
dptr->flags = dptr->flags & ~DEV_DIS; /* no, enable */
}
else {
if (dptr->flags & DEV_DIS) /* already dsb? ok */
return SCPE_OK;
for (i = 0; i < dptr->numunits; i++) { /* check units */
up = (dptr->units) + i; /* att or active? */
if ((up->flags & UNIT_ATT) || sim_is_active (up))
return SCPE_NOFNC; /* can't do it */
}
dptr->flags = dptr->flags | DEV_DIS; /* disable */
}
if (dptr->reset) /* reset device */
return dptr->reset (dptr);
else return SCPE_OK;
}
/* Set unit enabled/disabled routine */
t_stat set_unit_enbdis (DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
if (cptr)
return SCPE_ARG;
if (!(uptr->flags & UNIT_DISABLE)) /* allowed? */
return SCPE_NOFNC;
if (flag) /* enb? enable */
uptr->flags = uptr->flags & ~UNIT_DIS;
else {
if ((uptr->flags & UNIT_ATT) || /* dsb */
sim_is_active (uptr)) /* more tests */
return SCPE_NOFNC;
uptr->flags = uptr->flags | UNIT_DIS; /* disable */
}
return SCPE_OK;
}
/* Set device debug enabled/disabled routine */
t_stat set_dev_debug (DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
char gbuf[CBUFSIZE];
DEBTAB *dep;
if ((dptr->flags & DEV_DEBUG) == 0)
return SCPE_NOFNC;
if (cptr == NULL) { /* no arguments? */
dptr->dctrl = flag ? (dptr->debflags ? flag : 0xFFFFFFFF) : 0;/* disable/enable w/o table */
if (flag && dptr->debflags) { /* enable with table? */
for (dep = dptr->debflags; dep->name != NULL; dep++)
dptr->dctrl = dptr->dctrl | dep->mask; /* set all */
}
return SCPE_OK;
}
if (dptr->debflags == NULL) /* must have table */
return SCPE_ARG;
while (*cptr) {
cptr = get_glyph (cptr, gbuf, ';'); /* get debug flag */
for (dep = dptr->debflags; dep->name != NULL; dep++) {
if (strcmp (dep->name, gbuf) == 0) { /* match? */
if (flag)
dptr->dctrl = dptr->dctrl | dep->mask;
else dptr->dctrl = dptr->dctrl & ~dep->mask;
break;
}
} /* end for */
if (dep->mask == 0) /* no match? */
return SCPE_ARG;
} /* end while */
return SCPE_OK;
}
/* Show command */
t_stat show_cmd (int32 flag, char *cptr)
{
t_stat r;
cptr = get_sim_opt (CMD_OPT_SW|CMD_OPT_OF, cptr, &r); /* get sw, ofile */
if (!cptr) /* error? */
return r;
if (sim_ofile) { /* output file? */
r = show_cmd_fi (sim_ofile, flag, cptr); /* do show */
fclose (sim_ofile);
}
else {
r = show_cmd_fi (stdout, flag, cptr); /* no, stdout, log */
if (sim_log)
show_cmd_fi (sim_log, flag, cptr);
}
return r;
}
t_stat show_cmd_fi (FILE *ofile, int32 flag, char *cptr)
{
uint32 lvl = 0xFFFFFFFF;
char gbuf[CBUFSIZE], *cvptr, *svptr;
DEVICE *dptr;
UNIT *uptr;
MTAB *mptr;
SHTAB *shtb = NULL, *shptr;
GET_SWITCHES (cptr); /* get switches */
if (*cptr == 0) /* must be more */
return SCPE_2FARG;
cptr = get_glyph (svptr = cptr, gbuf, 0); /* get next glyph */
if ((dptr = find_dev (gbuf))) { /* device match? */
uptr = dptr->units; /* first unit */
shtb = show_dev_tab; /* global table */
lvl = MTAB_VDV; /* device match */
GET_SWITCHES (cptr); /* get more switches */
}
else if ((dptr = find_unit (gbuf, &uptr))) { /* unit match? */
if (uptr == NULL) /* invalid unit */
return SCPE_NXUN;
if (uptr->flags & UNIT_DIS) /* disabled? */
return SCPE_UDIS;
shtb = show_unit_tab; /* global table */
lvl = MTAB_VUN; /* unit match */
GET_SWITCHES (cptr); /* get more switches */
}
else if ((shptr = find_shtab (show_glob_tab, gbuf))) { /* global? */
GET_SWITCHES (cptr); /* get more switches */
return shptr->action (ofile, NULL, NULL, shptr->arg, cptr);
}
else {
if (sim_dflt_dev->modifiers) {
if ((cvptr = strchr (gbuf, '='))) /* = value? */
*cvptr++ = 0;
for (mptr = sim_dflt_dev->modifiers; mptr->mask != 0; mptr++) {
if ((((mptr->mask & MTAB_VDV) == MTAB_VDV) &&
(mptr->pstring && (MATCH_CMD (gbuf, mptr->pstring) == 0))) ||
(!(mptr->mask & MTAB_VDV) && (mptr->mstring && (MATCH_CMD (gbuf, mptr->mstring) == 0)))) {
dptr = sim_dflt_dev;
lvl = MTAB_VDV; /* device match */
cptr = svptr;
while (sim_isspace(*cptr))
++cptr;
break;
}
}
}
if (!dptr)
return SCPE_NXDEV; /* no match */
}
if (*cptr == 0) { /* now eol? */
return (lvl == MTAB_VDV)?
show_device (ofile, dptr, 0):
show_unit (ofile, dptr, uptr, -1);
}
GET_SWITCHES (cptr); /* get more switches */
while (*cptr != 0) { /* do all mods */
cptr = get_glyph (cptr, gbuf, ','); /* get modifier */
if ((cvptr = strchr (gbuf, '='))) /* = value? */
*cvptr++ = 0;
for (mptr = dptr->modifiers; mptr && (mptr->mask != 0); mptr++) {
if (((mptr->mask & MTAB_XTD)? /* right level? */
((mptr->mask & lvl) == lvl): (MTAB_VUN & lvl)) &&
((mptr->disp && mptr->pstring && /* named disp? */
(MATCH_CMD (gbuf, mptr->pstring) == 0))
// ||
// ((mptr->mask & MTAB_VAL) && /* named value? */
// mptr->mstring &&
// (MATCH_CMD (gbuf, mptr->mstring) == 0)))
)) {
if (cvptr && !(mptr->mask & MTAB_SHP))
return SCPE_ARG;
show_one_mod (ofile, dptr, uptr, mptr, cvptr, 1);
break;
} /* end if */
} /* end for */
if (!mptr || (mptr->mask == 0)) { /* no match? */
if (shtb && (shptr = find_shtab (shtb, gbuf))) {/* global match? */
t_stat r;
r = shptr->action (ofile, dptr, uptr, shptr->arg, cptr);
if (r != SCPE_OK)
return r;
}
else if (!dptr->modifiers) /* no modifiers? */
return SCPE_NOPARAM;
else
return SCPE_NXPAR;
} /* end if */
} /* end while */
return SCPE_OK;
}
SHTAB *find_shtab (SHTAB *tab, const char *gbuf)
{
if (!tab)
return NULL;
for (; tab->name != NULL; tab++) {
if (MATCH_CMD (gbuf, tab->name) == 0)
return tab;
}
return NULL;
}
/* Show device and unit */
t_stat show_device (FILE *st, DEVICE *dptr, int32 flag)
{
uint32 j, udbl, ucnt;
UNIT *uptr;
int32 toks = 0;
fprintf (st, "%s", sim_dname (dptr)); /* print dev name */
if ((flag == 2) && dptr->description) {
fprintf (st, "\t%s\n", dptr->description(dptr));
}
if (qdisable (dptr)) { /* disabled? */
fprintf (st, "\tdisabled\n");
return SCPE_OK;
}
for (j = ucnt = udbl = 0; j < dptr->numunits; j++) { /* count units */
uptr = dptr->units + j;
if (!(uptr->flags & UNIT_DIS)) /* count enabled units */
ucnt++;
else if (uptr->flags & UNIT_DISABLE)
udbl++; /* count user-disabled */
}
show_all_mods (st, dptr, dptr->units, MTAB_VDV, &toks); /* show dev mods */
if (dptr->numunits == 0) {
if (toks)
fprintf (st, "\n");
}
else {
if (ucnt == 0) {
fprint_sep (st, &toks);
fprintf (st, "all units disabled\n");
}
else if ((ucnt > 1) || (udbl > 0)) {
fprint_sep (st, &toks);
fprintf (st, "%d units\n", ucnt + udbl);
}
else
if ((flag != 2) || !dptr->description || toks)
fprintf (st, "\n");
toks = 0;
}
if (flag) /* dev only? */
return SCPE_OK;
for (j = 0; j < dptr->numunits; j++) { /* loop thru units */
uptr = dptr->units + j;
if ((uptr->flags & UNIT_DIS) == 0)
show_unit (st, dptr, uptr, ucnt + udbl);
}
return SCPE_OK;
}
void fprint_sep (FILE *st, int32 *tokens)
{
fprintf (st, (*tokens > 0) ? ", " : "\t");
*tokens += 1;
}
t_stat show_unit (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag)
{
int32 u = (int32)(uptr - dptr->units);
int32 toks = 0;
if (flag > 1)
fprintf (st, " %s%d", sim_dname (dptr), u);
else if (flag < 0)
fprintf (st, "%s%d", sim_dname (dptr), u);
if (uptr->flags & UNIT_FIX) {
fprint_sep (st, &toks);
fprint_capac (st, dptr, uptr);
}
if (uptr->flags & UNIT_ATT) {
fprint_sep (st, &toks);
fprintf (st, "attached to %s", uptr->filename);
if (uptr->flags & UNIT_RO)
fprintf (st, ", read only");
}
else {
if (uptr->flags & UNIT_ATTABLE) {
fprint_sep (st, &toks);
fprintf (st, "not attached");
}
}
show_all_mods (st, dptr, uptr, MTAB_VUN, &toks); /* show unit mods */
if (toks || (flag < 0) || (flag > 1))
fprintf (st, "\n");
return SCPE_OK;
}
void fprint_capac (FILE *st, DEVICE *dptr, UNIT *uptr)
{
t_addr kval = (uptr->flags & UNIT_BINK)? 1024: 1000;
t_addr mval;
t_addr psize = uptr->capac;
char scale, width;
if (sim_switches & SWMASK ('B'))
kval = 1024;
mval = kval * kval;
if (dptr->flags & DEV_SECTORS) {
kval = kval / 512;
mval = mval / 512;
}
if ((dptr->dwidth / dptr->aincr) > 8)
width = 'W';
else width = 'B';
if (uptr->capac < (kval * 10))
scale = 0;
else if (uptr->capac < (mval * 10)) {
scale = 'K';
psize = psize / kval;
}
else {
scale = 'M';
psize = psize / mval;
}
fprint_val (st, (t_value) psize, 10, T_ADDR_W, PV_LEFT);
if (scale)
fputc (scale, st);
fputc (width, st);
return;
}
/* Show <global name> processors */
t_stat show_version (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
int32 vmaj = SIM_MAJOR, vmin = SIM_MINOR, vpat = SIM_PATCH, vdelt = SIM_DELTA;
if (cptr && (*cptr != 0))
return SCPE_2MARG;
fprintf (st, "%s simulator V%d.%d-%d", sim_name, vmaj, vmin, vpat);
if (vdelt)
fprintf (st, " delta %d", vdelt);
#if defined (SIM_VERSION_MODE)
fprintf (st, " %s", SIM_VERSION_MODE);
#endif
if (flag) {
uint32 idle_capable, os_tick_size;
fprintf (st, "\n\tSimulator Framework Capabilities:");
fprintf (st, "\n\t\t%s", sim_si64);
fprintf (st, "\n\t\t%s", sim_sa64);
fprintf (st, "\n\t\t%s", eth_capabilities());
idle_capable = sim_timer_idle_capable (&os_tick_size);
fprintf (st, "\n\t\tIdle/Throttling support is %savailable", ((idle_capable == 0) ? "NOT " : ""));
if (sim_disk_vhd_support())
fprintf (st, "\n\t\tVirtual Hard Disk (VHD) support");
if (sim_disk_raw_support())
fprintf (st, "\n\t\tRAW disk and CD/DVD ROM support");
#if defined (SIM_ASYNCH_IO)
fprintf (st, "\n\t\tAsynchronous I/O support");
#endif
#if defined (SIM_ASYNCH_MUX)
fprintf (st, "\n\t\tAsynchronous Multiplexer support");
#endif
#if defined (SIM_ASYNCH_CLOCKS)
fprintf (st, "\n\t\tAsynchronous Clock support");
#endif
fprintf (st, "\n\tHost Platform:");
#if defined (__GNUC__) && defined (__VERSION__)
fprintf (st, "\n\t\tCompiler: GCC %s", __VERSION__);
#elif defined (__clang_version__)
fprintf (st, "\n\t\tCompiler: clang %s", __clang_version__);
#elif defined (_MSC_FULL_VER) && defined (_MSC_BUILD)
fprintf (st, "\n\t\tCompiler: Microsoft Visual C++ %d.%02d.%05d.%02d", _MSC_FULL_VER/10000000, (_MSC_FULL_VER/100000)%100, _MSC_FULL_VER%100000, _MSC_BUILD);
#elif defined (__DECC_VER)
fprintf (st, "\n\t\tCompiler: DEC C %c%d.%d-%03d", ("T SV")[((__DECC_VER/10000)%10)-6], __DECC_VER/10000000, (__DECC_VER/100000)%100, __DECC_VER%10000);
#elif defined (SIM_COMPILER)
#define S_xstr(a) S_str(a)
#define S_str(a) #a
fprintf (st, "\n\t\tCompiler: %s", S_xstr(SIM_COMPILER));
#undef S_str
#undef S_xstr
#endif
#if defined (__DATE__) && defined (__TIME__)
fprintf (st, "\n\t\tSimulator Compiled: %s at %s", __DATE__, __TIME__);
#endif
fprintf (st, "\n\t\tMemory Access: %s Endian", sim_end ? "Little" : "Big");
fprintf (st, "\n\t\tMemory Pointer Size: %d bits", (int)sizeof(dptr)*8);
fprintf (st, "\n\t\t%s", sim_toffset_64 ? "Large File (>2GB) support" : "No Large File support");
#if defined (USE_SIM_VIDEO)
fprintf (st, "\n\t\tSDL Video support: %s", vid_version());
#endif
#if defined (HAVE_PCREPOSIX_H)
fprintf (st, "\n\t\tPCRE RegEx support for EXPECT commands");
#elif defined (HAVE_REGEX_H)
fprintf (st, "\n\t\tRegEx support for EXPECT commands");
#else
fprintf (st, "\n\t\tNo RegEx support for EXPECT commands");
#endif
fprintf (st, "\n\t\tOS clock tick size (time taken by msleep(1)): %dms", os_tick_size);
#if defined(__VMS)
if (1) {
char *arch =
#if defined(__ia64)
"I64";
#elif defined(__ALPHA)
"Alpha";
#else
"VAX";
#endif
fprintf (st, "\n\t\tOS: OpenVMS %s %s", arch, __VMS_VERSION);
}
#elif defined(_WIN32)
fprintf (st, "\n\t\tOS: Windows: ");
fflush (st);
system ("ver");
system ("echo \t\t%PROCESSOR_IDENTIFIER% - %PROCESSOR_ARCHITECTURE%-%PROCESSOR_ARCHITEW6432%");
#else
fprintf (st, "\n\t\tOS: ");
fflush (st);
system ("uname -a");
#endif
}
#if defined(SIM_GIT_COMMIT_ID)
#define S_xstr(a) S_str(a)
#define S_str(a) #a
fprintf (st, "%sgit commit id: %8.8s", flag ? "\n " : " ", S_xstr(SIM_GIT_COMMIT_ID));
#undef S_str
#undef S_xstr
#endif
#if defined(SIM_BUILD)
#define S_xstr(a) S_str(a)
#define S_str(a) #a
fprintf (st, "%sBuild: %s", flag ? "\n " : " ", S_xstr(SIM_BUILD));
#undef S_str
#undef S_xstr
#endif
fprintf (st, "\n");
return SCPE_OK;
}
t_stat show_config (FILE *st, DEVICE *dnotused, UNIT *unotused, int32 flag, char *cptr)
{
int32 i;
DEVICE *dptr;
t_bool only_enabled = (sim_switches & SWMASK ('E'));
if (cptr && (*cptr != 0))
return SCPE_2MARG;
fprintf (st, "%s simulator configuration%s\n\n", sim_name, only_enabled ? " (enabled devices)" : "");
for (i = 0; (dptr = sim_devices[i]) != NULL; i++)
if (!only_enabled || !qdisable (dptr))
show_device (st, dptr, flag);
return SCPE_OK;
}
t_stat show_log_names (FILE *st, DEVICE *dnotused, UNIT *unotused, int32 flag, char *cptr)
{
int32 i;
DEVICE *dptr;
if (cptr && (*cptr != 0))
return SCPE_2MARG;
for (i = 0; (dptr = sim_devices[i]) != NULL; i++)
show_dev_logicals (st, dptr, NULL, 1, cptr);
return SCPE_OK;
}
t_stat show_dev_logicals (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
if (dptr->lname)
fprintf (st, "%s -> %s\n", dptr->lname, dptr->name);
else if (!flag)
fputs ("no logical name assigned\n", st);
return SCPE_OK;
}
t_stat show_queue (FILE *st, DEVICE *dnotused, UNIT *unotused, int32 flag, char *cptr)
{
DEVICE *dptr;
UNIT *uptr;
int32 accum;
if (cptr && (*cptr != 0))
return SCPE_2MARG;
if (sim_clock_queue == QUEUE_LIST_END)
fprintf (st, "%s event queue empty, time = %.0f, executing %.0f instructios/sec\n",
sim_name, sim_time, sim_timer_inst_per_sec ());
else {
fprintf (st, "%s event queue status, time = %.0f, executing %.0f instructions/sec\n",
sim_name, sim_time, sim_timer_inst_per_sec ());
accum = 0;
for (uptr = sim_clock_queue; uptr != QUEUE_LIST_END; uptr = uptr->next) {
if (uptr == &sim_step_unit)
fprintf (st, " Step timer");
else
if (uptr == &sim_expect_unit)
fprintf (st, " Expect fired");
else
if ((dptr = find_dev_from_unit (uptr)) != NULL) {
fprintf (st, " %s", sim_dname (dptr));
if (dptr->numunits > 1)
fprintf (st, " unit %d", (int32) (uptr - dptr->units));
}
else
fprintf (st, " Unknown");
fprintf (st, " at %d\n", accum + uptr->time);
accum = accum + uptr->time;
}
}
sim_show_clock_queues (st, dnotused, unotused, flag, cptr);
#if defined (SIM_ASYNCH_IO)
pthread_mutex_lock (&sim_asynch_lock);
fprintf (st, "asynchronous pending event queue\n");
if (sim_asynch_queue == QUEUE_LIST_END)
fprintf (st, " Empty\n");
else {
for (uptr = sim_asynch_queue; uptr != QUEUE_LIST_END; uptr = uptr->a_next) {
if ((dptr = find_dev_from_unit (uptr)) != NULL) {
fprintf (st, " %s", sim_dname (dptr));
if (dptr->numunits > 1) fprintf (st, " unit %d",
(int32) (uptr - dptr->units));
}
else fprintf (st, " Unknown");
fprintf (st, " event delay %d\n", uptr->a_event_time);
}
}
fprintf (st, "asynch latency: %d nanoseconds\n", sim_asynch_latency);
fprintf (st, "asynch instruction latency: %d instructions\n", sim_asynch_inst_latency);
pthread_mutex_unlock (&sim_asynch_lock);
#endif /* SIM_ASYNCH_IO */
return SCPE_OK;
}
t_stat show_time (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
if (cptr && (*cptr != 0))
return SCPE_2MARG;
fprintf (st, "Time:\t%.0f\n", sim_time);
return SCPE_OK;
}
t_stat show_break (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
t_stat r;
if (cptr && (*cptr != 0))
r = ssh_break (st, cptr, 1); /* more? */
else r = sim_brk_showall (st, sim_switches);
return r;
}
t_stat show_dev_radix (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
fprintf (st, "Radix=%d\n", dptr->dradix);
return SCPE_OK;
}
t_stat show_dev_debug (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
int32 any = 0;
DEBTAB *dep;
if (dptr->flags & DEV_DEBUG) {
if (dptr->dctrl == 0)
fputs ("Debugging disabled", st);
else if (dptr->debflags == NULL)
fputs ("Debugging enabled", st);
else {
fputs ("Debug=", st);
for (dep = dptr->debflags; dep->name != NULL; dep++) {
if ((dptr->dctrl & dep->mask) == dep->mask) {
if (any)
fputc (';', st);
fputs (dep->name, st);
any = 1;
}
}
}
fputc ('\n', st);
return SCPE_OK;
}
else return SCPE_NOFNC;
}
/* Show On actions */
t_stat show_on (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
int32 lvl, i;
if (cptr && (*cptr != 0)) return SCPE_2MARG; /* now eol? */
for (lvl=sim_do_depth; lvl >= 0; --lvl) {
if (lvl > 0)
fprintf(st, "On Processing at Do Nest Level: %d", lvl);
else
fprintf(st, "On Processing for input commands");
fprintf(st, " is %s\n", (sim_on_check[lvl]) ? "enabled" : "disabled");
for (i=1; i<SCPE_BASE; ++i) {
if (sim_on_actions[lvl][i])
fprintf(st, " on %5d %s\n", i, sim_on_actions[lvl][i]); }
for (i=SCPE_BASE; i<=SCPE_MAX_ERR; ++i) {
if (sim_on_actions[lvl][i])
fprintf(st, " on %-5s %s\n", scp_errors[i-SCPE_BASE].code, sim_on_actions[lvl][i]); }
if (sim_on_actions[lvl][0])
fprintf(st, " on ERROR %s\n", sim_on_actions[lvl][0]);
fprintf(st, "\n");
}
if (sim_on_inherit)
fprintf(st, "on state and actions are inherited by nested do commands and subroutines\n");
return SCPE_OK;
}
/* Show modifiers */
t_stat show_mod_names (FILE *st, DEVICE *dnotused, UNIT *unotused, int32 flag, char *cptr)
{
int32 i;
DEVICE *dptr;
if (cptr && (*cptr != 0)) /* now eol? */
return SCPE_2MARG;
for (i = 0; (dptr = sim_devices[i]) != NULL; i++)
show_dev_modifiers (st, dptr, NULL, flag, cptr);
for (i = 0; sim_internal_device_count && (dptr = sim_internal_devices[i]); ++i)
show_dev_modifiers (st, dptr, NULL, flag, cptr);
return SCPE_OK;
}
t_stat show_dev_modifiers (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
fprint_set_help (st, dptr);
return SCPE_OK;
}
t_stat show_all_mods (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, int32 *toks)
{
MTAB *mptr;
t_stat r = SCPE_OK;
if (dptr->modifiers == NULL)
return SCPE_OK;
for (mptr = dptr->modifiers; mptr->mask != 0; mptr++) {
if (mptr->pstring &&
((mptr->mask & MTAB_XTD)?
(MODMASK(mptr,flag) && !MODMASK(mptr,MTAB_NMO)):
((MTAB_VUN == (uint32)flag) && ((uptr->flags & mptr->mask) == mptr->match)))) {
if (*toks > 2) {
fprintf (st, "\n");
*toks = 0;
}
if (r == SCPE_OK)
fprint_sep (st, toks);
r = show_one_mod (st, dptr, uptr, mptr, NULL, 0);
}
}
return SCPE_OK;
}
t_stat show_one_mod (FILE *st, DEVICE *dptr, UNIT *uptr, MTAB *mptr,
char *cptr, int32 flag)
{
t_stat r = SCPE_OK;
//t_value val;
if (mptr->disp)
r = mptr->disp (st, uptr, mptr->match, cptr? cptr: mptr->desc);
//else if ((mptr->mask & MTAB_XTD) && (mptr->mask & MTAB_VAL)) {
// REG *rptr = (REG *) mptr->desc;
// fprintf (st, "%s=", mptr->pstring);
// val = get_rval (rptr, 0);
// fprint_val (st, val, rptr->radix, rptr->width,
// rptr->flags & REG_FMT);
// }
else fputs (mptr->pstring, st);
if ((r == SCPE_OK) && (flag && !((mptr->mask & MTAB_XTD) && MODMASK(mptr,MTAB_NMO))))
fputc ('\n', st);
return r;
}
/* Show show commands */
t_stat show_show_commands (FILE *st, DEVICE *dnotused, UNIT *unotused, int32 flag, char *cptr)
{
int32 i;
DEVICE *dptr;
if (cptr && (*cptr != 0)) /* now eol? */
return SCPE_2MARG;
for (i = 0; (dptr = sim_devices[i]) != NULL; i++)
show_dev_show_commands (st, dptr, NULL, flag, cptr);
for (i = 0; sim_internal_device_count && (dptr = sim_internal_devices[i]); ++i)
show_dev_show_commands (st, dptr, NULL, flag, cptr);
return SCPE_OK;
}
t_stat show_dev_show_commands (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
fprint_show_help (st, dptr);
return SCPE_OK;
}
/* Show/change the current working directiory commands */
t_stat show_default (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
char buffer[PATH_MAX];
char *wd = getcwd(buffer, PATH_MAX);
fprintf (st, "%s\n", wd);
return SCPE_OK;
}
t_stat set_default_cmd (int32 flg, char *cptr)
{
if (sim_is_running)
return SCPE_INVREM;
if ((!cptr) || (*cptr == 0))
return SCPE_2FARG;
sim_trim_endspc(cptr);
if (chdir(cptr) != 0)
return sim_messagef(SCPE_IOERR, "Unable to directory change to: %s\n", cptr);
return SCPE_OK;
}
t_stat pwd_cmd (int32 flg, char *cptr)
{
return show_cmd (0, "DEFAULT");
}
#if defined (_WIN32)
t_stat dir_cmd (int32 flg, char *cptr)
{
HANDLE hFind;
WIN32_FIND_DATAA File;
struct stat filestat;
char WildName[PATH_MAX + 1];
if (*cptr == '\0')
cptr = "./*";
if ((!stat (cptr, &filestat)) && (filestat.st_mode & S_IFDIR)) {
sprintf (WildName, "%s%c*", cptr, strchr (cptr, '/') ? '/' : '\\');
cptr = WildName;
}
if ((hFind = FindFirstFileA (cptr, &File)) != INVALID_HANDLE_VALUE) {
t_int64 FileSize, TotalSize = 0;
int DirCount = 0, FileCount = 0;
char DirName[PATH_MAX + 1], FileName[PATH_MAX + 1];
char *c, pathsep = '/';
struct tm *local;
GetFullPathNameA(cptr, sizeof(DirName), DirName, &c);
c = strrchr(DirName, pathsep);
if (NULL == c) {
pathsep = '\\';
c = strrchr(cptr, pathsep);
}
if (c) {
memcpy(DirName, cptr, c - cptr);
DirName[c - cptr] = '\0';
}
else {
getcwd(DirName, PATH_MAX);
}
sim_printf (" Directory of %s\n\n", DirName);
do {
FileSize = (((t_int64)(File.nFileSizeHigh)) << 32) | File.nFileSizeLow;
sprintf (FileName, "%s%c%s", DirName, pathsep, File.cFileName);
stat (FileName, &filestat);
local = localtime (&filestat.st_mtime);
sim_printf ("%02d/%02d/%04d %02d:%02d %s ", local->tm_mon+1, local->tm_mday, 1900+local->tm_year, local->tm_hour%12, local->tm_min, (local->tm_hour >= 12) ? "PM" : "AM");
if (filestat.st_mode & S_IFDIR) {
++DirCount;
sim_printf (" <DIR> ");
}
else {
if (filestat.st_mode & S_IFREG) {
++FileCount;
sim_print_val ((t_value) FileSize, 10, 17, PV_RCOMMA);
TotalSize += FileSize;
}
else {
sim_printf ("%17s", "");
}
}
sim_printf (" %s\n", File.cFileName);
} while (FindNextFile (hFind, &File));
sim_printf ("%16d File(s)", FileCount);
sim_print_val ((t_value) TotalSize, 10, 15, PV_RCOMMA);
sim_printf (" bytes\n");
sim_printf ("%16d Dir(s)\n", DirCount);
FindClose (hFind);
}
else {
sim_printf ("Can't list files for %s\n", cptr);
return SCPE_ARG;
}
return SCPE_OK;
}
#else /* !defined (_WIN32) */
#if defined (HAVE_GLOB)
#include <glob.h>
#else /* !defined (HAVE_GLOB) */
#include <dirent.h>
#if defined (HAVE_FNMATCH)
#include <fnmatch.h>
#endif
#endif /* defined (HAVE_GLOB) */
t_stat dir_cmd (int32 flg, char *cptr)
{
#if defined (HAVE_GLOB)
glob_t paths;
#else
DIR *dir;
#endif
struct stat filestat;
char *c;
char DirName[PATH_MAX + 1], WholeName[PATH_MAX + 1], WildName[PATH_MAX + 1];
if (*cptr == '\0')
cptr = "./*";
strcpy (WildName, cptr);
cptr = WildName;
while (strlen(WildName) && sim_isspace(WildName[strlen(WildName)-1]))
WildName[strlen(WildName)-1] = '\0';
if ((!stat (WildName, &filestat)) && (filestat.st_mode & S_IFDIR))
strcat (WildName, "/*");
if ((*cptr != '/') || (0 == memcmp (cptr, "./", 2)) || (0 == memcmp (cptr, "../", 3))) {
#if defined (VMS)
getcwd (WholeName, PATH_MAX, 0);
#else
getcwd (WholeName, PATH_MAX);
#endif
strcat (WholeName, "/");
strcat (WholeName, cptr);
while (strlen(WholeName) && sim_isspace(WholeName[strlen(WholeName)-1]))
WholeName[strlen(WholeName)-1] = '\0';
}
while ((c = strstr (WholeName, "/./")))
strcpy (c + 1, c + 3);
while ((c = strstr (WholeName, "//")))
strcpy (c + 1, c + 2);
while ((c = strstr (WholeName, "/../"))) {
char *c1;
c1 = c - 1;
while ((c1 >= WholeName) && (*c1 != '/'))
c1 = c1 - 1;
strcpy (c1, c + 3);
while (0 == memcmp (WholeName, "/../", 4))
strcpy (WholeName, WholeName+3);
}
c = strrchr (WholeName, '/');
if (c) {
memcpy (DirName, WholeName, c-WholeName);
DirName[c-WholeName] = '\0';
}
else
#if defined (VMS)
getcwd (WholeName, PATH_MAX, 0);
#else
getcwd (WholeName, PATH_MAX);
#endif
cptr = WholeName;
#if defined (HAVE_GLOB)
memset (&paths, 0, sizeof(paths));
if (0 == glob (cptr, 0, NULL, &paths)) {
#else
dir = opendir(DirName[0] ? DirName : "/.");
if (dir) {
struct dirent *ent;
#endif
t_offset FileSize, TotalSize = 0;
int DirCount = 0, FileCount = 0;
char FileName[PATH_MAX + 1];
#if defined (HAVE_FNMATCH)
char *MatchName = 1 + strrchr (cptr, '/');;
#endif
char *c;
struct tm *local;
#if defined (HAVE_GLOB)
size_t i;
#endif
sim_printf (" Directory of %s\n\n", DirName[0] ? DirName : "/");
#if defined (HAVE_GLOB)
for (i=0; i<paths.gl_pathc; i++) {
sprintf (FileName, "%s", paths.gl_pathv[i]);
#else
while ((ent = readdir (dir))) {
#if defined (HAVE_FNMATCH)
if (fnmatch(MatchName, ent->d_name, 0))
continue;
#endif
sprintf (FileName, "%s/%s", DirName, ent->d_name);
#endif
stat (FileName, &filestat);
local = localtime (&filestat.st_mtime);
sim_printf ("%02d/%02d/%04d %02d:%02d %s ", local->tm_mon+1, local->tm_mday, 1900+local->tm_year, local->tm_hour%12, local->tm_min, (local->tm_hour >= 12) ? "PM" : "AM");
if (filestat.st_mode & S_IFDIR) {
++DirCount;
sim_printf (" <DIR> ");
}
else {
if (filestat.st_mode & S_IFREG) {
++FileCount;
FileSize = sim_fsize_name_ex (FileName);
sim_print_val ((t_value) FileSize, 10, 17, PV_RCOMMA);
TotalSize += FileSize;
}
else {
sim_printf ("%17s", "");
}
}
c = strrchr (FileName, '/');
sim_printf (" %s\n", c ? c + 1 : FileName);
}
if (FileCount) {
sim_printf ("%16d File(s)", FileCount);
sim_print_val ((t_value) TotalSize, 10, 15, PV_RCOMMA);
sim_printf (" bytes\n");
sim_printf ("%16d Dir(s)\n", DirCount);
}
else {
sim_printf ("File Not Found\n");
}
#if defined (HAVE_GLOB)
globfree (&paths);
#else
closedir (dir);
#endif
}
else {
sim_printf ("Can't list files for %s\n", cptr);
return SCPE_ARG;
}
return SCPE_OK;
}
#endif /* !defined(_WIN32) */
/* Breakpoint commands */
t_stat brk_cmd (int32 flg, char *cptr)
{
GET_SWITCHES (cptr); /* get switches */
return ssh_break (NULL, cptr, flg); /* call common code */
}
t_stat ssh_break (FILE *st, char *cptr, int32 flg)
{
char gbuf[CBUFSIZE], *aptr;
const char *tptr, *t1ptr;
DEVICE *dptr = sim_dflt_dev;
UNIT *uptr = dptr->units;
t_stat r;
t_addr lo, hi, max = uptr->capac - 1;
int32 cnt;
if (sim_brk_types == 0)
return SCPE_NOFNC;
if ((dptr == NULL) || (uptr == NULL))
return SCPE_IERR;
if ((aptr = strchr (cptr, ';'))) { /* ;action? */
if (flg != SSH_ST) /* only on SET */
return SCPE_ARG;
*aptr++ = 0; /* separate strings */
}
if (*cptr == 0) { /* no argument? */
lo = (t_addr) get_rval (sim_PC, 0); /* use PC */
return ssh_break_one (st, flg, lo, 0, aptr);
}
while (*cptr) {
cptr = get_glyph (cptr, gbuf, ',');
tptr = get_range (dptr, gbuf, &lo, &hi, dptr->aradix, max, 0);
if (tptr == NULL)
return SCPE_ARG;
if (*tptr == '[') {
cnt = (int32) strtotv (tptr + 1, &t1ptr, 10);
if ((tptr == t1ptr) || (*t1ptr != ']') || (flg != SSH_ST))
return SCPE_ARG;
tptr = t1ptr + 1;
}
else cnt = 0;
if (*tptr != 0)
return SCPE_ARG;
if ((lo == 0) && (hi == max)) {
if (flg == SSH_CL)
sim_brk_clrall (sim_switches);
else if (flg == SSH_SH)
sim_brk_showall (st, sim_switches);
else return SCPE_ARG;
}
else {
for ( ; lo <= hi; lo = lo + 1) {
r = ssh_break_one (st, flg, lo, cnt, aptr);
if (r != SCPE_OK)
return r;
}
}
}
return SCPE_OK;
}
t_stat ssh_break_one (FILE *st, int32 flg, t_addr lo, int32 cnt, char *aptr)
{
switch (flg) {
case SSH_ST:
return sim_brk_set (lo, sim_switches, cnt, aptr);
break;
case SSH_CL:
return sim_brk_clr (lo, sim_switches);
break;
case SSH_SH:
return sim_brk_show (st, lo, sim_switches);
break;
default:
return SCPE_ARG;
}
}
/* Reset command and routines
re[set] reset all devices
re[set] all reset all devices
re[set] device reset specific device
*/
t_stat reset_cmd (int32 flag, char *cptr)
{
char gbuf[CBUFSIZE];
DEVICE *dptr;
GET_SWITCHES (cptr); /* get switches */
if (*cptr == 0) /* reset(cr) */
return (reset_all (0));
cptr = get_glyph (cptr, gbuf, 0); /* get next glyph */
if (*cptr != 0) /* now eol? */
return SCPE_2MARG;
if (strcmp (gbuf, "ALL") == 0)
return (reset_all (0));
dptr = find_dev (gbuf); /* locate device */
if (dptr == NULL) /* found it? */
return SCPE_NXDEV;
if (dptr->reset != NULL)
return dptr->reset (dptr);
else return SCPE_OK;
}
/* Reset devices start..end
Inputs:
start = number of starting device
Outputs:
status = error status
*/
t_stat reset_all (uint32 start)
{
DEVICE *dptr;
uint32 i;
t_stat reason;
for (i = 0; i < start; i++) {
if (sim_devices[i] == NULL)
return SCPE_IERR;
}
for (i = start; (dptr = sim_devices[i]) != NULL; i++) {
if (dptr->reset != NULL) {
reason = dptr->reset (dptr);
if (reason != SCPE_OK)
return reason;
}
}
for (i = 0; sim_internal_device_count && (dptr = sim_internal_devices[i]); ++i) {
if (dptr->reset != NULL) {
reason = dptr->reset (dptr);
if (reason != SCPE_OK)
return reason;
}
}
return SCPE_OK;
}
/* Reset to powerup state
Inputs:
start = number of starting device
Outputs:
status = error status
*/
t_stat reset_all_p (uint32 start)
{
t_stat r;
int32 old_sw = sim_switches;
sim_switches = SWMASK ('P');
r = reset_all (start);
sim_switches = old_sw;
return r;
}
/* Load and dump commands
lo[ad] filename {arg} load specified file
du[mp] filename {arg} dump to specified file
*/
t_stat load_cmd (int32 flag, char *cptr)
{
char gbuf[CBUFSIZE];
FILE *loadfile;
t_stat reason;
GET_SWITCHES (cptr); /* get switches */
if (*cptr == 0) /* must be more */
return SCPE_2FARG;
cptr = get_glyph_nc (cptr, gbuf, 0); /* get file name */
loadfile = sim_fopen (gbuf, flag? "wb": "rb"); /* open for wr/rd */
if (loadfile == NULL)
return SCPE_OPENERR;
GET_SWITCHES (cptr); /* get switches */
reason = sim_load (loadfile, cptr, gbuf, flag); /* load or dump */
fclose (loadfile);
return reason;
}
/* Attach command
at[tach] unit file attach specified unit to file
*/
t_stat attach_cmd (int32 flag, char *cptr)
{
char gbuf[CBUFSIZE];
DEVICE *dptr;
UNIT *uptr;
t_stat r;
GET_SWITCHES (cptr); /* get switches */
if (*cptr == 0) /* must be more */
return SCPE_2FARG;
cptr = get_glyph (cptr, gbuf, 0); /* get next glyph */
GET_SWITCHES (cptr); /* get switches */
if (*cptr == 0) /* now eol? */
return SCPE_2FARG;
dptr = find_unit (gbuf, &uptr); /* locate unit */
if (dptr == NULL) /* found dev? */
return SCPE_NXDEV;
if (uptr == NULL) /* valid unit? */
return SCPE_NXUN;
if (uptr->flags & UNIT_ATT) { /* already attached? */
if (!(uptr->dynflags & UNIT_ATTMULT) && /* and only single attachable */
!(dptr->flags & DEV_DONTAUTO)) { /* and auto detachable */
r = scp_detach_unit (dptr, uptr); /* detach it */
if (r != SCPE_OK) /* error? */
return r;
}
else {
if (!(uptr->dynflags & UNIT_ATTMULT))
return SCPE_ALATT; /* Already attached */
}
}
sim_trim_endspc (cptr); /* trim trailing spc */
return scp_attach_unit (dptr, uptr, cptr); /* attach */
}
/* Call device-specific or file-oriented attach unit routine */
t_stat scp_attach_unit (DEVICE *dptr, UNIT *uptr, char *cptr)
{
if (dptr->attach != NULL) /* device routine? */
return dptr->attach (uptr, cptr); /* call it */
return attach_unit (uptr, cptr); /* no, std routine */
}
/* Attach unit to file */
t_stat attach_unit (UNIT *uptr, char *cptr)
{
DEVICE *dptr;
if (uptr->flags & UNIT_DIS) /* disabled? */
return SCPE_UDIS;
if (!(uptr->flags & UNIT_ATTABLE)) /* not attachable? */
return SCPE_NOATT;
if ((dptr = find_dev_from_unit (uptr)) == NULL)
return SCPE_NOATT;
uptr->filename = (char *) calloc (CBUFSIZE, sizeof (char)); /* alloc name buf */
if (uptr->filename == NULL)
return SCPE_MEM;
strncpy (uptr->filename, cptr, CBUFSIZE); /* save name */
if (sim_switches & SWMASK ('R')) { /* read only? */
if ((uptr->flags & UNIT_ROABLE) == 0) /* allowed? */
return attach_err (uptr, SCPE_NORO); /* no, error */
uptr->fileref = sim_fopen (cptr, "rb"); /* open rd only */
if (uptr->fileref == NULL) /* open fail? */
return attach_err (uptr, SCPE_OPENERR); /* yes, error */
uptr->flags = uptr->flags | UNIT_RO; /* set rd only */
if (!sim_quiet && !(sim_switches & SWMASK ('Q'))) {
sim_printf ("%s: unit is read only\n", sim_dname (dptr));
}
}
else {
if (sim_switches & SWMASK ('N')) { /* new file only? */
uptr->fileref = sim_fopen (cptr, "wb+"); /* open new file */
if (uptr->fileref == NULL) /* open fail? */
return attach_err (uptr, SCPE_OPENERR); /* yes, error */
if (!sim_quiet && !(sim_switches & SWMASK ('Q'))) {
sim_printf ("%s: creating new file\n", sim_dname (dptr));
}
}
else { /* normal */
uptr->fileref = sim_fopen (cptr, "rb+"); /* open r/w */
if (uptr->fileref == NULL) { /* open fail? */
#if defined(EPERM)
if ((errno == EROFS) || (errno == EACCES) || (errno == EPERM)) {/* read only? */
#else
if ((errno == EROFS) || (errno == EACCES)) {/* read only? */
#endif
if ((uptr->flags & UNIT_ROABLE) == 0) /* allowed? */
return attach_err (uptr, SCPE_NORO);/* no error */
uptr->fileref = sim_fopen (cptr, "rb"); /* open rd only */
if (uptr->fileref == NULL) /* open fail? */
return attach_err (uptr, SCPE_OPENERR); /* yes, error */
uptr->flags = uptr->flags | UNIT_RO; /* set rd only */
if (!sim_quiet) {
sim_printf ("%s: unit is read only\n", sim_dname (dptr));
}
}
else { /* doesn't exist */
if (sim_switches & SWMASK ('E')) /* must exist? */
return attach_err (uptr, SCPE_OPENERR); /* yes, error */
uptr->fileref = sim_fopen (cptr, "wb+");/* open new file */
if (uptr->fileref == NULL) /* open fail? */
return attach_err (uptr, SCPE_OPENERR); /* yes, error */
if (!sim_quiet) {
sim_printf ("%s: creating new file\n", sim_dname (dptr));
}
}
} /* end if null */
} /* end else */
}
if (uptr->flags & UNIT_BUFABLE) { /* buffer? */
uint32 cap = ((uint32) uptr->capac) / dptr->aincr; /* effective size */
if (uptr->flags & UNIT_MUSTBUF) /* dyn alloc? */
uptr->filebuf = calloc (cap, SZ_D (dptr)); /* allocate */
if (uptr->filebuf == NULL) /* no buffer? */
return attach_err (uptr, SCPE_MEM); /* error */
if (!sim_quiet) {
sim_printf ("%s: buffering file in memory\n", sim_dname (dptr));
}
uptr->hwmark = (uint32)sim_fread (uptr->filebuf, /* read file */
SZ_D (dptr), cap, uptr->fileref);
uptr->flags = uptr->flags | UNIT_BUF; /* set buffered */
}
uptr->flags = uptr->flags | UNIT_ATT;
uptr->pos = 0;
return SCPE_OK;
}
t_stat attach_err (UNIT *uptr, t_stat stat)
{
free (uptr->filename);
uptr->filename = NULL;
return stat;
}
/* Detach command
det[ach] all detach all units
det[ach] unit detach specified unit
*/
t_stat detach_cmd (int32 flag, char *cptr)
{
char gbuf[CBUFSIZE];
DEVICE *dptr;
UNIT *uptr;
GET_SWITCHES (cptr); /* get switches */
if (*cptr == 0) /* must be more */
return SCPE_2FARG;
cptr = get_glyph (cptr, gbuf, 0); /* get next glyph */
if (*cptr != 0) /* now eol? */
return SCPE_2MARG;
if (strcmp (gbuf, "ALL") == 0)
return (detach_all (0, FALSE));
dptr = find_unit (gbuf, &uptr); /* locate unit */
if (dptr == NULL) /* found dev? */
return SCPE_NXDEV;
if (uptr == NULL) /* valid unit? */
return SCPE_NXUN;
return scp_detach_unit (dptr, uptr); /* detach */
}
/* Detach devices start..end
Inputs:
start = number of starting device
shutdown = TRUE if simulator shutting down
Outputs:
status = error status
Note that during shutdown, detach routines for non-attachable devices
will be called. These routines can implement simulator shutdown. Error
returns during shutdown are ignored.
*/
t_stat detach_all (int32 start, t_bool shutdown)
{
uint32 i, j;
DEVICE *dptr;
UNIT *uptr;
t_stat r;
if ((start < 0) || (start > 1))
return SCPE_IERR;
for (i = start; (dptr = sim_devices[i]) != NULL; i++) { /* loop thru dev */
for (j = 0; j < dptr->numunits; j++) { /* loop thru units */
uptr = (dptr->units) + j;
if ((uptr->flags & UNIT_ATT) || /* attached? */
(shutdown && dptr->detach && /* shutdown, spec rtn, */
!(uptr->flags & UNIT_ATTABLE))) { /* !attachable? */
r = scp_detach_unit (dptr, uptr); /* detach unit */
if ((r != SCPE_OK) && !shutdown) /* error and not shutting down? */
return r; /* bail out now with error status */
}
}
}
return SCPE_OK;
}
/* Call device-specific or file-oriented detach unit routine */
t_stat scp_detach_unit (DEVICE *dptr, UNIT *uptr)
{
if (dptr->detach != NULL) /* device routine? */
return dptr->detach (uptr);
return detach_unit (uptr); /* no, standard */
}
/* Detach unit from file */
t_stat detach_unit (UNIT *uptr)
{
DEVICE *dptr;
if (uptr == NULL)
return SCPE_IERR;
if (!(uptr->flags & UNIT_ATTABLE)) /* attachable? */
return SCPE_NOATT;
if (!(uptr->flags & UNIT_ATT)) { /* not attached? */
if (sim_switches & SIM_SW_REST) /* restoring? */
return SCPE_OK; /* allow detach */
else
return SCPE_NOATT; /* complain */
}
if ((dptr = find_dev_from_unit (uptr)) == NULL)
return SCPE_OK;
if (uptr->flags & UNIT_BUF) {
uint32 cap = (uptr->hwmark + dptr->aincr - 1) / dptr->aincr;
if (uptr->hwmark && ((uptr->flags & UNIT_RO) == 0)) {
if (!sim_quiet) {
sim_printf ("%s: writing buffer to file\n", sim_dname (dptr));
}
rewind (uptr->fileref);
sim_fwrite (uptr->filebuf, SZ_D (dptr), cap, uptr->fileref);
if (ferror (uptr->fileref))
perror ("I/O error");
}
if (uptr->flags & UNIT_MUSTBUF) { /* dyn alloc? */
free (uptr->filebuf); /* free buf */
uptr->filebuf = NULL;
}
uptr->flags = uptr->flags & ~UNIT_BUF;
}
uptr->flags = uptr->flags & ~(UNIT_ATT | UNIT_RO);
free (uptr->filename);
uptr->filename = NULL;
if (fclose (uptr->fileref) == EOF)
return SCPE_IOERR;
return SCPE_OK;
}
/* Assign command
as[sign] device name assign logical name to device
*/
t_stat assign_cmd (int32 flag, char *cptr)
{
char gbuf[CBUFSIZE];
DEVICE *dptr;
GET_SWITCHES (cptr); /* get switches */
if (*cptr == 0) /* must be more */
return SCPE_2FARG;
cptr = get_glyph (cptr, gbuf, 0); /* get next glyph */
GET_SWITCHES (cptr); /* get switches */
if (*cptr == 0) /* now eol? */
return SCPE_2FARG;
dptr = find_dev (gbuf); /* locate device */
if (dptr == NULL) /* found dev? */
return SCPE_NXDEV;
cptr = get_glyph (cptr, gbuf, 0); /* get next glyph */
if (*cptr != 0) /* must be eol */
return SCPE_2MARG;
if (find_dev (gbuf)) /* name in use */
return SCPE_ARG;
deassign_device (dptr); /* release current */
return assign_device (dptr, gbuf);
}
t_stat assign_device (DEVICE *dptr, char *cptr)
{
dptr->lname = (char *) calloc (CBUFSIZE, sizeof (char));
if (dptr->lname == NULL)
return SCPE_MEM;
strncpy (dptr->lname, cptr, CBUFSIZE);
return SCPE_OK;
}
/* Deassign command
dea[ssign] device deassign logical name
*/
t_stat deassign_cmd (int32 flag, char *cptr)
{
char gbuf[CBUFSIZE];
DEVICE *dptr;
GET_SWITCHES (cptr); /* get switches */
if (*cptr == 0) /* must be more */
return SCPE_2FARG;
cptr = get_glyph (cptr, gbuf, 0); /* get next glyph */
if (*cptr != 0) /* now eol? */
return SCPE_2MARG;
dptr = find_dev (gbuf); /* locate device */
if (dptr == NULL) /* found dev? */
return SCPE_NXDEV;
return deassign_device (dptr);
}
t_stat deassign_device (DEVICE *dptr)
{
if (dptr->lname)
free (dptr->lname);
dptr->lname = NULL;
return SCPE_OK;
}
/* Get device display name */
char *sim_dname (DEVICE *dptr)
{
return (dptr->lname? dptr->lname: dptr->name);
}
/* Get unit display name */
char *sim_uname (UNIT *uptr)
{
DEVICE *d = find_dev_from_unit(uptr);
static AIO_TLS char uname[CBUFSIZE];
if (!d)
return "";
if (d->numunits == 1)
return sim_dname (d);
sprintf (uname, "%s%d", sim_dname (d), (int)(uptr-d->units));
return uname;
}
/* Save command
sa[ve] filename save state to specified file
*/
t_stat save_cmd (int32 flag, char *cptr)
{
FILE *sfile;
t_stat r;
GET_SWITCHES (cptr); /* get switches */
if (*cptr == 0) /* must be more */
return SCPE_2FARG;
sim_trim_endspc (cptr);
if ((sfile = sim_fopen (cptr, "wb")) == NULL)
return SCPE_OPENERR;
r = sim_save (sfile);
fclose (sfile);
return r;
}
t_stat sim_save (FILE *sfile)
{
void *mbuf;
int32 l, t;
uint32 i, j;
t_addr k, high;
t_value val;
t_stat r;
t_bool zeroflg;
size_t sz;
DEVICE *dptr;
UNIT *uptr;
REG *rptr;
#define WRITE_I(xx) sim_fwrite (&(xx), sizeof (xx), 1, sfile)
fprintf (sfile, "%s\n%s\n%s\n%s\n%s\n%.0f\n",
save_vercur, /* [V2.5] save format */
sim_name, /* sim name */
sim_si64, sim_sa64, eth_capabilities(), /* [V3.5] options */
sim_time); /* [V3.2] sim time */
WRITE_I (sim_rtime); /* [V2.6] sim rel time */
for (i = 0; (dptr = sim_devices[i]) != NULL; i++) { /* loop thru devices */
fputs (dptr->name, sfile); /* device name */
fputc ('\n', sfile);
if (dptr->lname) /* [V3.0] logical name */
fputs (dptr->lname, sfile);
fputc ('\n', sfile);
WRITE_I (dptr->flags); /* [V2.10] flags */
for (j = 0; j < dptr->numunits; j++) {
uptr = dptr->units + j;
t = sim_activate_time (uptr);
WRITE_I (j); /* unit number */
WRITE_I (t); /* activation time */
WRITE_I (uptr->u3); /* unit specific */
WRITE_I (uptr->u4);
WRITE_I (uptr->u5); /* [V3.0] more unit */
WRITE_I (uptr->u6);
WRITE_I (uptr->flags); /* [V2.10] flags */
WRITE_I (uptr->dynflags);
WRITE_I (uptr->capac); /* [V3.5] capacity */
if (uptr->flags & UNIT_ATT) {
fputs (uptr->filename, sfile);
if ((uptr->flags & UNIT_BUF) && /* writable buffered */
uptr->hwmark && /* files need to be */
((uptr->flags & UNIT_RO) == 0)) { /* written on save */
uint32 cap = (uptr->hwmark + dptr->aincr - 1) / dptr->aincr;
rewind (uptr->fileref);
sim_fwrite (uptr->filebuf, SZ_D (dptr), cap, uptr->fileref);
fclose (uptr->fileref); /* flush data and state */
uptr->fileref = sim_fopen (uptr->filename, "rb+");/* reopen r/w */
}
}
fputc ('\n', sfile);
if (((uptr->flags & (UNIT_FIX + UNIT_ATTABLE)) == UNIT_FIX) &&
(dptr->examine != NULL) &&
((high = uptr->capac) != 0)) { /* memory-like unit? */
WRITE_I (high); /* [V2.5] write size */
sz = SZ_D (dptr);
if ((mbuf = calloc (SRBSIZ, sz)) == NULL) {
fclose (sfile);
return SCPE_MEM;
}
for (k = 0; k < high; ) { /* loop thru mem */
zeroflg = TRUE;
for (l = 0; (l < SRBSIZ) && (k < high); l++,
k = k + (dptr->aincr)) { /* check for 0 block */
r = dptr->examine (&val, k, uptr, SIM_SW_REST);
if (r != SCPE_OK) {
free (mbuf);
return r;
}
if (val) zeroflg = FALSE;
SZ_STORE (sz, val, mbuf, l);
} /* end for l */
if (zeroflg) { /* all zero's? */
l = -l; /* invert block count */
WRITE_I (l); /* write only count */
}
else {
WRITE_I (l); /* block count */
sim_fwrite (mbuf, sz, l, sfile);
}
} /* end for k */
free (mbuf); /* dealloc buffer */
} /* end if mem */
else { /* no memory */
high = 0; /* write 0 */
WRITE_I (high);
} /* end else mem */
} /* end unit loop */
t = -1; /* end units */
WRITE_I (t); /* write marker */
for (rptr = dptr->registers; (rptr != NULL) && /* loop thru regs */
(rptr->name != NULL); rptr++) {
fputs (rptr->name, sfile); /* name */
fputc ('\n', sfile);
WRITE_I (rptr->depth); /* [V2.10] depth */
for (j = 0; j < rptr->depth; j++) { /* loop thru values */
val = get_rval (rptr, j); /* get value */
WRITE_I (val); /* store */
}
}
fputc ('\n', sfile); /* end registers */
}
fputc ('\n', sfile); /* end devices */
return (ferror (sfile))? SCPE_IOERR: SCPE_OK; /* error during save? */
}
/* Restore command
re[store] filename restore state from specified file
*/
t_stat restore_cmd (int32 flag, char *cptr)
{
FILE *rfile;
t_stat r;
GET_SWITCHES (cptr); /* get switches */
if (*cptr == 0) /* must be more */
return SCPE_2FARG;
sim_trim_endspc (cptr);
if ((rfile = sim_fopen (cptr, "rb")) == NULL)
return SCPE_OPENERR;
r = sim_rest (rfile);
fclose (rfile);
return r;
}
t_stat sim_rest (FILE *rfile)
{
char buf[CBUFSIZE];
char **attnames = NULL;
UNIT **attunits = NULL;
int32 *attswitches = NULL;
int32 attcnt = 0;
void *mbuf;
int32 j, blkcnt, limit, unitno, time, flg;
uint32 us, depth;
t_addr k, high, old_capac;
t_value val, mask;
t_stat r;
size_t sz;
t_bool v35, v32;
DEVICE *dptr;
UNIT *uptr;
REG *rptr;
struct stat rstat;
t_bool force_restore = sim_switches & SWMASK ('F');
#define READ_S(xx) if (read_line ((xx), sizeof(xx), rfile) == NULL) \
return SCPE_IOERR;
#define READ_I(xx) if (sim_fread (&xx, sizeof (xx), 1, rfile) == 0) \
return SCPE_IOERR;
fstat (fileno (rfile), &rstat);
READ_S (buf); /* [V2.5+] read version */
v35 = v32 = FALSE;
if (strcmp (buf, save_vercur) == 0) /* version 3.5? */
v35 = v32 = TRUE;
else if (strcmp (buf, save_ver32) == 0) /* version 3.2? */
v32 = TRUE;
else if (strcmp (buf, save_ver30) != 0) { /* version 3.0? */
sim_printf ("Invalid file version: %s\n", buf);
return SCPE_INCOMP;
}
READ_S (buf); /* read sim name */
if (strcmp (buf, sim_name)) { /* name match? */
sim_printf ("Wrong system type: %s\n", buf);
return SCPE_INCOMP;
}
if (v35) { /* [V3.5+] options */
READ_S (buf); /* integer size */
if (strcmp (buf, sim_si64) != 0) {
sim_printf ("Incompatible integer size, save file = %s\n", buf);
return SCPE_INCOMP;
}
READ_S (buf); /* address size */
if (strcmp (buf, sim_sa64) != 0) {
sim_printf ("Incompatible address size, save file = %s\n", buf);
return SCPE_INCOMP;
}
READ_S (buf); /* Ethernet */
}
if (v32) { /* [V3.2+] time as string */
READ_S (buf);
sscanf (buf, "%lf", &sim_time);
}
else READ_I (sim_time); /* sim time */
READ_I (sim_rtime); /* [V2.6+] sim rel time */
detach_all (0, 0); /* Detach everything to start from a consistent state */
for ( ;; ) { /* device loop */
READ_S (buf); /* read device name */
if (buf[0] == 0) /* last? */
break;
if ((dptr = find_dev (buf)) == NULL) { /* locate device */
sim_printf ("Invalid device name: %s\n", buf);
return SCPE_INCOMP;
}
READ_S (buf); /* [V3.0+] logical name */
deassign_device (dptr); /* delete old name */
if ((buf[0] != 0) &&
((r = assign_device (dptr, buf)) != SCPE_OK))
return r;
READ_I (flg); /* [V2.10+] ctlr flags */
if (!v32)
flg = ((flg & DEV_UFMASK_31) << (DEV_V_UF - DEV_V_UF_31)) |
(flg & ~DEV_UFMASK_31); /* [V3.2+] flags moved */
dptr->flags = (dptr->flags & ~DEV_RFLAGS) | /* restore ctlr flags */
(flg & DEV_RFLAGS);
for ( ;; ) { /* unit loop */
sim_switches = SIM_SW_REST; /* flag rstr, clr RO */
READ_I (unitno); /* unit number */
if (unitno < 0) /* end units? */
break;
if ((uint32) unitno >= dptr->numunits) { /* too big? */
sim_printf ("Invalid unit number: %s%d\n", sim_dname (dptr), unitno);
return SCPE_INCOMP;
}
READ_I (time); /* event time */
uptr = (dptr->units) + unitno;
sim_cancel (uptr);
if (time > 0)
sim_activate (uptr, time - 1);
READ_I (uptr->u3); /* device specific */
READ_I (uptr->u4);
READ_I (uptr->u5); /* [V3.0+] more dev spec */
READ_I (uptr->u6);
READ_I (flg); /* [V2.10+] unit flags */
READ_I (uptr->dynflags);
old_capac = uptr->capac; /* save current capacity */
if (v35) { /* [V3.5+] capacity */
READ_I (uptr->capac);
}
if (!v32)
flg = ((flg & UNIT_UFMASK_31) << (UNIT_V_UF - UNIT_V_UF_31)) |
(flg & ~UNIT_UFMASK_31); /* [V3.2+] flags moved */
uptr->flags = (uptr->flags & ~UNIT_RFLAGS) |
(flg & UNIT_RFLAGS); /* restore */
READ_S (buf); /* attached file */
if (uptr->flags & UNIT_ATT) { /* unit currently attached? */
r = scp_detach_unit (dptr, uptr); /* detach it */
if (r != SCPE_OK)
return r;
}
if ((buf[0] != '\0') && /* unit to be reattached? */
((uptr->flags & UNIT_ATTABLE) || /* and unit is attachable */
(dptr->attach != NULL))) { /* or VM attach routine provided? */
uptr->flags = uptr->flags & ~UNIT_DIS; /* ensure device is enabled */
if (flg & UNIT_RO) /* [V2.10+] saved flgs & RO? */
sim_switches |= SWMASK ('R'); /* RO attach */
/* add unit to list of units to attach after registers are read */
attunits = (UNIT **)realloc (attunits, sizeof (*attunits)*(attcnt+1));
attunits[attcnt] = uptr;
attnames = (char **)realloc (attnames, sizeof (*attnames)*(attcnt+1));
attnames[attcnt] = (char *)malloc(1+strlen(buf));
strcpy (attnames[attcnt], buf);
attswitches = (int32 *)realloc (attswitches, sizeof (*attswitches)*(attcnt+1));
attswitches[attcnt] = sim_switches;
++attcnt;
}
READ_I (high); /* memory capacity */
if (high > 0) { /* [V2.5+] any memory? */
if (((uptr->flags & (UNIT_FIX + UNIT_ATTABLE)) != UNIT_FIX) ||
(dptr->deposit == NULL)) {
sim_printf ("Can't restore memory: %s%d\n", sim_dname (dptr), unitno);
return SCPE_INCOMP;
}
if (high != old_capac) { /* size change? */
uptr->capac = old_capac; /* temp restore old */
if ((dptr->flags & DEV_DYNM) &&
((dptr->msize == NULL) ||
(dptr->msize (uptr, (int32) high, NULL, NULL) != SCPE_OK))) {
sim_printf ("Can't change memory size: %s%d\n",
sim_dname (dptr), unitno);
return SCPE_INCOMP;
}
uptr->capac = high; /* new memory size */
sim_printf ("Memory size changed: %s%d = ", sim_dname (dptr), unitno);
fprint_capac (stdout, dptr, uptr);
if (sim_log)
fprint_capac (sim_log, dptr, uptr);
sim_printf ("\n");
}
sz = SZ_D (dptr); /* allocate buffer */
if ((mbuf = calloc (SRBSIZ, sz)) == NULL)
return SCPE_MEM;
for (k = 0; k < high; ) { /* loop thru mem */
if (sim_fread (&blkcnt, sizeof (blkcnt), 1, rfile) == 0) {/* block count */
free (mbuf);
return SCPE_IOERR;
}
if (blkcnt < 0) /* compressed? */
limit = -blkcnt;
else limit = (int32)sim_fread (mbuf, sz, blkcnt, rfile);
if (limit <= 0) { /* invalid or err? */
free (mbuf);
return SCPE_IOERR;
}
for (j = 0; j < limit; j++, k = k + (dptr->aincr)) {
if (blkcnt < 0) /* compressed? */
val = 0;
else SZ_LOAD (sz, val, mbuf, j); /* saved value */
r = dptr->deposit (val, k, uptr, SIM_SW_REST);
if (r != SCPE_OK) {
free (mbuf);
return r;
}
} /* end for j */
} /* end for k */
free (mbuf); /* dealloc buffer */
} /* end if high */
} /* end unit loop */
for ( ;; ) { /* register loop */
READ_S (buf); /* read reg name */
if (buf[0] == 0) /* last? */
break;
READ_I (depth); /* [V2.10+] depth */
if ((rptr = find_reg (buf, NULL, dptr)) == NULL) {
sim_printf ("Invalid register name: %s %s\n", sim_dname (dptr), buf);
for (us = 0; us < depth; us++) { /* skip values */
READ_I (val);
}
continue;
}
if (depth != rptr->depth) { /* [V2.10+] mismatch? */
sim_printf ("Register depth mismatch: %s %s, file = %d, sim = %d\n",
sim_dname (dptr), buf, depth, rptr->depth);
}
mask = width_mask[rptr->width]; /* get mask */
for (us = 0; us < depth; us++) { /* loop thru values */
READ_I (val); /* read value */
if (val > mask) { /* value ok? */
sim_printf ("Invalid register value: %s %s\n", sim_dname (dptr), buf);
}
else if (us < rptr->depth) /* in range? */
put_rval (rptr, us, val);
}
} /* end register loop */
} /* end device loop */
/* Now that all of the register state has been imported, we can attach
units which were originally attached. Some of these attach operations
may depend on the state of the device (in registers) to work correctly */
for (j=0, r = SCPE_OK; j<attcnt; j++) {
if (r == SCPE_OK) {
struct stat fstat;
t_addr saved_pos;
dptr = find_dev_from_unit (attunits[j]);
if ((!force_restore) &&
(!stat(attnames[j], &fstat)))
if (fstat.st_mtime > rstat.st_mtime + 30) {
r = SCPE_INCOMP;
sim_printf ("Error Attaching %s to %s - the restore state is %d seconds older than the attach file\n", sim_dname (dptr), attnames[j], (int)(fstat.st_mtime - rstat.st_mtime));
sim_printf ("restore with the -F switch to override this sanity check\n");
continue;
}
saved_pos = attunits[j]->pos;
sim_switches = attswitches[j];
r = scp_attach_unit (dptr, attunits[j], attnames[j]);/* reattach unit */
attunits[j]->pos = saved_pos;
if (r != SCPE_OK)
sim_printf ("Error Attaching %s to %s\n", sim_dname (dptr), attnames[j]);
}
free (attnames[j]);
}
free (attnames);
free (attunits);
free (attswitches);
return r;
}
/* Run, go, cont, step commands
ru[n] [new PC] reset and start simulation
go [new PC] start simulation
co[nt] start simulation
s[tep] [step limit] start simulation for 'limit' instructions
b[oot] device bootstrap from device and start simulation
switches:
-Q quiet return status
-T (only for step), causes the step limit to
be a number of microseconds to run for
*/
t_stat run_cmd (int32 flag, char *cptr)
{
char gbuf[CBUFSIZE];
const char *tptr;
uint32 i, j;
int32 sim_next;
int32 unitno;
t_value pcv;
t_stat r;
DEVICE *dptr;
UNIT *uptr;
GET_SWITCHES (cptr); /* get switches */
sim_step = 0;
if ((flag == RU_RUN) || (flag == RU_GO)) { /* run or go */
if (*cptr != 0) { /* argument? */
cptr = get_glyph (cptr, gbuf, 0); /* get next glyph */
if (*cptr != 0) /* should be end */
return SCPE_2MARG;
if (sim_vm_parse_addr) /* address parser? */
pcv = sim_vm_parse_addr (sim_dflt_dev, gbuf, (char **)&tptr);
else pcv = strtotv (gbuf, &tptr, sim_PC->radix);/* parse PC */
if ((tptr == gbuf) || (*tptr != 0) || /* error? */
(pcv > width_mask[sim_PC->width]))
return SCPE_ARG;
put_rval (sim_PC, 0, pcv);
}
if ((flag == RU_RUN) && /* run? */
((r = sim_run_boot_prep ()) != SCPE_OK)) /* reset sim */
return r;
}
else if ((flag == RU_STEP) ||
((flag == RU_NEXT) && !sim_vm_is_subroutine_call)) { /* step */
static t_bool not_implemented_message = FALSE;
if ((!not_implemented_message) && (flag == RU_NEXT)) {
sim_printf ("This simulator does not have subroutine call detection.\nPerforming a STEP instead\n");
not_implemented_message = TRUE;
flag = RU_STEP;
}
if (*cptr != 0) { /* argument? */
cptr = get_glyph (cptr, gbuf, 0); /* get next glyph */
if (*cptr != 0) /* should be end */
return SCPE_2MARG;
sim_step = (int32) get_uint (gbuf, 10, INT_MAX, &r);
if ((r != SCPE_OK) || (sim_step <= 0)) /* error? */
return SCPE_ARG;
}
else sim_step = 1;
if ((flag == RU_STEP) && (sim_switches & SWMASK ('T')))
sim_step = (int32)((sim_timer_inst_per_sec ()*sim_step)/1000000.0);
}
else if (flag == RU_NEXT) { /* next */
t_addr *addrs;
if (*cptr != 0) { /* argument? */
cptr = get_glyph (cptr, gbuf, 0); /* get next glyph */
if (*cptr != 0) /* should be end */
return SCPE_2MARG;
sim_next = (int32) get_uint (gbuf, 10, INT_MAX, &r);
if ((r != SCPE_OK) || (sim_next <= 0)) /* error? */
return SCPE_ARG;
}
else sim_next = 1;
if (sim_vm_is_subroutine_call(&addrs)) {
sim_brk_types |= BRK_TYP_DYN_STEPOVER;
for (i=0; addrs[i]; i++)
sim_brk_set (addrs[i], BRK_TYP_DYN_STEPOVER, 0, NULL);
}
else
sim_step = 1;
}
else if (flag == RU_BOOT) { /* boot */
if (*cptr == 0) /* must be more */
return SCPE_2FARG;
cptr = get_glyph (cptr, gbuf, 0); /* get next glyph */
if (*cptr != 0) /* should be end */
return SCPE_2MARG;
dptr = find_unit (gbuf, &uptr); /* locate unit */
if (dptr == NULL) /* found dev? */
return SCPE_NXDEV;
if (uptr == NULL) /* valid unit? */
return SCPE_NXUN;
if (dptr->boot == NULL) /* can it boot? */
return SCPE_NOFNC;
if (uptr->flags & UNIT_DIS) /* disabled? */
return SCPE_UDIS;
if ((uptr->flags & UNIT_ATTABLE) && /* if attable, att? */
!(uptr->flags & UNIT_ATT))
return SCPE_UNATT;
unitno = (int32) (uptr - dptr->units); /* recover unit# */
if ((r = sim_run_boot_prep ()) != SCPE_OK) /* reset sim */
return r;
if ((r = dptr->boot (unitno, dptr)) != SCPE_OK) /* boot device */
return r;
}
else if (flag != RU_CONT) /* must be cont */
return SCPE_IERR;
if (sim_switches & SIM_SW_HIDE) /* Setup only for Remote Console Mode */
return SCPE_OK;
for (i = 1; (dptr = sim_devices[i]) != NULL; i++) { /* reposition all */
for (j = 0; j < dptr->numunits; j++) { /* seq devices */
uptr = dptr->units + j;
if ((uptr->flags & (UNIT_ATT + UNIT_SEQ)) == (UNIT_ATT + UNIT_SEQ))
sim_fseek (uptr->fileref, uptr->pos, SEEK_SET);
}
}
stop_cpu = 0;
sim_is_running = 1; /* flag running */
if (sim_ttrun () != SCPE_OK) { /* set console mode */
sim_is_running = 0; /* flag idle */
sim_ttcmd ();
return SCPE_TTYERR;
}
if ((r = sim_check_console (30)) != SCPE_OK) { /* check console, error? */
sim_is_running = 0; /* flag idle */
sim_ttcmd ();
return r;
}
if (signal (SIGINT, int_handler) == SIG_ERR) { /* set WRU */
sim_is_running = 0; /* flag idle */
sim_ttcmd ();
return SCPE_SIGERR;
}
#ifdef SIGHUP
if (signal (SIGHUP, int_handler) == SIG_ERR) { /* set WRU */
sim_is_running = 0; /* flag idle */
sim_ttcmd ();
return SCPE_SIGERR;
}
#endif
if (signal (SIGTERM, int_handler) == SIG_ERR) { /* set WRU */
sim_is_running = 0; /* flag idle */
sim_ttcmd ();
return SCPE_SIGERR;
}
if (sim_step) /* set step timer */
sim_activate (&sim_step_unit, sim_step);
fflush(stdout); /* flush stdout */
if (sim_log) /* flush log if enabled */
fflush (sim_log);
sim_throt_sched (); /* set throttle */
sim_rtcn_init_all (); /* re-init clocks */
sim_start_timer_services (); /* enable wall clock timing */
do {
t_addr *addrs;
r = sim_instr();
if ((flag != RU_NEXT) || /* done if not doing NEXT */
(--sim_next <=0))
break;
if (sim_step == 0) { /* doing a NEXT? */
t_addr val;
BRKTAB *bp;
if (SCPE_BARE_STATUS(r) >= SCPE_BASE) /* done if an error occurred */
break;
if (sim_vm_pc_value) /* done if didn't stop at a dynamic breakpoint */
val = (t_addr)(*sim_vm_pc_value)();
else
val = (t_addr)get_rval (sim_PC, 0);
if ((!(bp = sim_brk_fnd (val))) || (!(bp->typ & BRK_TYP_DYN_STEPOVER)))
break;
sim_brk_clrall (BRK_TYP_DYN_STEPOVER); /* cancel any step/over subroutine breakpoints */
}
else {
if (r != SCPE_STEP) /* done if step didn't complete with step expired */
break;
}
/* setup another next/step */
sim_step = 0;
if (sim_vm_is_subroutine_call(&addrs)) {
sim_brk_types |= BRK_TYP_DYN_STEPOVER;
for (i=0; addrs[i]; i++)
sim_brk_set (addrs[i], BRK_TYP_DYN_STEPOVER, 0, NULL);
}
else
sim_step = 1;
if (sim_step) /* set step timer */
sim_activate (&sim_step_unit, sim_step);
} while (1);
sim_is_running = 0; /* flag idle */
sim_stop_timer_services (); /* disable wall clock timing */
sim_ttcmd (); /* restore console */
sim_brk_clrall (BRK_TYP_DYN_STEPOVER); /* cancel any step/over subroutine breakpoints */
signal (SIGINT, SIG_DFL); /* cancel WRU */
#ifdef SIGHUP
signal (SIGHUP, SIG_DFL); /* cancel WRU */
#endif
signal (SIGTERM, SIG_DFL); /* cancel WRU */
if (sim_log) /* flush console log */
fflush (sim_log);
if (sim_deb) /* flush debug log */
sim_debug_flush ();
for (i = 1; (dptr = sim_devices[i]) != NULL; i++) { /* flush attached files */
for (j = 0; j < dptr->numunits; j++) { /* if not buffered in mem */
uptr = dptr->units + j;
if (uptr->flags & UNIT_ATT) { /* attached, */
if (uptr->io_flush) /* unit specific flush routine */
uptr->io_flush (uptr); /* call it */
else {
if (!(uptr->flags & UNIT_BUF) && /* not buffered, */
(uptr->fileref) && /* real file, */
!(uptr->dynflags & UNIT_NO_FIO) && /* is FILE *, */
!(uptr->flags & UNIT_RO)) /* not read only? */
fflush (uptr->fileref);
}
}
}
}
sim_cancel (&sim_step_unit); /* cancel step timer */
sim_throt_cancel (); /* cancel throttle */
AIO_UPDATE_QUEUE;
UPDATE_SIM_TIME; /* update sim time */
return r | ((sim_switches & SWMASK ('Q')) ? SCPE_NOMESSAGE : 0);
}
/* run command message handler */
void
run_cmd_message (const char *unechoed_cmdline, t_stat r)
{
#if defined (VMS)
printf ("\n");
#endif
if (unechoed_cmdline && (r >= SCPE_BASE) && (r != SCPE_STEP) && (r != SCPE_STOP) && (r != SCPE_EXPECT))
sim_printf("%s> %s\n", do_position(), unechoed_cmdline);
fprint_stopped (stdout, r); /* print msg */
if (sim_log && (sim_log != stdout)) /* log if enabled */
fprint_stopped (sim_log, r);
if (sim_deb && (sim_deb != stdout) && (sim_deb != sim_log))/* debug if enabled */
fprint_stopped (sim_deb, r);
}
/* Common setup for RUN or BOOT */
t_stat sim_run_boot_prep (void)
{
UNIT *uptr;
sim_interval = 0; /* reset queue */
sim_time = sim_rtime = 0;
noqueue_time = 0;
for (uptr = sim_clock_queue; uptr != QUEUE_LIST_END; uptr = sim_clock_queue) {
sim_clock_queue = uptr->next;
uptr->next = NULL;
}
return reset_all (0);
}
/* Print stopped message
* For VM stops, if a VM-specific "sim_vm_fprint_stopped" pointer is defined,
* call the indicated routine to print additional information after the message
* and before the PC value is printed. If the routine returns FALSE, skip
* printing the PC and its related instruction.
*/
void fprint_stopped_gen (FILE *st, t_stat v, REG *pc, DEVICE *dptr)
{
int32 i;
t_stat r = 0;
t_addr k;
t_value pcval;
fputc ('\n', st); /* start on a new line */
if (v >= SCPE_BASE) /* SCP error? */
fputs (sim_error_text (v), st); /* print it from the SCP list */
else { /* VM error */
fputs (sim_stop_messages [v], st); /* print the VM-specific message */
if ((sim_vm_fprint_stopped != NULL) && /* if a VM-specific stop handler is defined */
(!sim_vm_fprint_stopped (st, v))) /* call it; if it returned FALSE, */
return; /* we're done */
}
fprintf (st, ", %s: ", pc->name); /* print the name of the PC register */
pcval = get_rval (pc, 0);
if ((pc->flags & REG_VMAD) && sim_vm_fprint_addr) /* if reg wants VM-specific printer */
sim_vm_fprint_addr (st, dptr, (t_addr) pcval); /* call it to print the PC address */
else fprint_val (st, pcval, pc->radix, pc->width, /* otherwise, print as a numeric value */
pc->flags & REG_FMT); /* with the radix and formatting specified */
if ((dptr != NULL) && (dptr->examine != NULL)) {
for (i = 0; i < sim_emax; i++)
sim_eval[i] = 0;
for (i = 0, k = (t_addr) pcval; i < sim_emax; i++, k = k + dptr->aincr) {
if ((r = dptr->examine (&sim_eval[i], k, dptr->units, SWMASK ('V')|SIM_SW_STOP)) != SCPE_OK)
break;
}
if ((r == SCPE_OK) || (i > 0)) {
fprintf (st, " (");
if (fprint_sym (st, (t_addr) pcval, sim_eval, NULL, SWMASK('M')|SIM_SW_STOP) > 0)
fprint_val (st, sim_eval[0], dptr->dradix, dptr->dwidth, PV_RZRO);
fprintf (st, ")");
}
}
fprintf (st, "\n");
return;
}
void fprint_stopped (FILE *st, t_stat v)
{
fprint_stopped_gen (st, v, sim_PC, sim_dflt_dev);
return;
}
/* Unit service for step timeout, originally scheduled by STEP n command
Return step timeout SCP code, will cause simulation to stop */
t_stat step_svc (UNIT *uptr)
{
return SCPE_STEP;
}
/* Unit service to facilitate expect matching to stop simulation.
Return expect SCP code, will cause simulation to stop */
t_stat expect_svc (UNIT *uptr)
{
return SCPE_EXPECT | (sim_do_echo ? 0 : SCPE_NOMESSAGE);
}
/* Cancel scheduled step service */
t_stat sim_cancel_step (void)
{
return sim_cancel (&sim_step_unit);
}
/* Signal handler for ^C signal - set stop simulation flag */
void int_handler (int sig)
{
stop_cpu = 1;
return;
}
/* Examine/deposit commands
ex[amine] [modifiers] list examine
de[posit] [modifiers] list val deposit
ie[xamine] [modifiers] list interactive examine
id[eposit] [modifiers] list interactive deposit
modifiers
@filename output file
-letter(s) switches
devname'n device name and unit number
[{&|^}value]{=|==|!|!=|>|>=|<|<=} value search specification
list list of addresses and registers
addr[:addr|-addr] address range
ALL all addresses
register[:register|-register] register range
register[index] register array element
register[start:end] register array range
STATE all registers
*/
t_stat exdep_cmd (int32 flag, char *cptr)
{
char gbuf[CBUFSIZE];
const char *gptr;
const char *tptr = NULL;
int32 opt;
t_addr low, high;
t_stat reason;
DEVICE *tdptr;
REG *lowr, *highr;
FILE *ofile;
opt = CMD_OPT_SW|CMD_OPT_SCH|CMD_OPT_DFT; /* options for all */
if (flag == EX_E) /* extra for EX */
opt = opt | CMD_OPT_OF;
cptr = get_sim_opt (opt, cptr, &reason); /* get cmd options */
if (!cptr) /* error? */
return reason;
if (*cptr == 0) /* must be more */
return SCPE_2FARG;
if (sim_dfunit == NULL) /* got a unit? */
return SCPE_NXUN;
cptr = get_glyph (cptr, gbuf, 0); /* get list */
if ((flag == EX_D) && (*cptr == 0)) /* deposit needs more */
return SCPE_2FARG;
ofile = sim_ofile? sim_ofile: stdout; /* no ofile? use stdout */
for (gptr = gbuf, reason = SCPE_OK;
(*gptr != 0) && (reason == SCPE_OK); gptr = tptr) {
tdptr = sim_dfdev; /* working dptr */
if (strncmp (gptr, "STATE", strlen ("STATE")) == 0) {
tptr = gptr + strlen ("STATE");
if (*tptr && (*tptr++ != ','))
return SCPE_ARG;
if ((lowr = sim_dfdev->registers) == NULL)
return SCPE_NXREG;
for (highr = lowr; highr->name != NULL; highr++) ;
sim_switches = sim_switches | SIM_SW_HIDE;
reason = exdep_reg_loop (ofile, sim_schrptr, flag, cptr,
lowr, --highr, 0, 0);
continue;
}
if ((lowr = find_reg (gptr, &tptr, tdptr)) || /* local reg or */
(!(sim_opt_out & CMD_OPT_DFT) && /* no dflt, global? */
(lowr = find_reg_glob (gptr, &tptr, &tdptr)))) {
low = high = 0;
if ((*tptr == '-') || (*tptr == ':')) {
highr = find_reg (tptr + 1, &tptr, tdptr);
if (highr == NULL)
return SCPE_NXREG;
}
else {
highr = lowr;
if (*tptr == '[') {
if (lowr->depth <= 1)
return SCPE_ARG;
tptr = get_range (NULL, tptr + 1, &low, &high,
10, lowr->depth - 1, ']');
if (tptr == NULL)
return SCPE_ARG;
}
}
if (*tptr && (*tptr++ != ','))
return SCPE_ARG;
reason = exdep_reg_loop (ofile, sim_schrptr, flag, cptr,
lowr, highr, (uint32) low, (uint32) high);
continue;
}
tptr = get_range (sim_dfdev, gptr, &low, &high, sim_dfdev->aradix,
(((sim_dfunit->capac == 0) || (flag == EX_E))? 0:
sim_dfunit->capac - sim_dfdev->aincr), 0);
if (tptr == NULL)
return SCPE_ARG;
if (*tptr && (*tptr++ != ','))
return SCPE_ARG;
reason = exdep_addr_loop (ofile, sim_schaptr, flag, cptr, low, high,
sim_dfdev, sim_dfunit);
} /* end for */
if (sim_ofile) /* close output file */
fclose (sim_ofile);
return reason;
}
/* Loop controllers for examine/deposit
exdep_reg_loop examine/deposit range of registers
exdep_addr_loop examine/deposit range of addresses
*/
t_stat exdep_reg_loop (FILE *ofile, SCHTAB *schptr, int32 flag, char *cptr,
REG *lowr, REG *highr, uint32 lows, uint32 highs)
{
t_stat reason;
uint32 idx, val_start=lows;
t_value val, last_val;
REG *rptr;
if ((lowr == NULL) || (highr == NULL))
return SCPE_IERR;
if (lowr > highr)
return SCPE_ARG;
for (rptr = lowr; rptr <= highr; rptr++) {
if ((sim_switches & SIM_SW_HIDE) &&
(rptr->flags & REG_HIDDEN))
continue;
val = last_val = 0;
for (idx = lows; idx <= highs; idx++) {
if (idx >= rptr->depth)
return SCPE_SUB;
val = get_rval (rptr, idx);
if (schptr && !test_search (&val, schptr))
continue;
if (flag == EX_E) {
if ((idx > lows) && (val == last_val))
continue;
if (idx > val_start+1) {
if (idx-1 == val_start+1) {
reason = ex_reg (ofile, val, flag, rptr, idx-1);
if (reason != SCPE_OK)
return reason;
}
else
fprintf (ofile, "%s[%d]-%s[%d]: same as above\n", rptr->name, val_start+1, rptr->name, idx-1);
}
last_val = val;
val_start = idx;
reason = ex_reg (ofile, val, flag, rptr, idx);
if (reason != SCPE_OK)
return reason;
if (sim_log && (ofile == stdout))
ex_reg (sim_log, val, flag, rptr, idx);
}
if (flag != EX_E) {
reason = dep_reg (flag, cptr, rptr, idx);
if (reason != SCPE_OK)
return reason;
}
}
if ((flag == EX_E) && (val_start != highs)) {
if (highs == val_start+1) {
reason = ex_reg (ofile, val, flag, rptr, highs);
if (reason != SCPE_OK)
return reason;
}
else
fprintf (ofile, "%s[%d]-%s[%d]: same as above\n", rptr->name, val_start+1, rptr->name, highs);
}
}
return SCPE_OK;
}
t_stat exdep_addr_loop (FILE *ofile, SCHTAB *schptr, int32 flag, char *cptr,
t_addr low, t_addr high, DEVICE *dptr, UNIT *uptr)
{
t_addr i, mask;
t_stat reason;
if (uptr->flags & UNIT_DIS) /* disabled? */
return SCPE_UDIS;
mask = (t_addr) width_mask[dptr->awidth];
if ((low > mask) || (high > mask) || (low > high))
return SCPE_ARG;
for (i = low; i <= high; ) { /* all paths must incr!! */
reason = get_aval (i, dptr, uptr); /* get data */
if (reason != SCPE_OK) /* return if error */
return reason;
if (schptr && !test_search (sim_eval, schptr))
i = i + dptr->aincr; /* sch fails, incr */
else { /* no sch or success */
if (flag != EX_D) { /* ex, ie, or id? */
reason = ex_addr (ofile, flag, i, dptr, uptr);
if (reason > SCPE_OK)
return reason;
if (sim_log && (ofile == stdout))
ex_addr (sim_log, flag, i, dptr, uptr);
}
else reason = 1 - dptr->aincr; /* no, dflt incr */
if (flag != EX_E) { /* ie, id, or d? */
reason = dep_addr (flag, cptr, i, dptr, uptr, reason);
if (reason > SCPE_OK)
return reason;
}
i = i + (1 - reason); /* incr */
}
}
return SCPE_OK;
}
/* Examine register routine
Inputs:
ofile = output stream
val = current register value
flag = type of ex/mod command (ex, iex, idep)
rptr = pointer to register descriptor
idx = index
Outputs:
return = error status
*/
t_stat ex_reg (FILE *ofile, t_value val, int32 flag, REG *rptr, uint32 idx)
{
int32 rdx;
if (rptr == NULL)
return SCPE_IERR;
if (rptr->depth > 1)
fprintf (ofile, "%s[%d]:\t", rptr->name, idx);
else fprintf (ofile, "%s:\t", rptr->name);
if (!(flag & EX_E))
return SCPE_OK;
GET_RADIX (rdx, rptr->radix);
if ((rptr->flags & REG_VMAD) && sim_vm_fprint_addr)
sim_vm_fprint_addr (ofile, sim_dflt_dev, (t_addr) val);
else if (!(rptr->flags & REG_VMIO) ||
(fprint_sym (ofile, rdx, &val, NULL, sim_switches | SIM_SW_REG) > 0)) {
fprint_val (ofile, val, rdx, rptr->width, rptr->flags & REG_FMT);
if (rptr->fields) {
fprintf (ofile, "\t");
fprint_fields (ofile, val, val, rptr->fields);
}
}
if (flag & EX_I)
fprintf (ofile, "\t");
else fprintf (ofile, "\n");
return SCPE_OK;
}
/* Get register value
Inputs:
rptr = pointer to register descriptor
idx = index
Outputs:
return = register value
*/
t_value get_rval (REG *rptr, uint32 idx)
{
size_t sz;
t_value val;
UNIT *uptr;
sz = SZ_R (rptr);
if ((rptr->depth > 1) && (rptr->flags & REG_CIRC)) {
idx = idx + rptr->qptr;
if (idx >= rptr->depth) idx = idx - rptr->depth;
}
if ((rptr->depth > 1) && (rptr->flags & REG_UNIT)) {
uptr = ((UNIT *) rptr->loc) + idx;
#if defined (USE_INT64)
if (sz <= sizeof (uint32))
val = *((uint32 *) uptr);
else val = *((t_uint64 *) uptr);
#else
val = *((uint32 *) uptr);
#endif
}
else if (((rptr->depth > 1) || (rptr->flags & REG_FIT)) &&
(sz == sizeof (uint8)))
val = *(((uint8 *) rptr->loc) + idx);
else if (((rptr->depth > 1) || (rptr->flags & REG_FIT)) &&
(sz == sizeof (uint16)))
val = *(((uint16 *) rptr->loc) + idx);
#if defined (USE_INT64)
else if (sz <= sizeof (uint32))
val = *(((uint32 *) rptr->loc) + idx);
else val = *(((t_uint64 *) rptr->loc) + idx);
#else
else val = *(((uint32 *) rptr->loc) + idx);
#endif
val = (val >> rptr->offset) & width_mask[rptr->width];
return val;
}
/* Deposit register routine
Inputs:
flag = type of deposit (normal/interactive)
cptr = pointer to input string
rptr = pointer to register descriptor
idx = index
Outputs:
return = error status
*/
t_stat dep_reg (int32 flag, char *cptr, REG *rptr, uint32 idx)
{
t_stat r;
t_value val, mask;
int32 rdx;
char *tptr, gbuf[CBUFSIZE];
if ((cptr == NULL) || (rptr == NULL))
return SCPE_IERR;
if (rptr->flags & REG_RO)
return SCPE_RO;
if (flag & EX_I) {
cptr = read_line (gbuf, sizeof(gbuf), stdin);
if (sim_log)
fprintf (sim_log, "%s\n", cptr? cptr: "");
if (cptr == NULL) /* force exit */
return 1;
if (*cptr == 0) /* success */
return SCPE_OK;
}
mask = width_mask[rptr->width];
GET_RADIX (rdx, rptr->radix);
if ((rptr->flags & REG_VMAD) && sim_vm_parse_addr) { /* address form? */
val = sim_vm_parse_addr (sim_dflt_dev, cptr, &tptr);
if ((tptr == cptr) || (*tptr != 0) || (val > mask))
return SCPE_ARG;
}
else if (!(rptr->flags & REG_VMIO) || /* dont use sym? */
(parse_sym (cptr, rdx, NULL, &val, sim_switches | SIM_SW_REG) > SCPE_OK)) {
val = get_uint (cptr, rdx, mask, &r);
if (r != SCPE_OK)
return SCPE_ARG;
}
if ((rptr->flags & REG_NZ) && (val == 0))
return SCPE_ARG;
put_rval (rptr, idx, val);
return SCPE_OK;
}
/* Put register value
Inputs:
rptr = pointer to register descriptor
idx = index
val = new value
mask = mask
Outputs:
none
*/
void put_rval (REG *rptr, uint32 idx, t_value val)
{
size_t sz;
t_value mask;
UNIT *uptr;
#define PUT_RVAL(sz,rp,id,v,m) \
*(((sz *) rp->loc) + id) = \
(sz)((*(((sz *) rp->loc) + id) & \
~((m) << (rp)->offset)) | ((v) << (rp)->offset))
if (rptr == sim_PC)
sim_brk_npc (0);
sz = SZ_R (rptr);
mask = width_mask[rptr->width];
if ((rptr->depth > 1) && (rptr->flags & REG_CIRC)) {
idx = idx + rptr->qptr;
if (idx >= rptr->depth)
idx = idx - rptr->depth;
}
if ((rptr->depth > 1) && (rptr->flags & REG_UNIT)) {
uptr = ((UNIT *) rptr->loc) + idx;
#if defined (USE_INT64)
if (sz <= sizeof (uint32))
*((uint32 *) uptr) = (*((uint32 *) uptr) &
~(((uint32) mask) << rptr->offset)) |
(((uint32) val) << rptr->offset);
else *((t_uint64 *) uptr) = (*((t_uint64 *) uptr)
& ~(mask << rptr->offset)) | (val << rptr->offset);
#else
*((uint32 *) uptr) = (*((uint32 *) uptr) &
~(((uint32) mask) << rptr->offset)) |
(((uint32) val) << rptr->offset);
#endif
}
else if (((rptr->depth > 1) || (rptr->flags & REG_FIT)) &&
(sz == sizeof (uint8)))
PUT_RVAL (uint8, rptr, idx, (uint32) val, (uint32) mask);
else if (((rptr->depth > 1) || (rptr->flags & REG_FIT)) &&
(sz == sizeof (uint16)))
PUT_RVAL (uint16, rptr, idx, (uint32) val, (uint32) mask);
#if defined (USE_INT64)
else if (sz <= sizeof (uint32))
PUT_RVAL (uint32, rptr, idx, (int32) val, (uint32) mask);
else PUT_RVAL (t_uint64, rptr, idx, val, mask);
#else
else PUT_RVAL (uint32, rptr, idx, val, mask);
#endif
return;
}
/* Examine address routine
Inputs: (sim_eval is an implicit argument)
ofile = output stream
flag = type of ex/mod command (ex, iex, idep)
addr = address to examine
dptr = pointer to device
uptr = pointer to unit
Outputs:
return = if > 0, error status
if <= 0,-number of extra addr units retired
*/
t_stat ex_addr (FILE *ofile, int32 flag, t_addr addr, DEVICE *dptr, UNIT *uptr)
{
t_stat reason;
int32 rdx;
if (sim_vm_fprint_addr)
sim_vm_fprint_addr (ofile, dptr, addr);
else fprint_val (ofile, addr, dptr->aradix, dptr->awidth, PV_LEFT);
fprintf (ofile, ":\t");
if (!(flag & EX_E))
return (1 - dptr->aincr);
GET_RADIX (rdx, dptr->dradix);
if ((reason = fprint_sym (ofile, addr, sim_eval, uptr, sim_switches)) > 0) {
fprint_val (ofile, sim_eval[0], rdx, dptr->dwidth, PV_RZRO);
reason = 1 - dptr->aincr;
}
if (flag & EX_I)
fprintf (ofile, "\t");
else fprintf (ofile, "\n");
return reason;
}
/* Get address routine
Inputs:
flag = type of ex/mod command (ex, iex, idep)
addr = address to examine
dptr = pointer to device
uptr = pointer to unit
Outputs: (sim_eval is an implicit output)
return = error status
*/
t_stat get_aval (t_addr addr, DEVICE *dptr, UNIT *uptr)
{
int32 i;
t_value mask;
t_addr j, loc;
size_t sz;
t_stat reason = SCPE_OK;
if ((dptr == NULL) || (uptr == NULL))
return SCPE_IERR;
mask = width_mask[dptr->dwidth];
for (i = 0; i < sim_emax; i++)
sim_eval[i] = 0;
for (i = 0, j = addr; i < sim_emax; i++, j = j + dptr->aincr) {
if (dptr->examine != NULL) {
reason = dptr->examine (&sim_eval[i], j, uptr, sim_switches);
if (reason != SCPE_OK)
break;
}
else {
if (!(uptr->flags & UNIT_ATT))
return SCPE_UNATT;
if (uptr->dynflags & UNIT_NO_FIO)
return SCPE_NOFNC;
if ((uptr->flags & UNIT_FIX) && (j >= uptr->capac)) {
reason = SCPE_NXM;
break;
}
sz = SZ_D (dptr);
loc = j / dptr->aincr;
if (uptr->flags & UNIT_BUF) {
SZ_LOAD (sz, sim_eval[i], uptr->filebuf, loc);
}
else {
sim_fseek (uptr->fileref, (t_addr)(sz * loc), SEEK_SET);
sim_fread (&sim_eval[i], sz, 1, uptr->fileref);
if ((feof (uptr->fileref)) &&
!(uptr->flags & UNIT_FIX)) {
reason = SCPE_EOF;
break;
}
else if (ferror (uptr->fileref)) {
clearerr (uptr->fileref);
reason = SCPE_IOERR;
break;
}
}
}
sim_eval[i] = sim_eval[i] & mask;
}
if ((reason != SCPE_OK) && (i == 0))
return reason;
return SCPE_OK;
}
/* Deposit address routine
Inputs:
flag = type of deposit (normal/interactive)
cptr = pointer to input string
addr = address to examine
dptr = pointer to device
uptr = pointer to unit
dfltinc = value to return on cr input
Outputs:
return = if > 0, error status
if <= 0, -number of extra address units retired
*/
t_stat dep_addr (int32 flag, char *cptr, t_addr addr, DEVICE *dptr,
UNIT *uptr, int32 dfltinc)
{
int32 i, count, rdx;
t_addr j, loc;
t_stat r, reason;
t_value mask;
size_t sz;
char gbuf[CBUFSIZE];
if (dptr == NULL)
return SCPE_IERR;
if (flag & EX_I) {
cptr = read_line (gbuf, sizeof(gbuf), stdin);
if (sim_log)
fprintf (sim_log, "%s\n", cptr? cptr: "");
if (cptr == NULL) /* force exit */
return 1;
if (*cptr == 0) /* success */
return dfltinc;
}
if (uptr->flags & UNIT_RO) /* read only? */
return SCPE_RO;
mask = width_mask[dptr->dwidth];
GET_RADIX (rdx, dptr->dradix);
if ((reason = parse_sym (cptr, addr, uptr, sim_eval, sim_switches)) > 0) {
sim_eval[0] = get_uint (cptr, rdx, mask, &reason);
if (reason != SCPE_OK)
return reason;
reason = dfltinc;
}
count = (1 - reason + (dptr->aincr - 1)) / dptr->aincr;
for (i = 0, j = addr; i < count; i++, j = j + dptr->aincr) {
sim_eval[i] = sim_eval[i] & mask;
if (dptr->deposit != NULL) {
r = dptr->deposit (sim_eval[i], j, uptr, sim_switches);
if (r != SCPE_OK)
return r;
}
else {
if (!(uptr->flags & UNIT_ATT))
return SCPE_UNATT;
if (uptr->dynflags & UNIT_NO_FIO)
return SCPE_NOFNC;
if ((uptr->flags & UNIT_FIX) && (j >= uptr->capac))
return SCPE_NXM;
sz = SZ_D (dptr);
loc = j / dptr->aincr;
if (uptr->flags & UNIT_BUF) {
SZ_STORE (sz, sim_eval[i], uptr->filebuf, loc);
if (loc >= uptr->hwmark)
uptr->hwmark = (uint32) loc + 1;
}
else {
sim_fseek (uptr->fileref, (t_addr)(sz * loc), SEEK_SET);
sim_fwrite (&sim_eval[i], sz, 1, uptr->fileref);
if (ferror (uptr->fileref)) {
clearerr (uptr->fileref);
return SCPE_IOERR;
}
}
}
}
return reason;
}
/* Evaluate command */
t_stat eval_cmd (int32 flg, char *cptr)
{
DEVICE *dptr = sim_dflt_dev;
int32 i, rdx, a, lim;
t_stat r;
GET_SWITCHES (cptr);
GET_RADIX (rdx, dptr->dradix);
for (i = 0; i < sim_emax; i++)
sim_eval[i] = 0;
if (*cptr == 0)
return SCPE_2FARG;
if ((r = parse_sym (cptr, 0, dptr->units, sim_eval, sim_switches)) > 0) {
sim_eval[0] = get_uint (cptr, rdx, width_mask[dptr->dwidth], &r);
if (r != SCPE_OK)
return r;
}
lim = 1 - r;
for (i = a = 0; a < lim; ) {
sim_printf ("%d:\t", a);
if ((r = fprint_sym (stdout, a, &sim_eval[i], dptr->units, sim_switches)) > 0)
r = fprint_val (stdout, sim_eval[i], rdx, dptr->dwidth, PV_RZRO);
if (sim_log) {
if ((r = fprint_sym (sim_log, a, &sim_eval[i], dptr->units, sim_switches)) > 0)
r = fprint_val (sim_log, sim_eval[i], rdx, dptr->dwidth, PV_RZRO);
}
sim_printf ("\n");
if (r < 0)
a = a + 1 - r;
else a = a + dptr->aincr;
i = a / dptr->aincr;
}
return SCPE_OK;
}
/* String processing routines
read_line read line
Inputs:
cptr = pointer to buffer
size = maximum size
stream = pointer to input stream
Outputs:
optr = pointer to first non-blank character
NULL if EOF
*/
char *read_line (char *cptr, int32 size, FILE *stream)
{
return read_line_p (NULL, cptr, size, stream);
}
/* read_line_p read line with prompt
Inputs:
prompt = pointer to prompt string
cptr = pointer to buffer
size = maximum size
stream = pointer to input stream
Outputs:
optr = pointer to first non-blank character
NULL if EOF
*/
char *read_line_p (const char *prompt, char *cptr, int32 size, FILE *stream)
{
char *tptr;
#if defined(HAVE_DLOPEN)
static int initialized = 0;
typedef char *(*readline_func)(const char *);
static readline_func p_readline = NULL;
typedef void (*add_history_func)(const char *);
static add_history_func p_add_history = NULL;
if (!initialized) {
initialized = 1;
void *handle;
#define S__STR_QUOTE(tok) #tok
#define S__STR(tok) S__STR_QUOTE(tok)
handle = dlopen("libncurses." S__STR(HAVE_DLOPEN), RTLD_NOW|RTLD_GLOBAL);
handle = dlopen("libcurses." S__STR(HAVE_DLOPEN), RTLD_NOW|RTLD_GLOBAL);
handle = dlopen("libreadline." S__STR(HAVE_DLOPEN), RTLD_NOW|RTLD_GLOBAL);
if (!handle)
handle = dlopen("libreadline." S__STR(HAVE_DLOPEN) ".6", RTLD_NOW|RTLD_GLOBAL);
if (!handle)
handle = dlopen("libreadline." S__STR(HAVE_DLOPEN) ".5", RTLD_NOW|RTLD_GLOBAL);
if (handle) {
p_readline = (readline_func)((size_t)dlsym(handle, "readline"));
p_add_history = (add_history_func)((size_t)dlsym(handle, "add_history"));
}
}
if (prompt) { /* interactive? */
if (p_readline) {
char *tmpc = p_readline (prompt); /* get cmd line */
if (tmpc == NULL) /* bad result? */
cptr = NULL;
else {
strncpy (cptr, tmpc, size); /* copy result */
free (tmpc) ; /* free temp */
}
}
else {
printf ("%s", prompt); /* display prompt */
cptr = fgets (cptr, size, stream); /* get cmd line */
}
}
else cptr = fgets (cptr, size, stream); /* get cmd line */
#else
if (prompt) /* interactive? */
printf ("%s", prompt); /* display prompt */
cptr = fgets (cptr, size, stream); /* get cmd line */
#endif
if (cptr == NULL) {
clearerr (stream); /* clear error */
return NULL; /* ignore EOF */
}
for (tptr = cptr; tptr < (cptr + size); tptr++) { /* remove cr or nl */
if ((*tptr == '\n') || (*tptr == '\r') ||
(tptr == (cptr + size - 1))) { /* str max length? */
*tptr = 0; /* terminate */
break;
}
}
if (0 == memcmp (cptr, "\xEF\xBB\xBF", 3)) /* Skip/ignore UTF8_BOM */
memmove (cptr, cptr + 3, strlen (cptr + 3));
while (sim_isspace (*cptr)) /* trim leading spc */
cptr++;
if ((*cptr == ';') || (*cptr == '#')) { /* ignore comment */
if (sim_do_echo) /* echo comments if -v */
sim_printf("%s> %s\n", do_position(), cptr);
*cptr = 0;
}
#if defined (HAVE_DLOPEN)
if (prompt && p_add_history && *cptr) /* Save non blank lines in history */
p_add_history (cptr);
#endif
return cptr;
}
/* get_glyph get next glyph (force upper case)
get_glyph_nc get next glyph (no conversion)
get_glyph_quoted get next glyph (potentially enclosed in quotes, no conversion)
get_glyph_gen get next glyph (general case)
Inputs:
iptr = pointer to input string
optr = pointer to output string
mchar = optional end of glyph character
uc = TRUE for convert to upper case (_gen only)
quote = TRUE to allow quote enclosing values (_gen only)
escape_char = optional escape character within quoted strings (_gen only)
Outputs
result = pointer to next character in input string
*/
static const char *get_glyph_gen (const char *iptr, char *optr, char mchar, t_bool uc, t_bool quote, char escape_char)
{
t_bool quoting = FALSE;
t_bool escaping = FALSE;
char quote_char = 0;
while ((*iptr != 0) &&
((quote && quoting) || ((sim_isspace (*iptr) == 0) && (*iptr != mchar)))) {
if (quote) {
if (quoting) {
if (!escaping) {
if (*iptr == escape_char)
escaping = TRUE;
else
if (*iptr == quote_char)
quoting = FALSE;
}
else
escaping = FALSE;
}
else {
if ((*iptr == '"') || (*iptr == '\'')) {
quoting = TRUE;
quote_char = *iptr;
}
}
}
if (sim_islower (*iptr) && uc)
*optr = (char)toupper (*iptr);
else *optr = *iptr;
iptr++; optr++;
}
*optr = 0;
if (mchar && (*iptr == mchar)) /* skip terminator */
iptr++;
while (sim_isspace (*iptr)) /* absorb spaces */
iptr++;
return iptr;
}
char *get_glyph (const char *iptr, char *optr, char mchar)
{
return (char *)get_glyph_gen (iptr, optr, mchar, TRUE, FALSE, 0);
}
char *get_glyph_nc (const char *iptr, char *optr, char mchar)
{
return (char *)get_glyph_gen (iptr, optr, mchar, FALSE, FALSE, 0);
}
char *get_glyph_quoted (const char *iptr, char *optr, char mchar)
{
return (char *)get_glyph_gen (iptr, optr, mchar, FALSE, TRUE, '\\');
}
/* Trim trailing spaces from a string
Inputs:
cptr = pointer to string
Outputs:
cptr = pointer to string
*/
char *sim_trim_endspc (char *cptr)
{
char *tptr;
tptr = cptr + strlen (cptr);
while ((--tptr >= cptr) && sim_isspace (*tptr))
*tptr = 0;
return cptr;
}
int sim_isspace (char c)
{
return (c & 0x80) ? 0 : isspace (c);
}
int sim_islower (char c)
{
return (c & 0x80) ? 0 : islower (c);
}
int sim_isalpha (char c)
{
return (c & 0x80) ? 0 : isalpha (c);
}
int sim_isprint (char c)
{
return (c & 0x80) ? 0 : isprint (c);
}
int sim_isdigit (char c)
{
return (c & 0x80) ? 0 : isdigit (c);
}
int sim_isgraph (char c)
{
return (c & 0x80) ? 0 : isgraph (c);
}
int sim_isalnum (char c)
{
return (c & 0x80) ? 0 : isalnum (c);
}
/* get_yn yes/no question
Inputs:
cptr = pointer to question
deflt = default answer
Outputs:
result = true if yes, false if no
*/
t_stat get_yn (char *ques, t_stat deflt)
{
char cbuf[CBUFSIZE], *cptr;
printf ("%s ", ques);
cptr = read_line (cbuf, sizeof(cbuf), stdin);
if ((cptr == NULL) || (*cptr == 0))
return deflt;
if ((*cptr == 'Y') || (*cptr == 'y'))
return TRUE;
return FALSE;
}
/* get_uint unsigned number
Inputs:
cptr = pointer to input string
radix = input radix
max = maximum acceptable value
*status = pointer to error status
Outputs:
val = value
*/
t_value get_uint (const char *cptr, uint32 radix, t_value max, t_stat *status)
{
t_value val;
const char *tptr;
*status = SCPE_OK;
val = strtotv (cptr, &tptr, radix);
if ((cptr == tptr) || (val > max))
*status = SCPE_ARG;
else {
while (sim_isspace (*tptr)) tptr++;
if (*tptr != 0)
*status = SCPE_ARG;
}
return val;
}
/* get_range range specification
Inputs:
dptr = pointer to device (NULL if none)
cptr = pointer to input string
*lo = pointer to low result
*hi = pointer to high result
aradix = radix
max = default high value
term = terminating character, 0 if none
Outputs:
tptr = input pointer after processing
NULL if error
*/
const char *get_range (DEVICE *dptr, const char *cptr, t_addr *lo, t_addr *hi,
uint32 rdx, t_addr max, char term)
{
const char *tptr;
if (max && strncmp (cptr, "ALL", strlen ("ALL")) == 0) { /* ALL? */
tptr = cptr + strlen ("ALL");
*lo = 0;
*hi = max;
}
else {
if (dptr && sim_vm_parse_addr) /* get low */
*lo = sim_vm_parse_addr (dptr, (char *)cptr, (char **)&tptr);
else *lo = (t_addr) strtotv (cptr, &tptr, rdx);
if (cptr == tptr) /* error? */
return NULL;
if ((*tptr == '-') || (*tptr == ':')) { /* range? */
cptr = tptr + 1;
if (dptr && sim_vm_parse_addr) /* get high */
*hi = sim_vm_parse_addr (dptr, (char *)cptr, (char **)&tptr);
else *hi = (t_addr) strtotv (cptr, &tptr, rdx);
if (cptr == tptr)
return NULL;
if (*lo > *hi)
return NULL;
}
else if (*tptr == '/') { /* relative? */
cptr = tptr + 1;
*hi = (t_addr) strtotv (cptr, &tptr, rdx); /* get high */
if ((cptr == tptr) || (*hi == 0))
return NULL;
*hi = *lo + *hi - 1;
}
else *hi = *lo;
}
if (term && (*tptr++ != term))
return NULL;
return tptr;
}
/* sim_decode_quoted_string
Inputs:
iptr = pointer to input string
optr = pointer to output buffer
the output buffer must be allocated by the caller
and to avoid overrunat it must be at least as big
as the input string.
Outputs
result = status of decode SCPE_OK when good, SCPE_ARG otherwise
osize = size of the data in the optr buffer
The input string must be quoted. Quotes may be either single or
double but the opening anc closing quote characters must match.
Within quotes C style character escapes are allowed.
The following character escapes are explicitly supported:
\r ASCII Carriage Return character (Decimal value 13)
\n ASCII Linefeed character (Decimal value 10)
\f ASCII Formfeed character (Decimal value 12)
\t ASCII Horizontal Tab character (Decimal value 9)
\v ASCII Vertical Tab character (Decimal value 11)
\b ASCII Backspace character (Decimal value 8)
\\ ASCII Backslash character (Decimal value 92)
\' ASCII Single Quote character (Decimal value 39)
\" ASCII Double Quote character (Decimal value 34)
\? ASCII Question Mark character (Decimal value 63)
\e ASCII Escape character (Decimal value 27)
as well as octal character values of the form:
\n{n{n}} where each n is an octal digit (0-7)
and hext character values of the form:
\xh{h} where each h is a hex digit (0-9A-Fa-f)
*/
t_stat sim_decode_quoted_string (const char *iptr, uint8 *optr, uint32 *osize)
{
char quote_char;
uint8 *ostart = optr;
*osize = 0;
if ((strlen(iptr) == 1) ||
(iptr[0] != iptr[strlen(iptr)-1]) ||
((iptr[strlen(iptr)-1] != '"') && (iptr[strlen(iptr)-1] != '\'')))
return SCPE_ARG; /* String must be quote delimited */
quote_char = *iptr++; /* Save quote character */
while (iptr[1]) { /* Skip trailing quote */
if (*iptr != '\\') {
if (*iptr == quote_char)
return SCPE_ARG; /* Imbedded quotes must be escaped */
*(optr++) = (uint8)(*(iptr++));
continue;
}
++iptr; /* Skip backslash */
switch (*iptr) {
case 'r': /* ASCII Carriage Return character (Decimal value 13) */
*(optr++) = 13; ++iptr;
break;
case 'n': /* ASCII Linefeed character (Decimal value 10) */
*(optr++) = 10; ++iptr;
break;
case 'f': /* ASCII Formfeed character (Decimal value 12) */
*(optr++) = 12; ++iptr;
break;
case 't': /* ASCII Horizontal Tab character (Decimal value 9) */
*(optr++) = 9; ++iptr;
break;
case 'v': /* ASCII Vertical Tab character (Decimal value 11) */
*(optr++) = 11; ++iptr;
break;
case 'b': /* ASCII Backspace character (Decimal value 8) */
*(optr++) = 8; ++iptr;
break;
case '\\': /* ASCII Backslash character (Decimal value 92) */
*(optr++) = 92; ++iptr;
break;
case 'e': /* ASCII Escape character (Decimal value 27) */
*(optr++) = 27; ++iptr;
break;
case '\'': /* ASCII Single Quote character (Decimal value 39) */
*(optr++) = 39; ++iptr;
break;
case '"': /* ASCII Double Quote character (Decimal value 34) */
*(optr++) = 34; ++iptr;
break;
case '?': /* ASCII Question Mark character (Decimal value 63) */
*(optr++) = 63; ++iptr;
break;
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7':
*optr = *(iptr++) - '0';
if ((*iptr >= '0') && (*iptr <= '7'))
*optr = ((*optr)<<3) + (*(iptr++) - '0');
if ((*iptr >= '0') && (*iptr <= '7'))
*optr = ((*optr)<<3) + (*(iptr++) - '0');
++optr;
break;
case 'x':
if (1) {
static char *hex_digits = "0123456789ABCDEF";
const char *c;
++iptr;
*optr = 0;
c = strchr (hex_digits, toupper(*iptr));
if (c) {
*optr = ((*optr)<<4) + (uint8)(c-hex_digits);
++iptr;
}
c = strchr (hex_digits, toupper(*iptr));
if (c) {
*optr = ((*optr)<<4) + (uint8)(c-hex_digits);
++iptr;
}
++optr;
}
break;
default:
return SCPE_ARG; /* Invalid escape */
}
}
*optr = '\0';
*osize = (uint32)(optr-ostart);
return SCPE_OK;
}
/* sim_encode_quoted_string
Inputs:
iptr = pointer to input buffer
size = number of bytes of data in the buffer
Outputs
optr = pointer to output buffer
the output buffer must be freed by the caller
The input data will be encoded into a simply printable form.
Control and other non-printable data will be escaped using the
following rules:
The following character escapes are explicitly supported:
\r ASCII Carriage Return character (Decimal value 13)
\n ASCII Linefeed character (Decimal value 10)
\f ASCII Formfeed character (Decimal value 12)
\t ASCII Horizontal Tab character (Decimal value 9)
\v ASCII Vertical Tab character (Decimal value 11)
\b ASCII Backspace character (Decimal value 8)
\\ ASCII Backslash character (Decimal value 92)
\' ASCII Single Quote character (Decimal value 39)
\" ASCII Double Quote character (Decimal value 34)
\? ASCII Question Mark character (Decimal value 63)
\e ASCII Escape character (Decimal value 27)
as well as octal character values of the form:
\n{n{n}} where each n is an octal digit (0-7)
and hext character values of the form:
\xh{h} where each h is a hex digit (0-9A-Fa-f)
*/
char *sim_encode_quoted_string (const uint8 *iptr, uint32 size)
{
uint32 i;
t_bool double_quote_found = FALSE;
t_bool single_quote_found = FALSE;
char quote = '"';
char *tptr, *optr;
optr = malloc (4*size + 3);
if (optr == NULL)
return NULL;
tptr = optr;
for (i=0; i<size; i++)
switch ((char)iptr[i]) {
case '"':
double_quote_found = TRUE;
break;
case '\'':
single_quote_found = TRUE;
break;
}
if (double_quote_found && (!single_quote_found))
quote = '\'';
*tptr++ = quote;
while (size--) {
switch (*iptr) {
case '\r': *tptr++ = '\\'; *tptr++ = 'r'; break;
case '\n':
*tptr++ = '\\'; *tptr++ = 'n'; break;
case '\f':
*tptr++ = '\\'; *tptr++ = 'f'; break;
case '\t':
*tptr++ = '\\'; *tptr++ = 't'; break;
case '\v':
*tptr++ = '\\'; *tptr++ = 'v'; break;
case '\b':
*tptr++ = '\\'; *tptr++ = 'b'; break;
case '\\':
*tptr++ = '\\'; *tptr++ = '\\'; break;
case '"':
case '\'':
if (quote == *iptr)
*tptr++ = '\\';
default:
if (sim_isprint (*iptr))
*tptr++ = *iptr;
else {
sprintf (tptr, "\\%03o", *iptr);
tptr += 4;
}
break;
}
++iptr;
}
*tptr++ = quote;
*tptr++ = '\0';
return optr;
}
void fprint_buffer_string (FILE *st, const uint8 *buf, uint32 size)
{
char *string;
string = sim_encode_quoted_string (buf, size);
fprintf (st, "%s", string);
free (string);
}
/* Find_device find device matching input string
Inputs:
cptr = pointer to input string
Outputs:
result = pointer to device
*/
DEVICE *find_dev (const char *cptr)
{
int32 i;
DEVICE *dptr;
for (i = 0; (dptr = sim_devices[i]) != NULL; i++) {
if ((strcmp (cptr, dptr->name) == 0) ||
(dptr->lname &&
(strcmp (cptr, dptr->lname) == 0)))
return dptr;
}
for (i = 0; sim_internal_device_count && (dptr = sim_internal_devices[i]); ++i) {
if ((strcmp (cptr, dptr->name) == 0) ||
(dptr->lname &&
(strcmp (cptr, dptr->lname) == 0)))
return dptr;
}
return NULL;
}
/* Find_unit find unit matching input string
Inputs:
cptr = pointer to input string
uptr = pointer to unit pointer
Outputs:
result = pointer to device (null if no dev)
*iptr = pointer to unit (null if nx unit)
*/
DEVICE *find_unit (const char *cptr, UNIT **uptr)
{
uint32 i, u;
char *nptr;
const char *tptr;
t_stat r;
DEVICE *dptr;
if (uptr == NULL) /* arg error? */
return NULL;
*uptr = NULL;
if ((dptr = find_dev (cptr))) { /* exact match? */
if (qdisable (dptr)) /* disabled? */
return NULL;
*uptr = dptr->units; /* unit 0 */
return dptr;
}
for (i = 0; (dptr = sim_devices[i]) != NULL; i++) { /* base + unit#? */
if (dptr->numunits && /* any units? */
(((nptr = dptr->name) &&
(strncmp (cptr, nptr, strlen (nptr)) == 0)) ||
((nptr = dptr->lname) &&
(strncmp (cptr, nptr, strlen (nptr)) == 0)))) {
tptr = cptr + strlen (nptr);
if (sim_isdigit (*tptr)) {
if (qdisable (dptr)) /* disabled? */
return NULL;
u = (uint32) get_uint (tptr, 10, dptr->numunits - 1, &r);
if (r != SCPE_OK) /* error? */
*uptr = NULL;
else *uptr = dptr->units + u;
return dptr;
}
}
}
return NULL;
}
/* sim_register_internal_device Add device to internal device list
Inputs:
dptr = pointer to device
*/
t_stat sim_register_internal_device (DEVICE *dptr)
{
uint32 i;
for (i = 0; (sim_devices[i] != NULL); i++)
if (sim_devices[i] == dptr)
return SCPE_OK;
for (i = 0; i < sim_internal_device_count; i++)
if (sim_internal_devices[i] == dptr)
return SCPE_OK;
++sim_internal_device_count;
sim_internal_devices = (DEVICE **)realloc(sim_internal_devices, (sim_internal_device_count+1)*sizeof(*sim_internal_devices));
sim_internal_devices[sim_internal_device_count-1] = dptr;
sim_internal_devices[sim_internal_device_count] = NULL;
return SCPE_OK;
}
/* Find_dev_from_unit find device for unit
Inputs:
uptr = pointer to unit
Outputs:
result = pointer to device
*/
DEVICE *find_dev_from_unit (UNIT *uptr)
{
DEVICE *dptr;
uint32 i, j;
if (uptr == NULL)
return NULL;
for (i = 0; (dptr = sim_devices[i]) != NULL; i++) {
for (j = 0; j < dptr->numunits; j++) {
if (uptr == (dptr->units + j))
return dptr;
}
}
for (i = 0; i<sim_internal_device_count; i++) {
dptr = sim_internal_devices[i];
for (j = 0; j < dptr->numunits; j++) {
if (uptr == (dptr->units + j))
return dptr;
}
}
return NULL;
}
/* Test for disabled device */
t_bool qdisable (DEVICE *dptr)
{
return (dptr->flags & DEV_DIS? TRUE: FALSE);
}
/* find_reg_glob find globally unique register
Inputs:
cptr = pointer to input string
optr = pointer to output pointer (can be null)
gdptr = pointer to global device
Outputs:
result = pointer to register, NULL if error
*optr = pointer to next character in input string
*gdptr = pointer to device where found
*/
REG *find_reg_glob (const char *cptr, const char **optr, DEVICE **gdptr)
{
int32 i;
DEVICE *dptr;
REG *rptr, *srptr = NULL;
*gdptr = NULL;
for (i = 0; (dptr = sim_devices[i]) != 0; i++) { /* all dev */
if (dptr->flags & DEV_DIS) /* skip disabled */
continue;
if ((rptr = find_reg (cptr, optr, dptr))) { /* found? */
if (srptr) /* ambig? err */
return NULL;
srptr = rptr; /* save reg */
*gdptr = dptr; /* save unit */
}
}
return srptr;
}
/* find_reg find register matching input string
Inputs:
cptr = pointer to input string
optr = pointer to output pointer (can be null)
dptr = pointer to device
Outputs:
result = pointer to register, NULL if error
*optr = pointer to next character in input string
*/
REG *find_reg (const char *cptr, const char **optr, DEVICE *dptr)
{
const char *tptr;
REG *rptr;
size_t slnt;
if ((cptr == NULL) || (dptr == NULL) || (dptr->registers == NULL))
return NULL;
tptr = cptr;
do {
tptr++;
} while (sim_isalnum (*tptr) || (*tptr == '*') || (*tptr == '_') || (*tptr == '.'));
slnt = tptr - cptr;
for (rptr = dptr->registers; rptr->name != NULL; rptr++) {
if ((slnt == strlen (rptr->name)) &&
(strncmp (cptr, rptr->name, slnt) == 0)) {
if (optr != NULL)
*optr = tptr;
return rptr;
}
}
return NULL;
}
/* get_switches get switches from input string
Inputs:
cptr = pointer to input string
Outputs:
sw = switch bit mask
0 if no switches, -1 if error
*/
int32 get_switches (char *cptr)
{
int32 sw;
if (*cptr != '-')
return 0;
sw = 0;
for (cptr++; (sim_isspace (*cptr) == 0) && (*cptr != 0); cptr++) {
if (sim_isalpha (*cptr) == 0)
return -1;
sw = sw | SWMASK (toupper (*cptr));
}
return sw;
}
/* get_sim_sw accumulate sim_switches
Inputs:
cptr = pointer to input string
Outputs:
ptr = pointer to first non-string glyph
NULL if error
*/
char *get_sim_sw (char *cptr)
{
int32 lsw;
char gbuf[CBUFSIZE];
while (*cptr == '-') { /* while switches */
cptr = get_glyph (cptr, gbuf, 0); /* get switch glyph */
lsw = get_switches (gbuf); /* parse */
if (lsw <= 0) /* invalid? */
return NULL;
sim_switches = sim_switches | lsw; /* accumulate */
}
return cptr;
}
/* get_sim_opt get simulator command options
Inputs:
opt = command options
cptr = pointer to input string
Outputs:
ptr = pointer to next glypsh, NULL if error
*stat = error status
*/
char *get_sim_opt (int32 opt, char *cptr, t_stat *st)
{
int32 t;
char *svptr, gbuf[CBUFSIZE];
DEVICE *tdptr;
UNIT *tuptr;
sim_switches = 0; /* no switches */
sim_ofile = NULL; /* no output file */
sim_schrptr = NULL; /* no search */
sim_schaptr = NULL; /* no search */
sim_stabr.logic = sim_staba.logic = SCH_OR; /* default search params */
sim_stabr.boolop = sim_staba.boolop = SCH_GE;
sim_stabr.count = 1;
sim_stabr.mask = realloc (sim_stabr.mask, sim_emax * sizeof(*sim_stabr.mask));
memset (sim_stabr.mask, 0, sim_emax * sizeof(*sim_stabr.mask));
sim_stabr.comp = realloc (sim_stabr.comp, sim_emax * sizeof(*sim_stabr.comp));
memset (sim_stabr.comp, 0, sim_emax * sizeof(*sim_stabr.comp));
sim_staba.count = sim_emax;
sim_staba.mask = realloc (sim_staba.mask, sim_emax * sizeof(*sim_staba.mask));
memset (sim_staba.mask, 0, sim_emax * sizeof(*sim_staba.mask));
sim_staba.comp = realloc (sim_staba.comp, sim_emax * sizeof(*sim_staba.comp));
memset (sim_staba.comp, 0, sim_emax * sizeof(*sim_staba.comp));
sim_dfdev = sim_dflt_dev;
sim_dfunit = sim_dfdev->units;
sim_opt_out = 0; /* no options yet */
*st = SCPE_OK;
while (*cptr) { /* loop through modifiers */
svptr = cptr; /* save current position */
if ((opt & CMD_OPT_OF) && (*cptr == '@')) { /* output file spec? */
if (sim_ofile) { /* already got one? */
fclose (sim_ofile); /* one per customer */
*st = SCPE_ARG;
return NULL;
}
cptr = get_glyph (cptr + 1, gbuf, 0);
sim_ofile = sim_fopen (gbuf, "a"); /* open for append */
if (sim_ofile == NULL) { /* open failed? */
*st = SCPE_OPENERR;
return NULL;
}
sim_opt_out |= CMD_OPT_OF; /* got output file */
continue;
}
cptr = get_glyph (cptr, gbuf, 0);
if ((t = get_switches (gbuf)) != 0) { /* try for switches */
if (t < 0) { /* err if bad switch */
*st = SCPE_INVSW;
return NULL;
}
sim_switches = sim_switches | t; /* or in new switches */
}
else if ((opt & CMD_OPT_SCH) && /* if allowed, */
get_rsearch (gbuf, sim_dfdev->dradix, &sim_stabr)) { /* try for search */
sim_schrptr = &sim_stabr; /* set search */
sim_schaptr = get_asearch (gbuf, sim_dfdev->dradix, &sim_staba);/* populate memory version of the same expression */
sim_opt_out |= CMD_OPT_SCH; /* got search */
}
else if ((opt & CMD_OPT_DFT) && /* default allowed? */
((sim_opt_out & CMD_OPT_DFT) == 0) && /* none yet? */
(tdptr = find_unit (gbuf, &tuptr)) && /* try for default */
(tuptr != NULL)) {
sim_dfdev = tdptr; /* set as default */
sim_dfunit = tuptr;
sim_opt_out |= CMD_OPT_DFT; /* got default */
}
else return svptr; /* not rec, break out */
}
return cptr;
}
/* Match file extension
Inputs:
fnam = file name
ext = extension, without period
Outputs:
cp = pointer to final '.' if match, NULL if not
*/
char *match_ext (char *fnam, char *ext)
{
char *pptr, *fptr, *eptr;
if ((fnam == NULL) || (ext == NULL)) /* bad arguments? */
return NULL;
pptr = strrchr (fnam, '.'); /* find last . */
if (pptr) { /* any? */
for (fptr = pptr + 1, eptr = ext; /* match characters */
#if defined (VMS) /* VMS: stop at ; or null */
(*fptr != 0) && (*fptr != ';');
#else
*fptr != 0; /* others: stop at null */
#endif
fptr++, eptr++) {
if (toupper (*fptr) != toupper (*eptr))
return NULL;
}
if (*eptr != 0) /* ext exhausted? */
return NULL;
}
return pptr;
}
/* Get register search specification
Inputs:
cptr = pointer to input string
radix = radix for numbers
schptr = pointer to search table
Outputs:
return = NULL if error
schptr if valid search specification
*/
SCHTAB *get_rsearch (const char *cptr, int32 radix, SCHTAB *schptr)
{
int32 c, logop, cmpop;
t_value logval, cmpval;
const char *sptr;
const char *tptr;
const char logstr[] = "|&^", cmpstr[] = "=!><";
logval = cmpval = 0;
if (*cptr == 0) /* check for clause */
return NULL;
for (logop = cmpop = -1; (c = *cptr++); ) { /* loop thru clauses */
if ((sptr = strchr (logstr, c))) { /* check for mask */
logop = (int32)(sptr - logstr);
logval = strtotv (cptr, &tptr, radix);
if (cptr == tptr)
return NULL;
cptr = (char *)tptr;
}
else if ((sptr = strchr (cmpstr, c))) { /* check for boolop */
cmpop = (int32)(sptr - cmpstr);
if (*cptr == '=') {
cmpop = cmpop + strlen (cmpstr);
cptr++;
}
cmpval = strtotv (cptr, &tptr, radix);
if (cptr == tptr)
return NULL;
cptr = (char *)tptr;
}
else return NULL;
} /* end for */
if (schptr->count != 1) {
free (schptr->mask);
schptr->mask = calloc (sim_emax, sizeof(*schptr->mask));
free (schptr->comp);
schptr->comp = calloc (sim_emax, sizeof(*schptr->comp));
}
if (logop >= 0) {
schptr->logic = logop;
schptr->mask[0] = logval;
}
if (cmpop >= 0) {
schptr->boolop = cmpop;
schptr->comp[0] = cmpval;
}
schptr->count = 1;
return schptr;
}
/* Get memory search specification
Inputs:
cptr = pointer to input string
radix = radix for numbers
schptr = pointer to search table
Outputs:
return = NULL if error
schptr if valid search specification
*/
SCHTAB *get_asearch (const char *cptr, int32 radix, SCHTAB *schptr)
{
int32 c, logop, cmpop;
t_value *logval, *cmpval;
t_stat reason;
const char *ocptr = cptr;
const char *sptr;
char gbuf[CBUFSIZE];
const char logstr[] = "|&^", cmpstr[] = "=!><";
if (*cptr == 0) /* check for clause */
return NULL;
logval = calloc (sim_emax, sizeof(*logval));
cmpval = calloc (sim_emax, sizeof(*cmpval));
for (logop = cmpop = -1; (c = *cptr++); ) { /* loop thru clauses */
if ((sptr = strchr (logstr, c))) { /* check for mask */
logop = (int32)(sptr - logstr);
cptr = get_glyph (cptr, gbuf, 0);
reason = parse_sym (gbuf, 0, sim_dfunit, logval, sim_switches);
if (reason > 0) {
free (logval);
free (cmpval);
return get_rsearch (ocptr, radix, schptr);
}
}
else if ((sptr = strchr (cmpstr, c))) { /* check for boolop */
cmpop = (int32)(sptr - cmpstr);
if (*cptr == '=') {
cmpop = cmpop + strlen (cmpstr);
cptr++;
}
cptr = get_glyph (cptr, gbuf, 0);
reason = parse_sym (gbuf, 0, sim_dfunit, cmpval, sim_switches);
if (reason > 0) {
free (logval);
free (cmpval);
return get_rsearch (ocptr, radix, schptr);
}
}
else {
free (logval);
free (cmpval);
return NULL;
}
} /* end for */
if (schptr->count != (1 - reason)) {
schptr->count = 1 - reason;
free (schptr->mask);
schptr->mask = calloc (sim_emax, sizeof(*schptr->mask));
free (schptr->comp);
schptr->comp = calloc (sim_emax, sizeof(*schptr->comp));
}
if (logop >= 0) {
schptr->logic = logop;
free (schptr->mask);
schptr->mask = logval;
}
if (cmpop >= 0) {
schptr->boolop = cmpop;
free (schptr->comp);
schptr->comp = cmpval;
}
return schptr;
}
/* Test value against search specification
Inputs:
val = value list to test
schptr = pointer to search table
Outputs:
return = 1 if value passes search criteria, 0 if not
*/
int32 test_search (t_value *values, SCHTAB *schptr)
{
t_value *val = NULL;
int32 i, updown;
int32 ret = 0;
if (schptr == NULL)
return ret;
val = malloc (schptr->count * sizeof (*values));
for (i=0; i<(int32)schptr->count; i++) {
val[i] = values[i];
switch (schptr->logic) { /* case on logical */
case SCH_OR:
val[i] = val[i] | schptr->mask[i];
break;
case SCH_AND:
val[i] = val[i] & schptr->mask[i];
break;
case SCH_XOR:
val[i] = val[i] ^ schptr->mask[i];
break;
}
}
ret = 1;
if (1) { /* Little Endian VM */
updown = -1;
i=schptr->count-1;
}
else { /* Big Endian VM */
updown = 1;
i=0;
}
for (; (i>=0) && (i<(int32)schptr->count) && ret; i += updown) {
switch (schptr->boolop) { /* case on comparison */
case SCH_E: case SCH_EE:
if (val[i] != schptr->comp[i])
ret = 0;
break;
case SCH_N: case SCH_NE:
if (val[i] != schptr->comp[i])
ret = 0;
break;
case SCH_G:
if (val[i] <= schptr->comp[i])
ret = 0;
break;
case SCH_GE:
if (val[i] < schptr->comp[i])
ret = 0;
break;
case SCH_L:
if (val[i] >= schptr->comp[i])
ret = 0;
break;
case SCH_LE:
if (val[i] > schptr->comp[i])
ret = 0;
break;
}
}
free (val);
return ret;
}
/* Radix independent input/output package
strtotv - general radix input routine
Inputs:
inptr = string to convert
endptr = pointer to first unconverted character
radix = radix for input
Outputs:
value = converted value
On an error, the endptr will equal the inptr.
*/
t_value strtotv (const char *inptr, const char **endptr, uint32 radix)
{
int32 nodigit;
t_value val;
uint32 c, digit;
*endptr = (char *)inptr; /* assume fails */
if ((radix < 2) || (radix > 36))
return 0;
while (sim_isspace (*inptr)) /* bypass white space */
inptr++;
val = 0;
nodigit = 1;
for (c = *inptr; sim_isalnum(c); c = *++inptr) { /* loop through char */
if (sim_islower (c))
c = toupper (c);
if (sim_isdigit (c)) /* digit? */
digit = c - (uint32) '0';
else if (radix <= 10) /* stop if not expected */
break;
else digit = c + 10 - (uint32) 'A'; /* convert letter */
if (digit >= radix) /* valid in radix? */
return 0;
val = (val * radix) + digit; /* add to value */
nodigit = 0;
}
if (nodigit) /* no digits? */
return 0;
*endptr = (char *)inptr; /* result pointer */
return val;
}
/* fprint_val - general radix printing routine
Inputs:
stream = stream designator
val = value to print
radix = radix to print
width = width to print
format = leading zeroes format
Outputs:
status = error status
if stream is NULL, returns length of output that would
have been generated.
*/
t_stat sprint_val (char *buffer, t_value val, uint32 radix,
uint32 width, uint32 format)
{
#define MAX_WIDTH ((int) ((CHAR_BIT * sizeof (t_value) * 4 + 3)/3))
t_value owtest, wtest;
int32 d, digit, ndigits, commas = 0;
char dbuf[MAX_WIDTH + 1];
for (d = 0; d < MAX_WIDTH; d++)
dbuf[d] = (format == PV_RZRO)? '0': ' ';
dbuf[MAX_WIDTH] = 0;
d = MAX_WIDTH;
do {
d = d - 1;
digit = (int32) (val % radix);
val = val / radix;
dbuf[d] = (char)((digit <= 9)? '0' + digit: 'A' + (digit - 10));
} while ((d > 0) && (val != 0));
switch (format) {
case PV_LEFT:
break;
case PV_RCOMMA:
for (digit = 0; digit < MAX_WIDTH; digit++)
if (dbuf[digit] != ' ')
break;
ndigits = MAX_WIDTH - digit;
commas = (ndigits - 1)/3;
for (digit=0; digit<ndigits-3; digit++)
dbuf[MAX_WIDTH + (digit - ndigits) - (ndigits - digit - 1)/3] = dbuf[MAX_WIDTH + (digit - ndigits)];
for (digit=1; digit<=commas; digit++)
dbuf[MAX_WIDTH - (digit * 4)] = ',';
d = d - commas;
if (width > MAX_WIDTH) {
if (!buffer)
return width;
sprintf (buffer, "%*s", -((int)width), dbuf);
return SCPE_OK;
}
else
if (width > 0)
d = MAX_WIDTH - width;
break;
case PV_RZRO:
case PV_RSPC:
wtest = owtest = radix;
ndigits = 1;
while ((wtest < width_mask[width]) && (wtest >= owtest)) {
owtest = wtest;
wtest = wtest * radix;
ndigits = ndigits + 1;
}
if ((MAX_WIDTH - (ndigits + commas)) < d)
d = MAX_WIDTH - (ndigits + commas);
break;
}
if (!buffer)
return strlen(dbuf+d);
if (width < strlen(dbuf+d))
return SCPE_IOERR;
strcpy(buffer, dbuf+d);
return SCPE_OK;
}
t_stat fprint_val (FILE *stream, t_value val, uint32 radix,
uint32 width, uint32 format)
{
char dbuf[MAX_WIDTH + 1];
if (!stream)
return sprint_val (NULL, val, radix, width, format);
if (width > MAX_WIDTH)
width = MAX_WIDTH;
sprint_val (dbuf, val, radix, width, format);
if (fputs (dbuf, stream) == EOF)
return SCPE_IOERR;
return SCPE_OK;
}
t_stat sim_print_val (t_value val, uint32 radix,
uint32 width, uint32 format)
{
char dbuf[MAX_WIDTH + 1];
if (width > MAX_WIDTH)
width = MAX_WIDTH;
sprint_val (dbuf, val, radix, width, format);
if (fputs (dbuf, stdout) == EOF)
return SCPE_IOERR;
if (sim_log && (sim_log != stdout))
if (fputs (dbuf, sim_log) == EOF)
return SCPE_IOERR;
if (sim_deb && (sim_deb != stdout))
if (fputs (dbuf, sim_deb) == EOF)
return SCPE_IOERR;
return SCPE_OK;
}
/* Event queue package
sim_activate add entry to event queue
sim_activate_abs add entry to event queue even if event already scheduled
sim_activate_notbefore add entry to event queue even if event already scheduled
but not before the specified time
sim_activate_after add entry to event queue after a specified amount of wall time
sim_cancel remove entry from event queue
sim_process_event process entries on event queue
sim_is_active see if entry is on event queue
sim_activate_time return time until activation
sim_atime return absolute time for an entry
sim_gtime return global time
sim_qcount return event queue entry count
Asynchronous events are set up by queueing a unit data structure
to the event queue with a timeout (in simulator units, relative
to the current time). Each simulator 'times' these events by
counting down interval counter sim_interval. When this reaches
zero the simulator calls sim_process_event to process the event
and to see if further events need to be processed, or sim_interval
reset to count the next one.
The event queue is maintained in clock order; entry timeouts are
RELATIVE to the time in the previous entry.
sim_process_event - process event
Inputs:
none
Outputs:
reason = reason code returned by any event processor,
or 0 (SCPE_OK) if no exceptions
*/
t_stat sim_process_event (void)
{
UNIT *uptr;
t_stat reason;
if (stop_cpu) /* stop CPU? */
return SCPE_STOP;
AIO_UPDATE_QUEUE;
UPDATE_SIM_TIME; /* update sim time */
if (sim_clock_queue == QUEUE_LIST_END) { /* queue empty? */
sim_interval = noqueue_time = NOQUEUE_WAIT; /* flag queue empty */
sim_debug (SIM_DBG_EVENT, sim_dflt_dev, "Queue Empty New Interval = %d\n", sim_interval);
return SCPE_OK;
}
do {
uptr = sim_clock_queue; /* get first */
sim_clock_queue = uptr->next; /* remove first */
uptr->next = NULL; /* hygiene */
uptr->time = 0;
if (sim_clock_queue != QUEUE_LIST_END)
sim_interval = sim_clock_queue->time;
else
sim_interval = noqueue_time = NOQUEUE_WAIT;
sim_debug (SIM_DBG_EVENT, sim_dflt_dev, "Processing Event for %s\n", sim_uname (uptr));
AIO_EVENT_BEGIN(uptr);
if (uptr->action != NULL)
reason = uptr->action (uptr);
else
reason = SCPE_OK;
AIO_EVENT_COMPLETE(uptr, reason);
} while ((reason == SCPE_OK) &&
(sim_interval <= 0) &&
(sim_clock_queue != QUEUE_LIST_END));
if (sim_clock_queue == QUEUE_LIST_END) { /* queue empty? */
sim_interval = noqueue_time = NOQUEUE_WAIT; /* flag queue empty */
sim_debug (SIM_DBG_EVENT, sim_dflt_dev, "Processing Queue Complete New Interval = %d\n", sim_interval);
}
else
sim_debug (SIM_DBG_EVENT, sim_dflt_dev, "Processing Queue Complete New Interval = %d(%s)\n", sim_interval, sim_uname(sim_clock_queue));
return reason;
}
/* sim_activate - activate (queue) event
Inputs:
uptr = pointer to unit
event_time = relative timeout
Outputs:
reason = result (SCPE_OK if ok)
*/
t_stat sim_activate (UNIT *uptr, int32 event_time)
{
return _sim_activate (uptr, event_time);
}
t_stat _sim_activate (UNIT *uptr, int32 event_time)
{
UNIT *cptr, *prvptr;
int32 accum;
AIO_ACTIVATE (_sim_activate, uptr, event_time);
if (sim_is_active (uptr)) /* already active? */
return SCPE_OK;
UPDATE_SIM_TIME; /* update sim time */
sim_debug (SIM_DBG_ACTIVATE, sim_dflt_dev, "Activating %s delay=%d\n", sim_uname (uptr), event_time);
prvptr = NULL;
accum = 0;
for (cptr = sim_clock_queue; cptr != QUEUE_LIST_END; cptr = cptr->next) {
if (event_time < (accum + cptr->time))
break;
accum = accum + cptr->time;
prvptr = cptr;
}
if (prvptr == NULL) { /* insert at head */
cptr = uptr->next = sim_clock_queue;
sim_clock_queue = uptr;
}
else {
cptr = uptr->next = prvptr->next; /* insert at prvptr */
prvptr->next = uptr;
}
uptr->time = event_time - accum;
if (cptr != QUEUE_LIST_END)
cptr->time = cptr->time - uptr->time;
sim_interval = sim_clock_queue->time;
return SCPE_OK;
}
/* sim_activate_abs - activate (queue) event even if event already scheduled
Inputs:
uptr = pointer to unit
event_time = relative timeout
Outputs:
reason = result (SCPE_OK if ok)
*/
t_stat sim_activate_abs (UNIT *uptr, int32 event_time)
{
AIO_ACTIVATE (sim_activate_abs, uptr, event_time);
sim_cancel (uptr);
return _sim_activate (uptr, event_time);
}
/* sim_activate_notbefore - activate (queue) event even if event already scheduled
but not before the specified time
Inputs:
uptr = pointer to unit
rtime = relative timeout
Outputs:
reason = result (SCPE_OK if ok)
*/
t_stat sim_activate_notbefore (UNIT *uptr, int32 rtime)
{
uint32 rtimenow, urtime = (uint32)rtime;
AIO_ACTIVATE (sim_activate_notbefore, uptr, rtime);
sim_cancel (uptr);
rtimenow = sim_grtime();
sim_cancel (uptr);
if (0x80000000 <= urtime-rtimenow)
return _sim_activate (uptr, 0);
else
return sim_activate (uptr, urtime-rtimenow);
}
/* sim_activate_after - activate (queue) event
Inputs:
uptr = pointer to unit
usec_delay = relative timeout (in microseconds)
Outputs:
reason = result (SCPE_OK if ok)
*/
t_stat sim_activate_after (UNIT *uptr, int32 event_time)
{
return _sim_activate_after (uptr, event_time);
}
t_stat _sim_activate_after (UNIT *uptr, int32 usec_delay)
{
if (sim_is_active (uptr)) /* already active? */
return SCPE_OK;
AIO_ACTIVATE (_sim_activate_after, uptr, usec_delay);
return sim_timer_activate_after (uptr, usec_delay);
}
/* sim_cancel - cancel (dequeue) event
Inputs:
uptr = pointer to unit
Outputs:
reason = result (SCPE_OK if ok)
*/
t_stat sim_cancel (UNIT *uptr)
{
UNIT *cptr, *nptr;
AIO_VALIDATE;
AIO_CANCEL(uptr);
AIO_UPDATE_QUEUE;
if (sim_clock_queue == QUEUE_LIST_END)
return SCPE_OK;
sim_debug (SIM_DBG_EVENT, sim_dflt_dev, "Canceling Event for %s\n", sim_uname(uptr));
UPDATE_SIM_TIME; /* update sim time */
if (!sim_is_active (uptr))
return SCPE_OK;
nptr = QUEUE_LIST_END;
if (sim_clock_queue == uptr) {
nptr = sim_clock_queue = uptr->next;
uptr->next = NULL; /* hygiene */
}
else {
for (cptr = sim_clock_queue; cptr != QUEUE_LIST_END; cptr = cptr->next) {
if (cptr->next == uptr) {
nptr = cptr->next = uptr->next;
uptr->next = NULL; /* hygiene */
break; /* end queue scan */
}
}
}
if (nptr != QUEUE_LIST_END)
nptr->time += (uptr->next) ? 0 : uptr->time;
if (!uptr->next)
uptr->time = 0;
if (sim_clock_queue != QUEUE_LIST_END)
sim_interval = sim_clock_queue->time;
else sim_interval = noqueue_time = NOQUEUE_WAIT;
if (uptr->next) {
if (sim_deb) {
sim_debug (SIM_DBG_EVENT, sim_dflt_dev, "Cancel failed for %s\n", sim_uname(uptr));
fclose(sim_deb);
}
abort ();
}
return SCPE_OK;
}
/* sim_is_active - test for entry in queue
Inputs:
uptr = pointer to unit
Outputs:
result = TRUE if unit is busy, FALSE inactive
*/
t_bool sim_is_active (UNIT *uptr)
{
AIO_VALIDATE;
AIO_UPDATE_QUEUE;
return (((uptr->next) || AIO_IS_ACTIVE(uptr)) ? TRUE : FALSE);
}
/* sim_activate_time - return activation time
Inputs:
uptr = pointer to unit
Outputs:
result = absolute activation time + 1, 0 if inactive
*/
int32 sim_activate_time (UNIT *uptr)
{
UNIT *cptr;
int32 accum = 0;
AIO_VALIDATE;
AIO_RETURN_TIME(uptr);
for (cptr = sim_clock_queue; cptr != QUEUE_LIST_END; cptr = cptr->next) {
if (cptr == sim_clock_queue) {
if (sim_interval > 0)
accum = accum + sim_interval;
}
else accum = accum + cptr->time;
if (cptr == uptr)
return accum + 1;
}
return 0;
}
/* sim_gtime - return global time
sim_grtime - return global time with rollover
Inputs: none
Outputs:
time = global time
*/
double sim_gtime (void)
{
if (AIO_MAIN_THREAD) {
UPDATE_SIM_TIME;
}
return sim_time;
}
uint32 sim_grtime (void)
{
UPDATE_SIM_TIME;
return sim_rtime;
}
/* sim_qcount - return queue entry count
Inputs: none
Outputs:
count = number of entries on the queue
*/
int32 sim_qcount (void)
{
int32 cnt;
UNIT *uptr;
cnt = 0;
for (uptr = sim_clock_queue; uptr != QUEUE_LIST_END; uptr = uptr->next)
cnt++;
return cnt;
}
/* Breakpoint package. This module replaces the VM-implemented one
instruction breakpoint capability.
Breakpoints are stored in table sim_brk_tab, which is ordered by address for
efficient binary searching. A breakpoint consists of a four entry structure:
addr address of the breakpoint
type types of breakpoints set on the address
a bit mask representing letters A-Z
cnt number of iterations before breakp is taken
action pointer command string to be executed
when break is taken
sim_brk_summ is a summary of the types of breakpoints that are currently set (it
is the bitwise OR of all the type fields). A simulator need only check for
a breakpoint of type X if bit SWMASK('X') is set in sim_brk_sum.
The package contains the following public routines:
sim_brk_init initialize
sim_brk_set set breakpoint
sim_brk_clr clear breakpoint
sim_brk_clrall clear all breakpoints
sim_brk_show show breakpoint
sim_brk_showall show all breakpoints
sim_brk_test test for breakpoint
sim_brk_npc PC has been changed
sim_brk_getact get next action
sim_brk_clract clear pending actions
Initialize breakpoint system.
*/
t_stat sim_brk_init (void)
{
sim_brk_lnt = SIM_BRK_INILNT;
sim_brk_tab = (BRKTAB *) calloc (sim_brk_lnt, sizeof (BRKTAB));
if (sim_brk_tab == NULL)
return SCPE_MEM;
sim_brk_ent = sim_brk_ins = 0;
sim_brk_clract ();
sim_brk_npc (0);
return SCPE_OK;
}
/* Search for a breakpoint in the sorted breakpoint table */
BRKTAB *sim_brk_fnd (t_addr loc)
{
int32 lo, hi, p;
BRKTAB *bp;
if (sim_brk_ent == 0) { /* table empty? */
sim_brk_ins = 0; /* insrt at head */
return NULL; /* sch fails */
}
lo = 0; /* initial bounds */
hi = sim_brk_ent - 1;
do {
p = (lo + hi) >> 1; /* probe */
bp = sim_brk_tab + p; /* table addr */
if (loc == bp->addr) /* match? */
return bp;
else if (loc < bp->addr) /* go down? p is upper */
hi = p - 1;
else lo = p + 1; /* go up? p is lower */
} while (lo <= hi);
if (loc < bp->addr) /* insrt before or */
sim_brk_ins = p;
else sim_brk_ins = p + 1; /* after last sch */
return NULL;
}
/* Insert a breakpoint */
BRKTAB *sim_brk_new (t_addr loc)
{
int32 i, t;
BRKTAB *bp, *newp;
if (sim_brk_ins < 0)
return NULL;
if (sim_brk_ent >= sim_brk_lnt) { /* out of space? */
t = sim_brk_lnt + SIM_BRK_INILNT; /* new size */
newp = (BRKTAB *) calloc (t, sizeof (BRKTAB)); /* new table */
if (newp == NULL) /* can't extend */
return NULL;
for (i = 0; i < sim_brk_lnt; i++) /* copy table */
*(newp + i) = *(sim_brk_tab + i);
free (sim_brk_tab); /* free old table */
sim_brk_tab = newp; /* new base, lnt */
sim_brk_lnt = t;
}
if (sim_brk_ins != sim_brk_ent) { /* move needed? */
for (bp = sim_brk_tab + sim_brk_ent;
bp > sim_brk_tab + sim_brk_ins; bp--)
*bp = *(bp - 1);
}
bp = sim_brk_tab + sim_brk_ins;
bp->addr = loc;
bp->typ = 0;
bp->cnt = 0;
bp->act = NULL;
sim_brk_ent = sim_brk_ent + 1;
return bp;
}
/* Set a breakpoint of type sw */
t_stat sim_brk_set (t_addr loc, int32 sw, int32 ncnt, char *act)
{
BRKTAB *bp;
if (sw == 0) sw = sim_brk_dflt;
if ((sim_brk_types & sw) == 0)
return SCPE_NOFNC;
if ((sw & BRK_TYP_DYN_ALL) && act) /* can't specify an action with a dynamic breakpoint */
return SCPE_ARG;
bp = sim_brk_fnd (loc); /* present? */
if (!bp) /* no, allocate */
bp = sim_brk_new (loc);
if (!bp) /* still no? mem err */
return SCPE_MEM;
bp->typ |= sw; /* set type */
bp->cnt = ncnt; /* set count */
if ((!(sw & BRK_TYP_DYN_ALL)) && /* Not Dynamic and */
(bp->act != NULL) && (act != NULL)) { /* replace old action? */
free (bp->act); /* deallocate */
bp->act = NULL; /* now no action */
}
if ((act != NULL) && (*act != 0)) { /* new action? */
char *newp = (char *) calloc (CBUFSIZE+1, sizeof (char)); /* alloc buf */
if (newp == NULL) /* mem err? */
return SCPE_MEM;
strncpy (newp, act, CBUFSIZE); /* copy action */
bp->act = newp; /* set pointer */
}
sim_brk_summ = sim_brk_summ | sw;
return SCPE_OK;
}
/* Clear a breakpoint */
t_stat sim_brk_clr (t_addr loc, int32 sw)
{
BRKTAB *bp = sim_brk_fnd (loc);
if (!bp) /* not there? ok */
return SCPE_OK;
if (sw == 0)
sw = SIM_BRK_ALLTYP;
bp->typ = bp->typ & ~sw;
if (bp->typ) /* clear all types? */
return SCPE_OK;
if (bp->act != NULL) /* deallocate action */
free (bp->act);
for ( ; bp < (sim_brk_tab + sim_brk_ent - 1); bp++) /* erase entry */
*bp = *(bp + 1);
sim_brk_ent = sim_brk_ent - 1; /* decrement count */
sim_brk_summ = 0; /* recalc summary */
for (bp = sim_brk_tab; bp < (sim_brk_tab + sim_brk_ent); bp++)
sim_brk_summ = sim_brk_summ | bp->typ;
return SCPE_OK;
}
/* Clear all breakpoints */
t_stat sim_brk_clrall (int32 sw)
{
BRKTAB *bp;
if (sw == 0) sw = SIM_BRK_ALLTYP;
for (bp = sim_brk_tab; bp < (sim_brk_tab + sim_brk_ent); ) {
if (bp->typ & sw)
sim_brk_clr (bp->addr, sw);
else bp++;
}
return SCPE_OK;
}
/* Show a breakpoint */
t_stat sim_brk_show (FILE *st, t_addr loc, int32 sw)
{
BRKTAB *bp = sim_brk_fnd (loc);
DEVICE *dptr;
int32 i, any;
if ((sw == 0) || (sw == SWMASK ('C')))
sw = SIM_BRK_ALLTYP | ((sw == SWMASK ('C')) ? SWMASK ('C') : 0);
if (!bp || (!(bp->typ & sw)))
return SCPE_OK;
dptr = sim_dflt_dev;
if (dptr == NULL)
return SCPE_OK;
if (sw & SWMASK ('C'))
fprintf (st, "SET BREAK ");
else {
if (sim_vm_fprint_addr)
sim_vm_fprint_addr (st, dptr, loc);
else fprint_val (st, loc, dptr->aradix, dptr->awidth, PV_LEFT);
fprintf (st, ":\t");
}
for (i = any = 0; i < 26; i++) {
if ((bp->typ >> i) & 1) {
if ((sw & SWMASK ('C')) == 0) {
if (any)
fprintf (st, ", ");
fputc (i + 'A', st);
}
else
fprintf (st, "-%c", i + 'A');
any = 1;
}
}
if (sw & SWMASK ('C')) {
fprintf (st, " ");
if (sim_vm_fprint_addr)
sim_vm_fprint_addr (st, dptr, loc);
else fprint_val (st, loc, dptr->aradix, dptr->awidth, PV_LEFT);
}
if (bp->cnt > 0)
fprintf (st, " [%d]", bp->cnt);
if (bp->act != NULL)
fprintf (st, "; %s", bp->act);
fprintf (st, "\n");
return SCPE_OK;
}
/* Show all breakpoints */
t_stat sim_brk_showall (FILE *st, int32 sw)
{
BRKTAB *bp;
if ((sw == 0) || (sw == SWMASK ('C')))
sw = SIM_BRK_ALLTYP | ((sw == SWMASK ('C')) ? SWMASK ('C') : 0);
for (bp = sim_brk_tab; bp < (sim_brk_tab + sim_brk_ent); bp++) {
if (bp->typ & sw)
sim_brk_show (st, bp->addr, sw);
}
return SCPE_OK;
}
/* Test for breakpoint */
uint32 sim_brk_test (t_addr loc, uint32 btyp)
{
BRKTAB *bp;
uint32 spc = (btyp >> SIM_BKPT_V_SPC) & (SIM_BKPT_N_SPC - 1);
if (sim_brk_summ & BRK_TYP_DYN_ALL)
btyp |= BRK_TYP_DYN_ALL;
if ((bp = sim_brk_fnd (loc)) && (btyp & bp->typ)) { /* in table, and type match? */
if ((sim_brk_pend[spc] && (loc == sim_brk_ploc[spc])) || /* previous location? */
(--bp->cnt > 0)) /* count > 0? */
return 0;
bp->cnt = 0; /* reset count */
sim_brk_ploc[spc] = loc; /* save location */
sim_brk_pend[spc] = TRUE; /* don't do twice */
sim_brk_setact (bp->act); /* set up actions */
return (btyp & bp->typ);
}
sim_brk_pend[spc] = FALSE;
return 0;
}
/* Get next pending action, if any */
char *sim_brk_getact (char *buf, int32 size)
{
char *ep;
size_t lnt;
if (sim_brk_act[sim_do_depth] == NULL) /* any action? */
return NULL;
while (sim_isspace (*sim_brk_act[sim_do_depth])) /* skip spaces */
sim_brk_act[sim_do_depth]++;
if (*sim_brk_act[sim_do_depth] == 0) { /* now empty? */
return sim_brk_clract ();
}
if ((ep = strchr (sim_brk_act[sim_do_depth], ';'))) { /* cmd delimiter? */
lnt = ep - sim_brk_act[sim_do_depth]; /* cmd length */
memcpy (buf, sim_brk_act[sim_do_depth], lnt + 1); /* copy with ; */
buf[lnt] = 0; /* erase ; */
sim_brk_act[sim_do_depth] += lnt + 1; /* adv ptr */
}
else {
strncpy (buf, sim_brk_act[sim_do_depth], size); /* copy action */
sim_brk_clract (); /* no more */
}
return buf;
}
/* Clear pending actions */
char *sim_brk_clract (void)
{
free (sim_brk_act_buf[sim_do_depth]);
return sim_brk_act[sim_do_depth] = sim_brk_act_buf[sim_do_depth] = NULL;
}
/* Set up pending actions */
void sim_brk_setact (const char *action)
{
if (action) {
sim_brk_act_buf[sim_do_depth] = realloc (sim_brk_act_buf[sim_do_depth], strlen (action) + 1);
strcpy (sim_brk_act_buf[sim_do_depth], action);
sim_brk_act[sim_do_depth] = sim_brk_act_buf[sim_do_depth];
}
else
sim_brk_clract ();
}
/* New PC */
void sim_brk_npc (uint32 cnt)
{
uint32 i;
if ((cnt == 0) || (cnt > SIM_BKPT_N_SPC))
cnt = SIM_BKPT_N_SPC;
for (i = 0; i < cnt; i++) {
sim_brk_pend[i] = FALSE;
sim_brk_ploc[i] = 0;
}
return;
}
/* Clear breakpoint space */
void sim_brk_clrspc (uint32 spc)
{
if (spc < SIM_BKPT_N_SPC) {
sim_brk_pend[spc] = FALSE;
sim_brk_ploc[spc] = 0;
}
return;
}
/* Expect package. This code provides a mechanism to stop and control simulator
execution based on traffic coming out of simulated ports and as well as a means
to inject data into those ports. It can conceptually viewed as a string
breakpoint package.
Expect rules are stored in tables associated with each port which can use this
facility. An expect rule consists of a five entry structure:
match the expect match string
size the number of bytes in the match string
match_pattern the expect match string in display format
cnt number of iterations before match is declared
action command string to be executed when match occurs
All active expect rules are contained in an expect match context structure.
rules the match rules
size the count of match rules
buf the buffer of output data which has been produced
buf_ins the buffer insertion point for the next output data
buf_size the buffer size
The package contains the following public routines:
sim_set_expect expect command parser and intializer
sim_set_noexpect noexpect command parser
sim_exp_init initialize an expect context
sim_exp_set set or add an expect rule
sim_exp_clr clear or delete an expect rule
sim_exp_clrall clear all expect rules
sim_exp_show show an expect rule
sim_exp_showall show all expect rules
sim_exp_check test for rule match
*/
/* Initialize an expect context. */
t_stat sim_exp_init (EXPECT *exp)
{
memset (exp, 0, sizeof(*exp));
return SCPE_OK;
}
/* Set expect */
t_stat sim_set_expect (EXPECT *exp, char *cptr)
{
char gbuf[CBUFSIZE], *tptr;
const char *c1ptr;
uint32 after = exp->after;
int32 cnt = 0;
t_stat r;
if ((cptr == NULL) || (*cptr == 0))
return SCPE_2FARG;
if (*cptr == '[') {
cnt = (int32) strtotv (cptr + 1, &c1ptr, 10);
if ((cptr == c1ptr) || (*c1ptr != ']'))
return sim_messagef (SCPE_ARG, "Invalid Repeat count specification\n");
cptr = (char *)(c1ptr + 1);
while (sim_isspace(*cptr))
++cptr;
}
tptr = get_glyph (cptr, gbuf, ',');
if ((!strncmp(gbuf, "HALTAFTER=", 10)) && (gbuf[10])) {
after = (uint32)get_uint (&gbuf[10], 10, 100000000, &r);
if (r != SCPE_OK)
return sim_messagef (SCPE_ARG, "Invalid Halt After Value\n");
cptr = tptr;
}
if ((*cptr != '"') && (*cptr != '\''))
return sim_messagef (SCPE_ARG, "String must be quote delimited\n");
cptr = get_glyph_quoted (cptr, gbuf, 0);
return sim_exp_set (exp, gbuf, cnt, (after ? after : exp->after), sim_switches, cptr);
}
/* Clear expect */
t_stat sim_set_noexpect (EXPECT *exp, char *cptr)
{
char gbuf[CBUFSIZE];
if (!cptr || !*cptr)
return sim_exp_clrall (exp); /* clear all rules */
if ((*cptr != '"') && (*cptr != '\''))
return sim_messagef (SCPE_ARG, "String must be quote delimited\n");
cptr = get_glyph_quoted (cptr, gbuf, 0);
if (*cptr != '\0')
return SCPE_2MARG; /* No more arguments */
return sim_exp_clr (exp, gbuf); /* clear one rule */
}
/* Search for an expect rule in an expect context */
EXPTAB *sim_exp_fnd (EXPECT *exp, const char *match)
{
int32 i;
if (!exp->rules)
return NULL;
for (i=0; i<exp->size; i++)
if (!strcmp (exp->rules[i].match_pattern, match))
return &exp->rules[i];
return NULL;
}
/* Clear (delete) an expect rule */
t_stat sim_exp_clr_tab (EXPECT *exp, EXPTAB *ep)
{
int32 i;
if (!ep) /* not there? ok */
return SCPE_OK;
free (ep->match); /* deallocate match string */
free (ep->match_pattern); /* deallocate the display format match string */
free (ep->act); /* deallocate action */
#if defined(USE_REGEX)
if (ep->switches & EXP_TYP_REGEX)
regfree (&ep->regex); /* release compiled regex */
#endif
exp->size -= 1; /* decrement count */
for (i=ep-exp->rules; i<exp->size; i++) /* shuffle up remaining rules */
exp->rules[i] = exp->rules[i+1];
if (exp->size == 0) { /* No rules left? */
free (exp->rules);
exp->rules = NULL;
}
return SCPE_OK;
}
t_stat sim_exp_clr (EXPECT *exp, const char *match)
{
return sim_exp_clr_tab (exp, sim_exp_fnd (exp, match));
}
/* Clear all expect rules */
t_stat sim_exp_clrall (EXPECT *exp)
{
int32 i;
for (i=0; i<exp->size; i++) {
free (exp->rules[i].match); /* deallocate match string */
free (exp->rules[i].match_pattern); /* deallocate display format match string */
free (exp->rules[i].act); /* deallocate action */
}
free (exp->rules);
exp->rules = NULL;
exp->size = 0;
free (exp->buf);
exp->buf = NULL;
exp->buf_size = 0;
exp->buf_ins = 0;
return SCPE_OK;
}
/* Set/Add an expect rule */
t_stat sim_exp_set (EXPECT *exp, const char *match, int32 cnt, uint32 after, int32 switches, char *act)
{
EXPTAB *ep;
uint8 *match_buf;
uint32 match_size;
int i;
/* Validate the match string */
match_buf = (uint8 *)calloc (strlen (match) + 1, 1);
if (!match_buf)
return SCPE_MEM;
if (switches & EXP_TYP_REGEX) {
#if !defined (USE_REGEX)
free (match_buf);
return sim_messagef (SCPE_ARG, "RegEx support not available\n");
}
#else /* USE_REGEX */
int res;
regex_t re;
memset (&re, 0, sizeof(re));
memcpy (match_buf, match+1, strlen(match)-2); /* extract string without surrounding quotes */
match_buf[strlen(match)-2] = '\0';
res = regcomp (&re, (char *)match_buf, REG_EXTENDED | ((switches & EXP_TYP_REGEX_I) ? REG_ICASE : 0));
if (res) {
size_t err_size = regerror (res, &re, NULL, 0);
char *err_buf = calloc (err_size+1, 1);
regerror (res, &re, err_buf, err_size);
sim_messagef (SCPE_ARG, "Regular Expression Error: %s\n", err_buf);
free (err_buf);
free (match_buf);
return SCPE_ARG|SCPE_NOMESSAGE;
}
sim_debug (exp->dbit, exp->dptr, "Expect Regular Expression: \"%s\" has %d sub expressions\n", match_buf, (int)re.re_nsub);
regfree (&re);
}
#endif
else {
if (switches & EXP_TYP_REGEX_I) {
free (match_buf);
return sim_messagef (SCPE_ARG, "Case independed matching is only valid for RegEx expect rules\n");
}
sim_data_trace(exp->dptr, exp->dptr->units, (uint8 *)match, "", strlen(match)+1, "Expect Match String", exp->dbit);
if (SCPE_OK != sim_decode_quoted_string (match, match_buf, &match_size)) {
free (match_buf);
return sim_messagef (SCPE_ARG, "Invalid quoted string\n");
}
}
free (match_buf);
ep = sim_exp_fnd (exp, match); /* present? */
if (ep) /* no, allocate */
sim_exp_clr_tab (exp, ep); /* clear it */
if (after && exp->size)
return sim_messagef (SCPE_ARG, "Multiple concurrent EXPECT rules aren't valid when a HALTAFTER parameter is non-zero\n");
exp->rules = (EXPTAB *) realloc (exp->rules, sizeof (*exp->rules)*(exp->size + 1));
ep = &exp->rules[exp->size];
exp->size += 1;
exp->after = after; /* set halt after value */
memset (ep, 0, sizeof(*ep));
ep->match_pattern = (char *)malloc (strlen (match) + 1);
strcpy (ep->match_pattern, match);
ep->cnt = cnt; /* set proceed count */
ep->switches = switches; /* set switches */
match_buf = (uint8 *)calloc (strlen (match) + 1, 1);
if (!match_buf) {
sim_exp_clr_tab (exp, ep); /* clear it */
return SCPE_MEM;
}
if (switches & EXP_TYP_REGEX) {
#if defined(USE_REGEX)
memcpy (match_buf, match+1, strlen(match)-2); /* extract string without surrounding quotes */
match_buf[strlen(match)-2] = '\0';
regcomp (&ep->regex, (char *)match_buf, REG_EXTENDED);
#endif
free (match_buf);
match_buf = NULL;
}
else {
sim_data_trace(exp->dptr, exp->dptr->units, (uint8 *)match, "", strlen(match)+1, "Expect Match String", exp->dbit);
sim_decode_quoted_string (match, match_buf, &match_size);
ep->match = match_buf;
ep->size = match_size;
}
ep->match_pattern = (char *)malloc (strlen (match) + 1);
strcpy (ep->match_pattern, match);
if (ep->act) { /* replace old action? */
free (ep->act); /* deallocate */
ep->act = NULL; /* now no action */
}
if (act) while (sim_isspace(*act)) ++act; /* skip leading spaces in action string */
if ((act != NULL) && (*act != 0)) { /* new action? */
char *newp = (char *) calloc (strlen (act)+1, sizeof (*act)); /* alloc buf */
if (newp == NULL) /* mem err? */
return SCPE_MEM;
strcpy (newp, act); /* copy action */
ep->act = newp; /* set pointer */
}
/* Make sure that the production buffer is large enough to detect a match for all rules including a NUL termination byte */
for (i=0; i<exp->size; i++) {
uint32 compare_size = (exp->rules[i].switches & EXP_TYP_REGEX) ? MAX(10 * strlen(ep->match_pattern), 1024) : exp->rules[i].size;
if (compare_size >= exp->buf_size) {
exp->buf = (uint8 *)realloc (exp->buf, compare_size + 2); /* Extra byte to null terminate regex compares */
exp->buf_size = compare_size + 1;
}
}
return SCPE_OK;
}
/* Show an expect rule */
t_stat sim_exp_show_tab (FILE *st, EXPECT *exp, EXPTAB *ep)
{
if (!ep)
return SCPE_OK;
fprintf (st, "EXPECT");
if (ep->switches & EXP_TYP_PERSIST)
fprintf (st, " -p");
if (ep->switches & EXP_TYP_CLEARALL)
fprintf (st, " -c");
if (ep->switches & EXP_TYP_REGEX)
fprintf (st, " -r");
if (ep->switches & EXP_TYP_REGEX_I)
fprintf (st, " -i");
fprintf (st, " %s", ep->match_pattern);
if (ep->cnt > 0)
fprintf (st, " [%d]", ep->cnt);
if (ep->act)
fprintf (st, " %s", ep->act);
fprintf (st, "\n");
return SCPE_OK;
}
t_stat sim_exp_show (FILE *st, EXPECT *exp, const char *match)
{
EXPTAB *ep = sim_exp_fnd (exp, match);
if (exp->buf_size) {
char *bstr = sim_encode_quoted_string (exp->buf, exp->buf_ins);
fprintf (st, "Match Buffer Size: %d\n", exp->buf_size);
fprintf (st, "Buffer Insert Offset: %d\n", exp->buf_ins);
fprintf (st, "Buffer Contents: %s\n", bstr);
free (bstr);
}
if (exp->after)
fprintf (st, "Halt After: %d instructions\n", exp->after);
if (exp->dptr && exp->dbit)
fprintf (st, "Debugging via: SET %s DEBUG%s%s\n", sim_dname(exp->dptr), exp->dptr->debflags ? "=" : "", exp->dptr->debflags ? get_dbg_verb (exp->dbit, exp->dptr) : "");
if (!*match)
return sim_exp_showall (st, exp);
if (!ep)
return SCPE_ARG;
return sim_exp_show_tab (st, exp, ep);
}
/* Show all expect rules */
t_stat sim_exp_showall (FILE *st, EXPECT *exp)
{
int32 i;
for (i=0; i < exp->size; i++)
sim_exp_show_tab (st, exp, &exp->rules[i]);
return SCPE_OK;
}
/* Test for expect match */
t_stat sim_exp_check (EXPECT *exp, uint8 data)
{
int32 i;
EXPTAB *ep;
int regex_checks = 0;
char *tstr = NULL;
if ((!exp) || (!exp->rules)) /* Anying to check? */
return SCPE_OK;
exp->buf[exp->buf_ins++] = data; /* Save new data */
exp->buf[exp->buf_ins] = '\0'; /* Nul terminate for RegEx match */
for (i=0; i < exp->size; i++) {
ep = &exp->rules[i];
if (ep->switches & EXP_TYP_REGEX) {
#if defined (USE_REGEX)
regmatch_t *matches;
char *cbuf = (char *)exp->buf;
static size_t sim_exp_match_sub_count = 0;
if (tstr)
cbuf = tstr;
else {
if (strlen ((char *)exp->buf) != exp->buf_ins) { /* Nul characters in buffer? */
size_t off;
tstr = malloc (exp->buf_ins + 1);
tstr[0] = '\0';
for (off=0; off < exp->buf_ins; off += 1 + strlen ((char *)&exp->buf[off]))
strcpy (&tstr[strlen (tstr)], (char *)&exp->buf[off]);
cbuf = tstr;
}
}
++regex_checks;
matches = calloc ((ep->regex.re_nsub + 1), sizeof(*matches));
if (sim_deb && exp->dptr && (exp->dptr->dctrl & exp->dbit)) {
char *estr = sim_encode_quoted_string (exp->buf, exp->buf_ins);
sim_debug (exp->dbit, exp->dptr, "Checking String: %s\n", estr);
sim_debug (exp->dbit, exp->dptr, "Against RegEx Match Rule: %s\n", ep->match_pattern);
free (estr);
}
if (!regexec (&ep->regex, cbuf, ep->regex.re_nsub + 1, matches, REG_NOTBOL)) {
size_t j;
char *buf = malloc (1 + exp->buf_ins);
for (j=0; j<ep->regex.re_nsub + 1; j++) {
char env_name[32];
sprintf (env_name, "_EXPECT_MATCH_GROUP_%d", (int)j);
memcpy (buf, &cbuf[matches[j].rm_so], matches[j].rm_eo-matches[j].rm_so);
buf[matches[j].rm_eo-matches[j].rm_so] = '\0';
setenv (env_name, buf, 1); /* Make the match and substrings available as environment variables */
sim_debug (exp->dbit, exp->dptr, "%s=%s\n", env_name, buf);
}
for (; j<sim_exp_match_sub_count; j++) {
char env_name[32];
sprintf (env_name, "_EXPECT_MATCH_GROUP_%d", (int)j);
setenv (env_name, "", 1); /* Remove previous extra environment variables */
}
sim_exp_match_sub_count = ep->regex.re_nsub;
free (matches);
free (buf);
break;
}
free (matches);
#endif
}
else {
if (exp->buf_ins < ep->size) { /* Match stradle end of buffer */
/*
* First compare the newly deposited data at the beginning
* of buffer with the end of the match string
*/
if (exp->buf_ins) {
if (sim_deb && exp->dptr && (exp->dptr->dctrl & exp->dbit)) {
char *estr = sim_encode_quoted_string (exp->buf, exp->buf_ins);
char *mstr = sim_encode_quoted_string (&ep->match[ep->size-exp->buf_ins], exp->buf_ins);
sim_debug (exp->dbit, exp->dptr, "Checking String[0:%d]: %s\n", exp->buf_ins, estr);
sim_debug (exp->dbit, exp->dptr, "Against Match Data: %s\n", mstr);
free (estr);
free (mstr);
}
if (memcmp (exp->buf, &ep->match[ep->size-exp->buf_ins], exp->buf_ins))
continue;
}
if (sim_deb && exp->dptr && (exp->dptr->dctrl & exp->dbit)) {
char *estr = sim_encode_quoted_string (&exp->buf[exp->buf_size-(ep->size-exp->buf_ins)], ep->size-exp->buf_ins);
char *mstr = sim_encode_quoted_string (ep->match, ep->size-exp->buf_ins);
sim_debug (exp->dbit, exp->dptr, "Checking String[%d:%d]: %s\n", exp->buf_size-(ep->size-exp->buf_ins), ep->size-exp->buf_ins, estr);
sim_debug (exp->dbit, exp->dptr, "Against Match Data: %s\n", mstr);
free (estr);
free (mstr);
}
if (memcmp (&exp->buf[exp->buf_size-(ep->size-exp->buf_ins)], ep->match, ep->size-exp->buf_ins))
continue;
break;
}
else {
if (sim_deb && exp->dptr && (exp->dptr->dctrl & exp->dbit)) {
char *estr = sim_encode_quoted_string (&exp->buf[exp->buf_ins-ep->size], ep->size);
char *mstr = sim_encode_quoted_string (ep->match, ep->size);
sim_debug (exp->dbit, exp->dptr, "Checking String[%d:%d]: %s\n", exp->buf_ins-ep->size, ep->size, estr);
sim_debug (exp->dbit, exp->dptr, "Against Match Data: %s\n", mstr);
free (estr);
free (mstr);
}
if (memcmp (&exp->buf[exp->buf_ins-ep->size], ep->match, ep->size))
continue;
break;
}
}
}
if (exp->buf_ins == exp->buf_size) { /* At end of match buffer? */
if (regex_checks) {
/* When processing regular expressions, let the match buffer fill
up and then shuffle the buffer contents down by half the buffer size
so that the regular expression has a single contiguous buffer to
match against instead of the wrapping buffer paradigm which is
used when no regular expression rules are in effect */
memmove (exp->buf, &exp->buf[exp->buf_size/2], exp->buf_size-(exp->buf_size/2));
exp->buf_ins -= exp->buf_size/2;
sim_debug (exp->dbit, exp->dptr, "Buffer Full - sliding the last %d bytes to start of buffer new insert at: %d\n", (exp->buf_size/2), exp->buf_ins);
}
else {
exp->buf_ins = 0; /* wrap around to beginning */
sim_debug (exp->dbit, exp->dptr, "Buffer wrapping\n");
}
}
if (i != exp->size) { /* Found? */
sim_debug (exp->dbit, exp->dptr, "Matched expect pattern: %s\n", ep->match_pattern);
setenv ("_EXPECT_MATCH_PATTERN", ep->match_pattern, 1); /* Make the match detail available as an environment variable */
if (ep->cnt > 0) {
ep->cnt -= 1;
sim_debug (exp->dbit, exp->dptr, "Waiting for %d more match%s before stopping\n",
ep->cnt, (ep->cnt == 1) ? "" : "es");
}
else {
if (ep->act && *ep->act) {
sim_debug (exp->dbit, exp->dptr, "Initiating actions: %s\n", ep->act);
}
else {
sim_debug (exp->dbit, exp->dptr, "No actions specified, stopping...\n");
}
sim_brk_setact (ep->act); /* set up actions */
if (ep->switches & EXP_TYP_CLEARALL) /* Clear-all expect rule? */
sim_exp_clrall (exp); /* delete all rules */
else {
if (!(ep->switches & EXP_TYP_PERSIST)) /* One shot expect rule? */
sim_exp_clr_tab (exp, ep); /* delete it */
}
sim_activate (&sim_expect_unit, /* schedule simulation stop when indicated */
(ep->switches & EXP_TYP_TIME) ?
(int32)((sim_timer_inst_per_sec ()*exp->after)/1000000.0) :
exp->after);
}
/* Matched data is no longer available for future matching */
exp->buf_ins = 0;
}
free (tstr);
return SCPE_OK;
}
/* Queue input data for sending */
t_stat sim_send_input (SEND *snd, uint8 *data, size_t size, uint32 after, uint32 delay)
{
if (snd->extoff != 0) {
if (snd->insoff-snd->extoff > 0)
memmove(snd->buffer, snd->buffer+snd->extoff, snd->insoff-snd->extoff);
snd->insoff -= snd->extoff;
snd->extoff -= snd->extoff;
}
if (snd->insoff+size > snd->bufsize) {
snd->bufsize = snd->insoff+size;
snd->buffer = realloc(snd->buffer, snd->bufsize);
}
memcpy(snd->buffer+snd->insoff, data, size);
snd->insoff += size;
if (delay)
snd->delay = (sim_switches & SWMASK ('T')) ? (uint32)((sim_timer_inst_per_sec()*delay)/1000000.0) : delay;
if (after)
snd->after = (sim_switches & SWMASK ('T')) ? (uint32)((sim_timer_inst_per_sec()*after)/1000000.0) : after;
if (snd->after == 0)
snd->after = snd->delay;
snd->next_time = sim_gtime() + snd->after;
return SCPE_OK;
}
/* Cancel Queued input data */
t_stat sim_send_clear (SEND *snd)
{
snd->insoff = 0;
snd->extoff = 0;
return SCPE_OK;
}
/* Display console Queued input data status */
t_stat sim_show_send_input (FILE *st, SEND *snd)
{
if (snd->extoff < snd->insoff) {
fprintf (st, "%d bytes of pending input Data:\n ", snd->insoff-snd->extoff);
fprint_buffer_string (st, snd->buffer+snd->extoff, snd->insoff-snd->extoff);
fprintf (st, "\n");
}
else
fprintf (st, "No Pending Input Data\n");
if ((snd->next_time - sim_gtime()) > 0)
if ((snd->next_time - sim_gtime()) > (sim_timer_inst_per_sec()/1000000.0))
fprintf (st, "Minimum of %d instructions (%d microseconds) befor sending first character\n", (int)(snd->next_time - sim_gtime()),
(int)((snd->next_time - sim_gtime())/(sim_timer_inst_per_sec()/1000000.0)));
else
fprintf (st, "Minimum of %d instructions befor sending first character\n", (int)(snd->next_time - sim_gtime()));
if (snd->delay > (sim_timer_inst_per_sec()/1000000.0))
fprintf (st, "Minimum of %d instructions (%d microseconds) between characters\n", (int)snd->delay, (int)(snd->delay/(sim_timer_inst_per_sec()/1000000.0)));
else
fprintf (st, "Minimum of %d instructions between characters\n", (int)snd->delay);
if (snd->dptr && snd->dbit)
fprintf (st, "Debugging via: SET %s DEBUG%s%s\n", sim_dname(snd->dptr), snd->dptr->debflags ? "=" : "", snd->dptr->debflags ? get_dbg_verb (snd->dbit, snd->dptr) : "");
return SCPE_OK;
}
/* Poll for Queued input data */
t_bool sim_send_poll_data (SEND *snd, t_stat *stat)
{
if (snd && (snd->extoff < snd->insoff)) { /* pending input characters available? */
if (sim_gtime() < snd->next_time) { /* too soon? */
*stat = SCPE_OK;
sim_debug (snd->dbit, snd->dptr, "Too soon to inject next byte\n");
}
else {
char dstr[8] = "";
*stat = snd->buffer[snd->extoff++] | SCPE_KFLAG;/* get one */
snd->next_time = sim_gtime() + snd->delay;
if (sim_isgraph(*stat & 0xFF) || ((*stat & 0xFF) == ' '))
sprintf (dstr, " '%c'", *stat & 0xFF);
sim_debug (snd->dbit, snd->dptr, "Byte value: 0x%02X%s injected\n", *stat & 0xFF, dstr);
}
return TRUE;
}
return FALSE;
}
/* Message Text */
const char *sim_error_text (t_stat stat)
{
static char msgbuf[64];
stat &= ~(SCPE_KFLAG|SCPE_BREAK|SCPE_NOMESSAGE); /* remove any flags */
if (stat == SCPE_OK)
return "No Error";
if ((stat >= SCPE_BASE) && (stat <= SCPE_MAX_ERR))
return scp_errors[stat-SCPE_BASE].message;
sprintf(msgbuf, "Error %d", stat);
return msgbuf;
}
t_stat sim_string_to_stat (char *cptr, t_stat *stat)
{
char gbuf[CBUFSIZE];
int32 cond;
*stat = SCPE_ARG;
cptr = get_glyph (cptr, gbuf, 0);
if (0 == memcmp("SCPE_", gbuf, 5))
strcpy (gbuf, gbuf+5); /* skip leading SCPE_ */
for (cond=0; cond < (SCPE_MAX_ERR-SCPE_BASE); cond++)
if (0 == strcmp(scp_errors[cond].code, gbuf)) {
cond += SCPE_BASE;
break;
}
if (0 == strcmp(gbuf, "OK"))
cond = SCPE_OK;
if (cond == (SCPE_MAX_ERR-SCPE_BASE)) { /* not found? */
if (0 == (cond = strtol(gbuf, NULL, 0))) /* try explicit number */
return SCPE_ARG;
}
if (cond > SCPE_MAX_ERR)
return SCPE_ARG;
*stat = cond;
return SCPE_OK;
}
/* Debug printout routines, from Dave Hittner */
const char* debug_bstates = "01_^";
AIO_TLS char debug_line_prefix[256];
int32 debug_unterm = 0;
/* Finds debug phrase matching bitmask from from device DEBTAB table */
static char *get_dbg_verb (uint32 dbits, DEVICE* dptr)
{
static char *debtab_none = "DEBTAB_ISNULL";
static char *debtab_nomatch = "DEBTAB_NOMATCH";
int32 offset = 0;
if (dptr->debflags == 0)
return debtab_none;
/* Find matching words for bitmask */
while (dptr->debflags[offset].name && (offset < 32)) {
if (dptr->debflags[offset].mask & dbits)
return dptr->debflags[offset].name;
offset++;
}
return debtab_nomatch;
}
/* Prints standard debug prefix unless previous call unterminated */
static const char *sim_debug_prefix (uint32 dbits, DEVICE* dptr)
{
char* debug_type = get_dbg_verb (dbits, dptr);
char tim_t[32] = "";
char tim_a[32] = "";
char pc_s[64] = "";
struct timespec time_now;
if (sim_deb_switches & (SWMASK ('T') | SWMASK ('R') | SWMASK ('A'))) {
clock_gettime(CLOCK_REALTIME, &time_now);
if (sim_deb_switches & SWMASK ('R'))
sim_timespec_diff (&time_now, &time_now, &sim_deb_basetime);
if (sim_deb_switches & SWMASK ('T')) {
time_t tnow = (time_t)time_now.tv_sec;
struct tm *now = gmtime(&tnow);
sprintf(tim_t, "%02d:%02d:%02d.%03d ", now->tm_hour, now->tm_min, now->tm_sec, (int)(time_now.tv_nsec/1000000));
}
if (sim_deb_switches & SWMASK ('A')) {
sprintf(tim_t, "%" LL_FMT "d.%03d ", (long long)(time_now.tv_sec), (int)(time_now.tv_nsec/1000000));
}
}
if (sim_deb_switches & SWMASK ('P')) {
t_value val;
/* Some simulators expose the PC as a register, some don't expose it or expose a register
which is not a variable which is updated during instruction execution (i.e. only upon
exit of sim_instr()). For the -P debug option to be effective, such a simulator should
provide a routine which returns the value of the current PC and set the sim_vm_pc_value
routine pointer to that routine.
*/
if (sim_vm_pc_value)
val = (*sim_vm_pc_value)();
else
val = get_rval (sim_PC, 0);
sprintf(pc_s, "-%s:", sim_PC->name);
sprint_val (&pc_s[strlen(pc_s)], val, sim_PC->radix, sim_PC->width, sim_PC->flags & REG_FMT);
}
sprintf(debug_line_prefix, "DBG(%s%s%.0f%s)%s> %s %s: ", tim_t, tim_a, sim_gtime(), pc_s, AIO_MAIN_THREAD ? "" : "+", dptr->name, debug_type);
return debug_line_prefix;
}
void fprint_fields (FILE *stream, t_value before, t_value after, BITFIELD* bitdefs)
{
int32 i, fields, offset;
uint32 value, beforevalue, mask;
for (fields=offset=0; bitdefs[fields].name; ++fields) {
if (bitdefs[fields].offset == 0xffffffff) /* fixup uninitialized offsets */
bitdefs[fields].offset = offset;
offset += bitdefs[fields].width;
}
for (i = fields-1; i >= 0; i--) { /* print xlation, transition */
if (bitdefs[i].name[0] == '\0')
continue;
if ((bitdefs[i].width == 1) && (bitdefs[i].valuenames == NULL)) {
int off = ((after >> bitdefs[i].offset) & 1) + (((before ^ after) >> bitdefs[i].offset) & 1) * 2;
fprintf(stream, "%s%c ", bitdefs[i].name, debug_bstates[off]);
}
else {
char *delta = "";
mask = 0xFFFFFFFF >> (32-bitdefs[i].width);
value = (uint32)((after >> bitdefs[i].offset) & mask);
beforevalue = (uint32)((before >> bitdefs[i].offset) & mask);
if (value < beforevalue)
delta = "_";
if (value > beforevalue)
delta = "^";
if (bitdefs[i].valuenames)
fprintf(stream, "%s=%s%s ", bitdefs[i].name, delta, bitdefs[i].valuenames[value]);
else
if (bitdefs[i].format) {
fprintf(stream, "%s=%s", bitdefs[i].name, delta);
fprintf(stream, bitdefs[i].format, value);
fprintf(stream, " ");
}
else
fprintf(stream, "%s=%s0x%X ", bitdefs[i].name, delta, value);
}
}
}
/* Prints state of a register: bit translation + state (0,1,_,^)
indicating the state and transition of the bit and bitfields. States:
0=steady(0->0), 1=steady(1->1), _=falling(1->0), ^=rising(0->1) */
void sim_debug_bits(uint32 dbits, DEVICE* dptr, BITFIELD* bitdefs,
uint32 before, uint32 after, int terminate)
{
if (sim_deb && dptr && (dptr->dctrl & dbits)) {
if (!debug_unterm)
fprintf(sim_deb, "%s", sim_debug_prefix(dbits, dptr)); /* print prefix if required */
fprint_fields (sim_deb, (t_value)before, (t_value)after, bitdefs); /* print xlation, transition */
if (terminate)
fprintf(sim_deb, "\r\n");
debug_unterm = terminate ? 0 : 1; /* set unterm for next */
}
}
/* Print message to stdout, sim_log (if enabled) and sim_deb (if enabled) */
void sim_printf (const char* fmt, ...)
{
char stackbuf[STACKBUFSIZE];
int32 bufsize = sizeof(stackbuf);
char *buf = stackbuf;
int32 len;
va_list arglist;
while (1) { /* format passed string, args */
va_start (arglist, fmt);
#if defined(NO_vsnprintf)
#if defined(HAS_vsprintf_void)
/* Note, this could blow beyond the buffer, and we couldn't tell */
/* That is a limitation of the C runtime library available on this platform */
vsprintf (buf, fmt, arglist);
for (len = 0; len < bufsize-1; len++)
if (buf[len] == 0) break;
#else
len = vsprintf (buf, fmt, arglist);
#endif /* HAS_vsprintf_void */
#else /* NO_vsnprintf */
#if defined(HAS_vsnprintf_void)
vsnprintf (buf, bufsize-1, fmt, arglist);
for (len = 0; len < bufsize-1; len++)
if (buf[len] == 0) break;
#else
len = vsnprintf (buf, bufsize-1, fmt, arglist);
#endif /* HAS_vsnprintf_void */
#endif /* NO_vsnprintf */
va_end (arglist);
/* If the formatted result didn't fit into the buffer, then grow the buffer and try again */
if ((len < 0) || (len >= bufsize-1)) {
if (buf != stackbuf)
free (buf);
bufsize = bufsize * 2;
buf = (char *) malloc (bufsize);
if (buf == NULL) /* out of memory */
return;
buf[bufsize-1] = '\0';
continue;
}
break;
}
if (sim_is_running) {
char *c, *remnant = buf;
while ((c = strchr(remnant, '\n'))) {
printf("%.*s\r\n", (int)(c-remnant), remnant);
remnant = c + 1;
}
printf("%s", remnant);
}
else
printf("%s", buf);
if (sim_log && (sim_log != stdout))
fprintf (sim_log, "%s", buf);
if (sim_deb && (sim_deb != stdout) && (sim_deb != sim_log))
fprintf (sim_deb, "%s", buf);
if (buf != stackbuf)
free (buf);
}
/* Print command result message to stdout, sim_log (if enabled) and sim_deb (if enabled) */
t_stat sim_messagef (t_stat stat, const char* fmt, ...)
{
char stackbuf[STACKBUFSIZE];
int32 bufsize = sizeof(stackbuf);
char *buf = stackbuf;
int32 len;
va_list arglist;
t_bool inhibit_message = (!sim_show_message || (stat & SCPE_NOMESSAGE));
while (1) { /* format passed string, args */
va_start (arglist, fmt);
#if defined(NO_vsnprintf)
#if defined(HAS_vsprintf_void)
/* Note, this could blow beyond the buffer, and we couldn't tell */
/* That is a limitation of the C runtime library available on this platform */
vsprintf (buf, fmt, arglist);
for (len = 0; len < bufsize-1; len++)
if (buf[len] == 0) break;
#else
len = vsprintf (buf, fmt, arglist);
#endif /* HAS_vsprintf_void */
#else /* NO_vsnprintf */
#if defined(HAS_vsnprintf_void)
vsnprintf (buf, bufsize-1, fmt, arglist);
for (len = 0; len < bufsize-1; len++)
if (buf[len] == 0) break;
#else
len = vsnprintf (buf, bufsize-1, fmt, arglist);
#endif /* HAS_vsnprintf_void */
#endif /* NO_vsnprintf */
va_end (arglist);
/* If the formatted result didn't fit into the buffer, then grow the buffer and try again */
if ((len < 0) || (len >= bufsize-1)) {
if (buf != stackbuf)
free (buf);
bufsize = bufsize * 2;
buf = (char *) malloc (bufsize);
if (buf == NULL) /* out of memory */
return SCPE_MEM;
buf[bufsize-1] = '\0';
continue;
}
break;
}
if (sim_do_ocptr[sim_do_depth]) {
if (!sim_do_echo && !sim_quiet && !inhibit_message)
sim_printf("%s> %s\n", do_position(), sim_do_ocptr[sim_do_depth]);
else {
if (sim_deb) /* Always put context in debug output */
fprintf (sim_deb, "%s> %s\n", do_position(), sim_do_ocptr[sim_do_depth]);
}
}
if (sim_is_running && !inhibit_message) {
char *c, *remnant = buf;
while ((c = strchr(remnant, '\n'))) {
printf("%.*s\r\n", (int)(c-remnant), remnant);
remnant = c + 1;
}
printf("%s", remnant);
}
else {
if (!inhibit_message)
printf("%s", buf);
}
if (sim_log && (sim_log != stdout) && !inhibit_message)
fprintf (sim_log, "%s", buf);
if (sim_deb && (((sim_deb != stdout) && (sim_deb != sim_log)) || inhibit_message))/* Always display messages in debug output */
fprintf (sim_deb, "%s", buf);
if (buf != stackbuf)
free (buf);
return stat | SCPE_NOMESSAGE;
}
/* Inline debugging - will print debug message if debug file is
set and the bitmask matches the current device debug options.
Extra returns are added for un*x systems, since the output
device is set into 'raw' mode when the cpu is booted,
and the extra returns don't hurt any other systems.
Callers should be calling sim_debug() which is a macro
defined in scp.h which evaluates the action condition before
incurring call overhead. */
void _sim_debug (uint32 dbits, DEVICE* dptr, const char* fmt, ...)
{
if (sim_deb && dptr && (dptr->dctrl & dbits)) {
char stackbuf[STACKBUFSIZE];
int32 bufsize = sizeof(stackbuf);
char *buf = stackbuf;
va_list arglist;
int32 i, j, len;
const char* debug_prefix = sim_debug_prefix(dbits, dptr); /* prefix to print if required */
buf[bufsize-1] = '\0';
while (1) { /* format passed string, args */
va_start (arglist, fmt);
#if defined(NO_vsnprintf)
#if defined(HAS_vsprintf_void)
/* Note, this could blow beyond the buffer, and we couldn't tell */
/* That is a limitation of the C runtime library available on this platform */
vsprintf (buf, fmt, arglist);
for (len = 0; len < bufsize-1; len++)
if (buf[len] == 0) break;
#else
len = vsprintf (buf, fmt, arglist);
#endif /* HAS_vsprintf_void */
#else /* NO_vsnprintf */
#if defined(HAS_vsnprintf_void)
vsnprintf (buf, bufsize-1, fmt, arglist);
for (len = 0; len < bufsize-1; len++)
if (buf[len] == 0) break;
#else
len = vsnprintf (buf, bufsize-1, fmt, arglist);
#endif /* HAS_vsnprintf_void */
#endif /* NO_vsnprintf */
va_end (arglist);
/* If the formatted result didn't fit into the buffer, then grow the buffer and try again */
if ((len < 0) || (len >= bufsize-1)) {
if (buf != stackbuf)
free (buf);
bufsize = bufsize * 2;
buf = (char *) malloc (bufsize);
if (buf == NULL) /* out of memory */
return;
buf[bufsize-1] = '\0';
continue;
}
break;
}
/* Output the formatted data expanding newlines where they exist */
for (i = j = 0; i < len; ++i) {
if ('\n' == buf[i]) {
if (i >= j) {
if ((i != j) || (i == 0)) {
if (debug_unterm)
fprintf (sim_deb, "%.*s\r\n", i-j, &buf[j]);
else /* print prefix when required */
fprintf (sim_deb, "%s%.*s\r\n", debug_prefix, i-j, &buf[j]);
}
debug_unterm = 0;
}
j = i + 1;
}
}
if (i > j) {
if (debug_unterm)
fprintf (sim_deb, "%.*s", i-j, &buf[j]);
else /* print prefix when required */
fprintf (sim_deb, "%s%.*s", debug_prefix, i-j, &buf[j]);
}
/* Set unterminated flag for next time */
debug_unterm = len ? (((buf[len-1]=='\n')) ? 0 : 1) : debug_unterm;
if (buf != stackbuf)
free (buf);
}
return;
}
void sim_data_trace(DEVICE *dptr, UNIT *uptr, const uint8 *data, const char *position, size_t len, const char *txt, uint32 reason)
{
if (sim_deb && (dptr->dctrl & reason)) {
sim_debug (reason, dptr, "%s %s %slen: %08X\n", sim_uname(uptr), txt, position, len);
if (data && len) {
size_t i, same, group, sidx, oidx;
char outbuf[80], strbuf[18];
static char hex[] = "0123456789ABCDEF";
for (i=same=0; i<len; i += 16) {
if ((i > 0) && (0 == memcmp (&data[i], &data[i-16], 16))) {
++same;
continue;
}
if (same > 0) {
sim_debug (reason, dptr, "%04X thru %04X same as above\n", i-(16*same), i-1);
same = 0;
}
group = (((len - i) > 16) ? 16 : (len - i));
for (sidx=oidx=0; sidx<group; ++sidx) {
outbuf[oidx++] = ' ';
outbuf[oidx++] = hex[(data[i+sidx]>>4)&0xf];
outbuf[oidx++] = hex[data[i+sidx]&0xf];
if (sim_isprint (data[i+sidx]))
strbuf[sidx] = data[i+sidx];
else
strbuf[sidx] = '.';
}
outbuf[oidx] = '\0';
strbuf[sidx] = '\0';
sim_debug (reason, dptr, "%04X%-48s %s\n", i, outbuf, strbuf);
}
if (same > 0) {
sim_debug (reason, dptr, "%04X thru %04X same as above\n", i-(16*same), len-1);
}
}
}
}
/* Hierarchical help presentation
*
* Device help can be presented hierarchically by calling
*
* t_stat scp_help (FILE *st, struct sim_device *dptr,
* struct sim_unit *uptr, int flag, const char *help, char *cptr)
*
* or one of its three cousins from the device HELP routine.
*
* *help is the pointer to the structured help text to be displayed.
*
* The format and usage, and some helper macros can be found in scp_help.h
* If you don't use the macros, it is not necessary to #include "scp_help.h".
*
* Actually, if you don't specify a DEVICE pointer and don't include
* other device references, it can be used for non-device help.
*/
#define blankch(x) ((x) == ' ' || (x) == '\t')
typedef struct topic {
uint32 level;
char *title;
char *label;
struct topic *parent;
struct topic **children;
uint32 kids;
char *text;
size_t len;
uint32 flags;
uint32 kidwid;
#define HLP_MAGIC_TOPIC 1
} TOPIC;
static volatile struct {
const char *error;
const char *prox;
size_t block;
size_t line;
} help_where = { "", NULL, 0, 0 };
jmp_buf (help_env);
#define FAIL(why,text,here) { help_where.error = #text; help_where.prox = here; longjmp (help_env, (why)); }
/* Add to topic text.
* Expands text buffer as necessary.
*/
static void appendText (TOPIC *topic, const char *text, size_t len)
{
char *newt;
if (!len)
return;
newt = (char *)realloc (topic->text, topic->len + len +1);
if (!newt) {
FAIL (SCPE_MEM, No memory, NULL);
}
topic->text = newt;
memcpy (newt + topic->len, text, len);
topic->len +=len;
newt[topic->len] = '\0';
return;
}
/* Release memory held by a topic and its children.
*/
static void cleanHelp (TOPIC *topic)
{
TOPIC *child;
size_t i;
free (topic->title);
free (topic->text);
free (topic->label);
for (i = 0; i < topic->kids; i++) {
child = topic->children[i];
cleanHelp (child);
free (child);
}
free (topic->children);
return;
}
/* Build a help tree from a string.
* Handles substitutions, formatting.
*/
static TOPIC *buildHelp (TOPIC *topic, struct sim_device *dptr,
struct sim_unit *uptr, const char *htext, va_list ap)
{
char *end;
size_t n, ilvl;
#define VSMAX 100
char *vstrings[VSMAX];
size_t vsnum = 0;
char *astrings[VSMAX+1];
size_t asnum = 0;
char *const *hblock;
const char *ep;
t_bool excluded = FALSE;
/* variable arguments consumed table.
* The scheme used allows arguments to be accessed in random
* order, but for portability, all arguments must be char *.
* If you try to violate this, there ARE machines that WILL break.
*/
memset (vstrings, 0, VSMAX * sizeof (char *));
memset (astrings, 0, VSMAX * sizeof (char *));
astrings[asnum++] = (char *) htext;
for (hblock = astrings; (htext = *hblock) != NULL; hblock++) {
help_where.block = hblock - astrings;
help_where.line = 0;
while (*htext) {
const char *start;
help_where.line++;
if (sim_isspace (*htext) || *htext == '+') {/* Topic text, indented topic text */
if (excluded) { /* Excluded topic text */
while (*htext && *htext != '\n')
htext++;
if (*htext)
++htext;
continue;
}
ilvl = 1;
appendText (topic, " ", 4); /* Basic indentation */
if (*htext == '+') { /* More for each + */
while (*htext == '+') {
ilvl++;
appendText (topic, " ", 4);
htext++;
}
}
while (*htext && *htext != '\n' && sim_isspace (*htext))
htext++;
if (!*htext) /* Empty after removing leading spaces */
break;
start = htext;
while (*htext) { /* Process line for substitutions */
if (*htext == '%') {
appendText (topic, start, htext - start); /* Flush up to escape */
switch (*++htext) { /* Evaluate escape */
case 'U':
if (dptr) {
char buf[129];
n = uptr? uptr - dptr->units: 0;
sprintf (buf, "%s%u", dptr->name, (int)n);
appendText (topic, buf, strlen (buf));
}
break;
case 'D':
if (dptr) {
appendText (topic, dptr->name, strlen (dptr->name));
break;
}
case 'S':
appendText (topic, sim_name, strlen (sim_name));
break;
case '%':
appendText (topic, "%", 1);
break;
case '+':
appendText (topic, "+", 1);
break;
default: /* Check for vararg # */
if (sim_isdigit (*htext)) {
n = 0;
while (sim_isdigit (*htext))
n += (n * 10) + (*htext++ - '0');
if (( *htext != 'H' && *htext != 's') ||
n == 0 || n >= VSMAX)
FAIL (SCPE_ARG, Invalid escape, htext);
while (n > vsnum) /* Get arg pointer if not cached */
vstrings[vsnum++] = va_arg (ap, char *);
start = vstrings[n-1]; /* Insert selected string */
if (*htext == 'H') { /* Append as more input */
if (asnum >= VSMAX) {
FAIL (SCPE_ARG, Too many blocks, htext);
}
astrings[asnum++] = (char *)start;
break;
}
ep = start;
while (*ep) {
if (*ep == '\n') {
ep++; /* Segment to \n */
appendText (topic, start, ep - start);
if (*ep) { /* More past \n, indent */
size_t i;
for (i = 0; i < ilvl; i++)
appendText (topic, " ", 4);
}
start = ep;
}
else
ep++;
}
appendText (topic, start, ep-start);
break;
}
FAIL (SCPE_ARG, Invalid escape, htext);
} /* switch (escape) */
start = ++htext;
continue; /* Current line */
} /* if (escape) */
if (*htext == '\n') { /* End of line, append last segment */
htext++;
appendText (topic, start, htext - start);
break; /* To next line */
}
htext++; /* Regular character */
}
continue;
} /* topic text line */
if (sim_isdigit (*htext)) { /* Topic heading */
TOPIC **children;
TOPIC *newt;
char nbuf[100];
n = 0;
start = htext;
while (sim_isdigit (*htext))
n += (n * 10) + (*htext++ - '0');
if ((htext == start) || !n) {
FAIL (SCPE_ARG, Invalid topic heading, htext);
}
if (n <= topic->level) { /* Find level for new topic */
while (n <= topic->level)
topic = topic->parent;
}
else {
if (n > topic->level +1) { /* Skipping down more than 1 */
FAIL (SCPE_ARG, Level not contiguous, htext); /* E.g. 1 3, not reasonable */
}
}
while (*htext && (*htext != '\n') && sim_isspace (*htext))
htext++;
if (!*htext || (*htext == '\n')) { /* Name missing */
FAIL (SCPE_ARG, Missing topic name, htext);
}
start = htext;
while (*htext && (*htext != '\n'))
htext++;
if (start == htext) { /* Name NULL */
FAIL (SCPE_ARG, Null topic name, htext);
}
excluded = FALSE;
if (*start == '?') { /* Conditional topic? */
size_t n = 0;
start++;
while (sim_isdigit (*start)) /* Get param # */
n += (n * 10) + (*start++ - '0');
if (!*start || *start == '\n'|| n == 0 || n >= VSMAX)
FAIL (SCPE_ARG, Invalid parameter number, start);
while (n > vsnum) /* Get arg pointer if not cached */
vstrings[vsnum++] = va_arg (ap, char *);
end = vstrings[n-1]; /* Check for True */
if (!end || !(toupper (*end) == 'T' || *end == '1')) {
excluded = TRUE; /* False, skip topic this time */
if (*htext)
htext++;
continue;
}
}
newt = (TOPIC *) calloc (sizeof (TOPIC), 1);
if (!newt) {
FAIL (SCPE_MEM, No memory, NULL);
}
newt->title = (char *) malloc ((htext - start)+1);
if (!newt->title) {
free (newt);
FAIL (SCPE_MEM, No memory, NULL);
}
memcpy (newt->title, start, htext - start);
newt->title[htext - start] = '\0';
if (*htext)
htext++;
if (newt->title[0] == '$')
newt->flags |= HLP_MAGIC_TOPIC;
children = (TOPIC **) realloc (topic->children,
(topic->kids +1) * sizeof (TOPIC *));
if (!children) {
free (newt->title);
free (newt);
FAIL (SCPE_MEM, No memory, NULL);
}
topic->children = children;
topic->children[topic->kids++] = newt;
newt->level = n;
newt->parent = topic;
n = strlen (newt->title);
if (n > topic->kidwid)
topic->kidwid = n;
sprintf (nbuf, ".%u", topic->kids);
n = strlen (topic->label) + strlen (nbuf) + 1;
newt->label = (char *) malloc (n);
if (!newt->label) {
free (newt->title);
topic->children[topic->kids -1] = NULL;
free (newt);
FAIL (SCPE_MEM, No memory, NULL);
}
sprintf (newt->label, "%s%s", topic->label, nbuf);
topic = newt;
continue;
} /* digits introducing a topic */
if (*htext == ';') { /* Comment */
while (*htext && *htext != '\n')
htext++;
continue;
}
FAIL (SCPE_ARG, Unknown line type, htext); /* Unknown line */
} /* htext not at end */
memset (vstrings, 0, VSMAX * sizeof (char *));
vsnum = 0;
} /* all strings */
return topic;
}
/* Create prompt string - top thru current topic
* Add prompt at end.
*/
static char *helpPrompt ( TOPIC *topic, const char *pstring, t_bool oneword )
{
char *prefix;
char *newp, *newt;
if (topic->level == 0) {
prefix = (char *) calloc (2,1);
if (!prefix) {
FAIL (SCPE_MEM, No memory, NULL);
}
prefix[0] = '\n';
}
else
prefix = helpPrompt (topic->parent, "", oneword);
newp = (char *) malloc (strlen (prefix) + 1 + strlen (topic->title) + 1 +
strlen (pstring) +1);
if (!newp) {
free (prefix);
FAIL (SCPE_MEM, No memory, NULL);
}
strcpy (newp, prefix);
if (topic->children) {
if (topic->level != 0)
strcat (newp, " ");
newt = (topic->flags & HLP_MAGIC_TOPIC)?
topic->title+1: topic->title;
if (oneword) {
char *np = newp + strlen (newp);
while (*newt) {
*np++ = blankch (*newt)? '_' : *newt;
newt++;
}
*np = '\0';
}
else
strcat (newp, newt);
if (*pstring && *pstring != '?')
strcat (newp, " ");
}
strcat (newp, pstring);
free (prefix);
return newp;
}
static void displayMagicTopic (FILE *st, struct sim_device *dptr, TOPIC *topic)
{
char tbuf[CBUFSIZE];
size_t i, skiplines;
#ifdef _WIN32
FILE *tmp;
char *tmpnam;
do {
int fd;
tmpnam = _tempnam (NULL, "simh");
fd = _open (tmpnam, _O_CREAT | _O_RDWR | _O_EXCL, _S_IREAD | _S_IWRITE);
if (fd != -1) {
tmp = _fdopen (fd, "w+");
break;
}
} while (1);
#else
FILE *tmp = tmpfile();
#endif
if (!tmp) {
fprintf (st, "Unable to create temporary file: %s\n", strerror (errno));
return;
}
if (topic->title)
fprintf (st, "%s\n", topic->title+1);
skiplines = 0;
if (!strcmp (topic->title+1, "Registers")) {
fprint_reg_help (tmp, dptr) ;
skiplines = 1;
}
else
if (!strcmp (topic->title+1, "Set commands")) {
fprint_set_help (tmp, dptr);
skiplines = 3;
}
else
if (!strcmp (topic->title+1, "Show commands")) {
fprint_show_help (tmp, dptr);
skiplines = 3;
}
rewind (tmp);
/* Discard leading blank lines/redundant titles */
for (i =0; i < skiplines; i++)
fgets (tbuf, sizeof (tbuf), tmp);
while (fgets (tbuf, sizeof (tbuf), tmp)) {
if (tbuf[0] != '\n')
fputs (" ", st);
fputs (tbuf, st);
}
fclose (tmp);
#ifdef _WIN32
remove (tmpnam);
free (tmpnam);
#endif
return;
}
/* Flatten and display help for those who say they prefer it.
*/
static t_stat displayFlatHelp (FILE *st, struct sim_device *dptr,
struct sim_unit *uptr, int32 flag,
TOPIC *topic, va_list ap )
{
size_t i;
if (topic->flags & HLP_MAGIC_TOPIC) {
fprintf (st, "\n%s ", topic->label);
displayMagicTopic (st, dptr, topic);
}
else
fprintf (st, "\n%s %s\n", topic->label, topic->title);
/* Topic text (for magic topics, follows for explanations)
* It's possible/reasonable for a magic topic to have no text.
*/
if (topic->text)
fputs (topic->text, st);
for (i = 0; i < topic->kids; i++)
displayFlatHelp (st, dptr, uptr, flag, topic->children[i], ap);
return SCPE_OK;
}
#define HLP_MATCH_AMBIGUOUS (~0u)
#define HLP_MATCH_WILDCARD (~1U)
#define HLP_MATCH_NONE 0
static int matchHelpTopicName (TOPIC *topic, const char *token)
{
size_t i, match;
char cbuf[CBUFSIZE], *cptr;
if (!strcmp (token, "*"))
return HLP_MATCH_WILDCARD;
match = 0;
for (i = 0; i < topic->kids; i++) {
strcpy (cbuf,topic->children[i]->title +
((topic->children[i]->flags & HLP_MAGIC_TOPIC)? 1 : 0));
cptr = cbuf;
while (*cptr) {
if (blankch (*cptr)) {
*cptr++ = '_';
}
else {
*cptr = (char)toupper (*cptr);
cptr++;
}
}
if (!strncmp (cbuf, token, strlen (token))) {
if (match)
return HLP_MATCH_AMBIGUOUS;
match = i+1;
}
}
return match;
}
/* Main help routine
* Takes a va_list
*/
t_stat scp_vhelp (FILE *st, struct sim_device *dptr,
struct sim_unit *uptr, int32 flag,
const char *help, const char *cptr, va_list ap)
{
TOPIC top;
TOPIC *topic = &top;
int failed;
size_t match;
size_t i;
char *p;
t_bool flat_help = FALSE;
char cbuf [CBUFSIZE], gbuf[CBUFSIZE];
static const char attach_help[] = { " ATTACH" };
static const char brief_help[] = { "%s help. Type <CR> to exit, HELP for navigation help" };
static const char onecmd_help[] = { "%s help." };
static const char help_help[] = {
/****|***********************80 column width guide********************************/
" This help command provides hierarchical help. To see more information,\n"
" type an offered subtopic name. To move back a level, just type <CR>.\n"
" To review the current topic/subtopic, type \"?\".\n"
" To view all subtopics, type \"*\".\n"
" To exit help at any time, type EXIT.\n"
};
memset (&top, 0, sizeof(top));
top.parent = &top;
if ((failed = setjmp (help_env)) != 0) {
fprintf (stderr, "\nHelp was unable to process the help for this device.\n"
"Error in block %u line %u: %s\n"
"%s%*.*s%s"
" Please contact the device maintainer.\n",
(int)help_where.block, (int)help_where.line, help_where.error,
help_where.prox ? "Near '" : "",
help_where.prox ? 15 : 0, help_where.prox ? 15 : 0,
help_where.prox ? help_where.prox : "",
help_where.prox ? "'" : "");
cleanHelp (&top);
return failed;
}
/* Compile string into navigation tree */
/* Root */
if (dptr) {
p = dptr->name;
flat_help = (dptr->flags & DEV_FLATHELP) != 0;
}
else
p = sim_name;
top.title = (char *) malloc (strlen (p) + ((flag & SCP_HELP_ATTACH)? sizeof (attach_help)-1: 0) +1);
for (i = 0; p[i]; i++ )
top.title[i] = (char)toupper (p[i]);
top.title[i] = '\0';
if (flag & SCP_HELP_ATTACH)
strcpy (top.title+i, attach_help);
top.label = (char *) malloc (sizeof ("1"));
strcpy (top.label, "1");
flat_help = flat_help || !sim_ttisatty() || (flag & SCP_HELP_FLAT);
if (flat_help) {
flag |= SCP_HELP_FLAT;
if (sim_ttisatty())
fprintf (st, "%s help.\nThis help is also available in hierarchical form.\n", top.title);
else
fprintf (st, "%s help.\n", top.title);
}
else
fprintf (st, ((flag & SCP_HELP_ONECMD)? onecmd_help: brief_help), top.title);
/* Add text and subtopics */
(void) buildHelp (&top, dptr, uptr, help, ap);
/* Go to initial topic if provided */
while (cptr && *cptr) {
cptr = get_glyph (cptr, gbuf, 0);
if (!gbuf[0])
break;
if (!strcmp (gbuf, "HELP")) { /* HELP (about help) */
fprintf (st, "\n");
fputs (help_help, st);
break;
}
match = matchHelpTopicName (topic, gbuf);
if (match == HLP_MATCH_WILDCARD) {
displayFlatHelp (st, dptr, uptr, flag, topic, ap);
cleanHelp (&top);
return SCPE_OK;
}
if (match == HLP_MATCH_AMBIGUOUS) {
fprintf (st, "\n%s is ambiguous in %s\n", gbuf, topic->title);
break;
}
if (match == HLP_MATCH_NONE) {
fprintf (st, "\n%s is not available in %s\n", gbuf, topic->title);
break;
}
topic = topic->children[match-1];
}
cptr = NULL;
if (flat_help) {
displayFlatHelp (st, dptr, uptr, flag, topic, ap);
cleanHelp (&top);
return SCPE_OK;
}
/* Interactive loop displaying help */
while (TRUE) {
char *pstring;
const char *prompt[2] = {"? ", "Subtopic? "};
/* Some magic topic names for help from data structures */
if (topic->flags & HLP_MAGIC_TOPIC) {
fputc ('\n', st);
displayMagicTopic (st, dptr, topic);
}
else
fprintf (st, "\n%s\n", topic->title);
/* Topic text (for magic topics, follows for explanations)
* It's possible/reasonable for a magic topic to have no text.
*/
if (topic->text)
fputs (topic->text, st);
if (topic->kids) {
size_t w = 0;
char *p;
char tbuf[CBUFSIZE];
fprintf (st, "\n Additional information available:\n\n");
for (i = 0; i < topic->kids; i++) {
strcpy (tbuf, topic->children[i]->title +
((topic->children[i]->flags & HLP_MAGIC_TOPIC)? 1 : 0));
for (p = tbuf; *p; p++) {
if (blankch (*p))
*p = '_';
}
w += 4 + topic->kidwid;
if (w > 80) {
w = 4 + topic->kidwid;
fputc ('\n', st);
}
fprintf (st, " %-*s", topic->kidwid, tbuf);
}
fprintf (st, "\n\n");
if (flag & SCP_HELP_ONECMD) {
pstring = helpPrompt (topic, "", TRUE);
fprintf (st, "To view additional topics, type HELP %s topicname\n", pstring+1);
free (pstring);
break;
}
}
if (!sim_ttisatty() || (flag & SCP_HELP_ONECMD))
break;
reprompt:
if (!cptr || !*cptr) {
pstring = helpPrompt (topic, prompt[topic->kids != 0], FALSE);
cptr = read_line_p (pstring, cbuf, sizeof (cbuf), stdin);
free (pstring);
}
if (!cptr) /* EOF, exit help */
break;
cptr = get_glyph (cptr, gbuf, 0);
if (!strcmp (gbuf, "*")) { /* Wildcard */
displayFlatHelp (st, dptr, uptr, flag, topic, ap);
gbuf[0] = '\0'; /* Displayed all subtopics, go up */
}
if (!gbuf[0]) { /* Blank, up a level */
if (topic->level == 0)
break;
topic = topic->parent;
continue;
}
if (!strcmp (gbuf, "?")) /* ?, repaint current topic */
continue;
if (!strcmp (gbuf, "HELP")) { /* HELP (about help) */
fputs (help_help, st);
goto reprompt;
}
if (!strcmp (gbuf, "EXIT") || !strcmp (gbuf, "QUIT")) /* EXIT (help) */
break;
/* String - look for that topic */
if (!topic->kids) {
fprintf (st, "No additional help at this level.\n");
cptr = NULL;
goto reprompt;
}
match = matchHelpTopicName (topic, gbuf);
if (match == HLP_MATCH_AMBIGUOUS) {
fprintf (st, "%s is ambiguous, please type more of the topic name\n", gbuf);
cptr = NULL;
goto reprompt;
}
if (match == HLP_MATCH_NONE) {
fprintf (st, "Help for %s is not available\n", gbuf);
cptr = NULL;
goto reprompt;
}
/* Found, display subtopic */
topic = topic->children[match-1];
}
/* Free structures and return */
cleanHelp (&top);
return SCPE_OK;
}
/* variable argument list shell - most commonly used
*/
t_stat scp_help (FILE *st, struct sim_device *dptr,
struct sim_unit *uptr, int32 flag,
const char *help, const char *cptr, ...)
{
t_stat r;
va_list ap;
va_start (ap, cptr);
r = scp_vhelp (st, dptr, uptr, flag, help, cptr, ap);
va_end (ap);
return r;
}
#if 01
/* Read help from a file
*
* Not recommended due to OS-dependent issues finding the file, + maintenance.
* Don't hardcode any path - just name.hlp - so there's a chance the file can
* be found.
*/
t_stat scp_vhelpFromFile (FILE *st, struct sim_device *dptr,
struct sim_unit *uptr, int32 flag,
const char *helpfile,
const char *cptr, va_list ap)
{
FILE *fp;
char *help, *p;
t_offset size, n;
int c;
t_stat r;
fp = sim_fopen (helpfile, "r");
if (fp == NULL) {
if (sim_argv && *sim_argv[0]) {
char fbuf[(4*PATH_MAX)+1]; /* PATH_MAX is ridiculously small on some platforms */
char *d = NULL;
/* Try to find a path from argv[0]. This won't always
* work (one reason files are probably not a good idea),
* but we might as well try. Some OSs won't include a
* path. Having failed in the CWD, try to find the location
* of the executable. Failing that, try the 'help' subdirectory
* of the executable. Failing that, we're out of luck.
*/
strncpy (fbuf, sim_argv[0], sizeof (fbuf));
if ((p = match_ext (fbuf, "EXE")))
*p = '\0';
if ((p = strrchr (fbuf, '\\'))) {
p[1] = '\0';
d = "%s\\";
}
else {
if ((p = strrchr (fbuf, '/'))) {
p[1] = '\0';
d = "%s/";
#ifdef VMS
}
else {
if ((p = strrchr (fbuf, ']'))) {
p[1] = '\0';
d = "[%s]";
}
#endif
}
}
if (p && (strlen (fbuf) + strlen (helpfile) +1) <= sizeof (fbuf)) {
strcat (fbuf, helpfile);
fp = sim_fopen (fbuf, "r");
}
if (!fp && p && (strlen (fbuf) + strlen (d) + sizeof ("help") +
strlen (helpfile) +1) <= sizeof (fbuf)) {
sprintf (p+1, d, "help");
strcat (p+1, helpfile);
fp = sim_fopen (fbuf, "r");
}
}
}
if (fp == NULL) {
fprintf (stderr, "Unable to open %s\n", helpfile);
return SCPE_UNATT;
}
size = sim_fsize_ex (fp); /* Estimate; line endings will vary */
help = (char *) malloc ((size_t) size +1);
if (!help) {
fclose (fp);
return SCPE_MEM;
}
p = help;
n = 0;
while ((c = fgetc (fp)) != EOF) {
if (++n > size) {
#define XPANDQ 512
p = (char *) realloc (help, ((size_t)size) + XPANDQ +1);
if (!p) {
free (help);
fclose (fp);
return SCPE_MEM;
}
help = p;
size += XPANDQ;
p += n;
}
*p++ = (char)c;
}
*p++ = '\0';
fclose (fp);
r = scp_vhelp (st, dptr, uptr, flag, help, cptr, ap);
free (help);
return r;
}
t_stat scp_helpFromFile (FILE *st, struct sim_device *dptr,
struct sim_unit *uptr, int32 flag,
const char *helpfile, const char *cptr, ...)
{
t_stat r;
va_list ap;
va_start (ap, cptr);
r = scp_vhelpFromFile (st, dptr, uptr, flag, helpfile, cptr, ap);
va_end (ap);
return r;
}
#endif