/* sim_extension.c: SCP extension routines Copyright (c) 2019-2020, J. David Bryan 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 THE AUTHOR 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 the author shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from the author. 14-Feb-20 JDB First release version 18-Mar-19 JDB Created This module implements extensions to the base Simulation Control Program (SCP) front end for SIMH version 3.x. The current extensions are: - host serial port support for the console and terminal multiplexers - automated prompt/response processing, initially for the system console, but extendable to other keyboard/display units - concurrent console mode to enter SCP commands without stopping simulation - work-alikes for a subset of the SCP 4.x commands - execution of a global initialization file at simulator startup This module, and its associated declarations file (sim_extension.h) act as shims between the front end and a simulator-specific back end, such as the HP 2100 or HP 3000 simulator. Each simulator back end must have this inclusion: #include "sim_extension.h" ...placed in the "_defs.h" file that is included by all back-end modules, and all back-end modules must be compiled with the inclusion. Also, this module (sim_extension.c) must be compiled and linked with the other SCP and back-end modules. Extending SCP is possible by the use of hooks and shims. SCP 3.x provides a number of replaceable function and variable pointers (the "hooks") that may be altered to implement custom behavior. Hooks are necessary where the internal behavior of the SCP must change -- for example, when adding new command executors to the original table of commands. A one-time initializer within this module is called by SCP at simulator startup. This initializer points the desired hooks at functions within this module to implement the extended actions. To extend the capability at the interface between the SCP front end and the simulator-specific back end, shims are used to intercept the calls between them. One call from the front end to the back end -- "sim_instr" -- is intercepted. All of the other shims are for calls from the back end to front-end services or global variables set by the back end. The general mechanism is to use a macro to rename a given function identifier within the context of the back end. The new name refers to the extension shim, which, internally, may call the original function. For example, "sim_extension.h" includes this macro: #define sim_putchar ex_sim_putchar ...which is included during the back-end compilation. Therefore, a back-end module that called "sim_putchar" would actually be compiled to call "ex_sim_putchar", a function within this module, instead. Within the shim, the macro substitution is not done, so a call to "sim_putchar" calls the front-end function. The following shims are provided: Shimmed Routine Source Module Shimming Routine ================ ============= ================= tmxr_poll_conn sim_tmxr.c ex_tmxr_poll_conn sim_putchar sim_console.c ex_sim_putchar sim_putchar_s sim_console.c ex_sim_putchar_s sim_poll_kbd sim_console.c ex_sim_poll_kbd sim_brk_test scp.c ex_sim_brk_test sim_instr hp----_cpu.c vm_sim_instr sim_vm_init hp----_sys.c vm_sim_vm_init sim_vm_cmd hp----_sys.c vm_sim_vm_cmd The following extensions are provided: Extension Routine Module Extended Extended Action ================== =============== =================================== tmxr_attach_unit sim_tmxr.c Attach a network or serial port tmxr_detach_unit sim_tmxr.c Detach a network or serial port tmxr_detach_line sim_tmxr.c Detach a serial port tmxr_control_line sim_tmxr.c Control a line tmxr_line_status sim_tmxr.c Get a line's status tmxr_line_free sim_tmxr.c Check if a line is disconnected tmxr_mux_free sim_tmxr.c Check if all lines are disconnected The following extension hooks are provided: Hook Name Hook Description ====================== =========================== vm_console_input_unit Console input unit pointer vm_console_output_unit Console output unit pointer The following SCP hooks are used: Hook Name Source Module ================ ============= sim_vm_init scp.c sim_vm_cmd scp.c sim_vm_unit_name scp.c sub_args scp.c sim_get_radix scp.c tmxr_read sim_tmxr.c tmxr_write sim_tmxr.c tmxr_show sim_tmxr.c tmxr_close sim_tmxr.c tmxr_is_extended sim_tmxr.c The extension hooks should be set by the back end to point at the console units. They are used to identify the console units for the REPLY and BREAK commands. The VM-specific SCP hooks may be set as usual by the back end. The extension module intercepts these but ensures that they are called as part of extension processing, so their effects are maintained. Implementation notes: 1. If the base set of options to the SHOW CONSOLE command is changed in "scp.c", the "ex_show_console" routine below must be changed as well. See the routine's implementation note for details. 2. The existing SCP command table model presents some difficulties when extending the command set. When adding new commands that begin with the same letter(s) as existing commands, the existing commands must be duplicated in the extension table before the new ones. This is required to preserve the standard command abbreviation behavior when the extension table is searched first. For example, when adding a new CLEAR command, the existing CONTINUE command must be duplicated and placed ahead of the CLEAR entry, so that entering "C" invokes CONTINUE and not CLEAR. But duplicating the CONTINUE entry introduces the potential for error if the standard CONTINUE entry is changed. This means that duplicated entries must be copied dynamically, which then means that the new table cannot be a constant. Also, in some standard and extension cases, additional actions may need to be taken for specific commands. The method of identifying the command by testing the "action" field of the resulting CTAB entry does not work if the command has been replaced. For instance, testing "cmdp->action == &do_cmd" doesn't work if the DO command was replaced. The only reliable way is to test the "name" field for the expected command name string, i.e., testing "strcmp (cmdp->name, "DO") == 0", which is both slow and awkward. Further, new commands may be valid only in certain contexts, e.g., in a command file or only during some specific mode of execution. There is no easy way of passing context to the command executor, except by global variables, as all executors receive the same parameters (an integer argument and a character pointer). In short, we have to jump through some hoops in implementing the command extensions. These are described in the comments associated with their appearances. */ #define COMPILING_EXTENSIONS /* defined to prevent shimming in sim_extension.h */ #include #include #include #include #include #include "sim_defs.h" #include "sim_serial.h" #include "sim_extension.h" /* The following pragmas quell clang and Microsoft Visual C++ warnings that are on by default but should not be, in my opinion. They warn about the use of perfectly valid code and require the addition of redundant parentheses and braces to silence them. Rather than clutter up the code with scores of extra symbols that, in my view, make the code harder to read and maintain, I elect to suppress these warnings. VC++ 2008 warning descriptions: - 4114: "same type qualifier used more than once" [legal per C99] - 4554: "check operator precedence for possible error; use parentheses to clarify precedence" - 4996: "function was declared deprecated" */ #if defined (__clang__) #pragma clang diagnostic ignored "-Wlogical-not-parentheses" #pragma clang diagnostic ignored "-Wlogical-op-parentheses" #pragma clang diagnostic ignored "-Wbitwise-op-parentheses" #pragma clang diagnostic ignored "-Wshift-op-parentheses" #pragma clang diagnostic ignored "-Wdangling-else" #elif defined (_MSC_VER) #pragma warning (disable: 4114 4554 4996) #endif /* MSVC does not have the C-standard "snprintf" function */ #if defined (_MSC_VER) #define snprintf _snprintf #endif /* Character constants (as integers) */ #define BS 0010 #define CR 0015 #define LF 0012 #define ESC 0033 #define DEL 0177 /* Flags for restricted-use commands */ #define EX_GOTO 0 #define EX_CALL 1 #define EX_RETURN 2 #define EX_ABORT 3 /* External variable declarations (actually should be in sim_console.h) */ extern TMXR sim_con_tmxr; /* External routine declarations (actually should be in scp.h) */ extern t_stat show_break (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr); /* One-time extension initializer */ static void ex_initialize (void); /* Hooks provided by SCP */ void (*sim_vm_init) (void) = ex_initialize; /* use our one-time initializer */ /* Hooks provided by us for the back end virtual machine */ CTAB *vm_sim_vm_cmd = NULL; void (*vm_sim_vm_init) (void); UNIT *vm_console_input_unit; /* console input unit pointer */ UNIT *vm_console_output_unit; /* console output unit pointer */ /* Pointer to the VM handler for unit names */ static char * (*vm_unit_name_handler) (const UNIT *uptr) = NULL; /* Extended terminal multiplexer line descriptor. This structure extends the TMLN structure defined by the multiplexer library to enable serial port support. The TMLN structure contains a void extension pointer, "exptr", which will be initialized to NULL by the line descriptor declarations in the various multiplexer simulators. For lines controlled by extension routines, this pointer is changed to point at an EX_TMLN extension structure, which is defined below. Implementation notes: 1. The name of the serial port is kept in an allocated buffer and referenced by the UNIT's "filename" pointer. The "sername" pointer points at the same buffer; it is needed only to permit the "ex_tmxr_show" routine to print the name when given the TMLN structure. This pointer must NOT be freed; the buffer is deallocated by freeing the "filename" pointer. */ typedef struct { /* extended line descriptor */ SERHANDLE serport; /* serial port handle */ char *sername; /* copy of the serial port name pointer */ t_bool controlled; /* TRUE if the modem lines are controlled */ TMCKT signals; /* modem control signals */ } EX_TMLN; /* Pointers to the standard routines provided by the TMXR library */ static int32 (*tmxr_base_read) (TMLN *lp, int32 length); static int32 (*tmxr_base_write) (TMLN *lp, int32 length); static void (*tmxr_base_show) (TMLN *lp, FILE *stream); static void (*tmxr_base_close) (TMLN *lp); /* Hooked terminal multiplexer replacement routine declarations */ static int32 ex_tmxr_read (TMLN *lp, int32 length); static int32 ex_tmxr_write (TMLN *lp, int32 length); static void ex_tmxr_show (TMLN *lp, FILE *stream); static void ex_tmxr_close (TMLN *lp); static t_bool ex_tmxr_extended (TMLN *lp); /* Local terminal multiplexer extension routine declarations */ static t_stat ex_tmxr_attach_line (TMXR *mp, UNIT *uptr, char *cptr); static EX_TMLN *serial_line (TMLN *lp); /* String breakpoint structure. String breakpoints are implemented by shimming the terminal output routines and matching each output character to a breakpoint string. A string breakpoint structure holds the character string to be matched and some additional data that defines how the breakpoint is handled. The structure is populated by a BREAK command having a quoted string parameter. The structure may exist only until the breakpoint occurs (a "temporary" breakpoint) or until a NOBREAK command is issued to cancel it (a "permanent" breakpoint). Consequently, a breakpoint may be reused and so much contain enough information to allow the breakpoint to be reset after it triggers. The set of active breakpoint structures are maintained in a linked list headed by the "sb_list" global variable. Implementation notes: 1. The structure contains some fields (e.g., "count") that are not used in the initial implementation. They are present for future expansion. 2. String breakpoints are initially implemented for the system console only. Future expansion to allow breakpoints on terminal multiplexer lines is envisioned. 3. The trigger field contains the simulation global time at which a matched breakpoint should trigger. It is set to -1 if the breakpoint has not yet matched (i.e., is still pending). */ typedef struct sbnode { /* string breakpoint descriptor */ UNIT *uptr; /* output unit pointer */ char match [CBUFSIZE]; /* match string */ char *mptr; /* match pointer */ int32 type; /* mask of breakpoint types */ int32 count; /* proceed count */ int32 delay; /* trigger enable delay */ double trigger; /* trigger time */ char action [CBUFSIZE]; /* action string */ struct sbnode *next; /* pointer to the next entry in the list */ } STRING_BREAKPOINT; typedef STRING_BREAKPOINT *SBPTR; /* the string breakpoint node pointer type */ static SBPTR sb_list = NULL; /* the pointer to the head of the breakpoint list */ #define BP_STRING (SWMASK ('_')) /* the default string breakpoint type */ /* String breakpoint local SCP support routines */ static char *breakpoint_name (const UNIT *uptr); static t_stat breakpoint_service (UNIT *uptr); /* String breakpoint SCP data structures */ /* Unit list */ static UNIT breakpoint_unit [] = { /* Event Routine Unit Flags Capacity Delay */ /* ------------------- ---------- -------- ----- */ { UDATA (&breakpoint_service, UNIT_IDLE, 0), 0 } }; /* Reply structure. Replies are implemented by shimming the terminal input routines and supplying characters one-at-a-time from a response string. A reply structure holds the character string to be supplied and some additional data that defines how the reply is handled. The structure is populated by a REPLY command having a quoted string parameter. The structure exists only until the reply is completed. Implementation notes: 1. Replies are initially implemented for the system console only. Future expansion to allow replies on terminal multiplexer lines is envisioned. 2. Only a single reply may exist in the initial implementation. Multiple replies for different devices would be implemented by a linked-list of structures that are allocated dynamically. Initially, though, the "reply" global points at a single static structure instead of the head of a list of structures. 3. A reply is pending if "rptr" points at "reply [0]" and the current simulation time is earlier than the "trigger" time. */ typedef struct rpnode { /* reply descriptor */ UNIT *uptr; /* input unit pointer */ char reply [CBUFSIZE]; /* reply string */ char *rptr; /* reply pointer */ double trigger; /* trigger time */ struct rpnode *next; /* pointer to the next entry in the list */ } REPLY; typedef REPLY *RPPTR; /* the reply node pointer type */ static RPPTR rp_list = NULL; /* the pointer to the head of the reply list */ static REPLY rpx; /* the initial reply descriptor */ /* Local default break and reply delay declarations */ static int32 break_delay = 0; static int32 reply_delay = 0; /* Local string breakpoint extension routine declarations */ static void free_breakpoints (void); static SBPTR find_breakpoint (char *match, SBPTR *prev); static void test_breakpoint (int32 test_char); /* Concurrent console mode status returns */ #define SCPE_EXEC (SCPE_LAST + 1) /* a command is ready to execute */ #define SCPE_ABORT (SCPE_LAST + 2) /* an ABORT command was entered */ /* Concurrent console mode enumerator */ typedef enum { /* keyboard mode enumerator */ Console, /* keystrokes are sent to the console */ Command /* keystrokes are sent to the command buffer */ } KEY_MODE; /* Signal handler function type */ typedef void (*SIG_HANDLER) (int); /* Concurrent console mode local variables */ static t_bool concurrent_mode = TRUE; /* the console mode, initially in Concurrent mode */ static KEY_MODE keyboard_mode = Console; /* the keyboard mode, initially delivered to the console */ static char cmd_buf [CBUFSIZE]; /* the concurrent console command buffer */ static char *cmd_ptr; /* the command buffer pointer */ static char *concurrent_do_ptr = NULL; /* the pointer to a concurrent DO command line */ static t_bool concurrent_run = FALSE; /* TRUE if the VM is executing in concurrent mode */ static t_bool stop_requested = FALSE; /* TRUE if a CTRL+E was entered by the user */ /* Concurrent console mode local routine declarations */ static void wru_handler (int sig); static void put_string (const char *cptr); static t_stat get_command (char *cptr, CTAB **cmdp); /* Local command extension declarations */ static int32 ex_quiet = 0; /* a copy of the global "sim_quiet" setting */ /* Command handler function type */ typedef t_stat CMD_HANDLER (int32 flag, char *cptr); /* Command extension handler function declarations */ static t_stat ex_break_cmd (int32 flag, char *cptr); static t_stat ex_reply_cmd (int32 flag, char *cptr); static t_stat ex_run_cmd (int32 flag, char *cptr); static t_stat ex_do_cmd (int32 flag, char *cptr); static t_stat ex_if_cmd (int32 flag, char *cptr); static t_stat ex_delete_cmd (int32 flag, char *cptr); static t_stat ex_restricted_cmd (int32 flag, char *cptr); static t_stat ex_set_cmd (int32 flag, char *cptr); static t_stat ex_show_cmd (int32 flag, char *cptr); /* Command extension table. This table defines commands and command behaviors that are specific to this extension. Specifically: * RUN and GO accept an UNTIL clause, and all forms provide a quiet DO mode * BREAK and NOBREAK provide temporary and string breakpoints * REPLY and NOREPLY provide console responses * DO provides transfers to labels and quiet stops * IF and GOTO add conditional command file execution * DELETE adds platform-independent file purges * CALL and RETURN add labeled subroutine invocations * ABORT stops execution of the current and any nested DO command files * SET adds ENVIRONMENT to define environment variables and CONSOLE CONCURRENT/NOCONCURRENT to set the console concurrency mode * SHOW adds string breakpoints to BREAK and adds REPLY for replies The table is initialized with only those fields that differ from the standard command table. During one-time initialization, empty or zero fields are filled in from the corresponding standard command table entries. This ensures that the extension table automatically picks up any changes to the standard commands that it modifies. Implementation notes: 1. The RESET and DEPOSIT commands are duplicated from the standard SCP command table so that entering "R" doesn't invoke the RUN command and entering "D" doesn't invoke the DO command. This would otherwise occur because the extension command table is searched before the standard command table. Similarly, the ATTACH, ASSIGN, and ASSERT commands are duplicated so that entering "A" doesn't invoke the ABORT command. 2. The "execute_file" routine needs to do special processing for the RUN, GO, STEP, CONTINUE, and BOOT commands. Unfortunately, there is no easy way to determine these commands from their CTAB entries. We cannot simply check the action routine pointers, because the VM may override them, potentially with five different routines. We can't use special argument values, because they are used by the standard RUN executor to differentiate between the various commands (and they are not unique to these commands). What we need is a VM extension field in the CTAB structure, but there isn't one. So we rely on the hack that nothing in 3.x uses the "help_base" field, which was added for 4.x compatibility. We therefore indicate a RUN, etc. command by setting the "help_base" field non-null (the actual value is, so far, immaterial). */ static CTAB ex_cmds [] = { /* Name Action Routine Argument Help String */ /* ---------- ----------------- ----------- ----------------------------------------------------------------- */ { "RESET", NULL, 0, NULL }, { "DEPOSIT", NULL, 0, NULL }, { "ATTACH", NULL, 0, NULL }, { "ASSIGN", NULL, 0, NULL }, { "ASSERT", NULL, 0, NULL }, { "RUN", ex_run_cmd, 0, NULL, "RUN" }, { "GO", ex_run_cmd, 0, NULL, "RUN" }, { "STEP", ex_run_cmd, 0, NULL, "RUN" }, { "CONTINUE", ex_run_cmd, 0, NULL, "RUN" }, { "BOOT", ex_run_cmd, 0, NULL, "RUN" }, { "BREAK", ex_break_cmd, 0, NULL }, { "NOBREAK", ex_break_cmd, 0, NULL }, { "REPLY", ex_reply_cmd, 0, "reply {} send characters to the console\n" }, { "NOREPLY", ex_reply_cmd, 1, "noreply cancel a pending reply\n" }, { "DO", ex_do_cmd, 1, NULL }, { "IF", ex_if_cmd, 0, "if ;... execute commands if condition TRUE\n" }, { "DELETE", ex_delete_cmd, 0, "del{ete} delete a file\n" }, { "GOTO", ex_restricted_cmd, EX_GOTO, "goto