root/src/dps8/dps8_crdpun.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. pun_init
  2. pun_reset
  3. remove_spaces
  4. log_char_matrix_pattern
  5. search_glyph_patterns
  6. get_lace_char
  7. scan_card_for_glyphs
  8. create_punch_file
  9. write_punch_files
  10. log_card
  11. print_event
  12. print_state
  13. print_transition
  14. clear_card_cache
  15. save_card_in_cache
  16. transition_state
  17. do_state_idle
  18. do_state_starting_job
  19. do_state_scan_card_for_glyphs
  20. do_state_end_of_header
  21. do_state_cache_card
  22. do_state_end_of_deck
  23. do_state_end_of_job
  24. unexpected_event
  25. parse_card
  26. punWriteRecord
  27. pun_iom_cmd
  28. pun_show_nunits
  29. pun_set_nunits
  30. pun_show_device_name
  31. pun_set_device_name
  32. pun_set_path
  33. pun_show_path
  34. pun_set_config
  35. pun_show_config

   1 /*
   2  * vim: filetype=c:tabstop=4:ai:expandtab
   3  * SPDX-License-Identifier: ICU
   4  * SPDX-License-Identifier: Multics
   5  * scspell-id: 7ec3e12d-f62d-11ec-8431-80ee73e9b8e7
   6  *
   7  * ---------------------------------------------------------------------------
   8  *
   9  * Copyright (c) 2007-2013 Michael Mondy
  10  * Copyright (c) 2012-2016 Harry Reed
  11  * Copyright (c) 2013-2016 Charles Anthony
  12  * Copyright (c) 2021 Dean Anderson
  13  * Copyright (c) 2021-2025 The DPS8M Development Team
  14  *
  15  * This software is made available under the terms of the ICU License.
  16  * See the LICENSE.md file at the top-level directory of this distribution.
  17  *
  18  * ---------------------------------------------------------------------------
  19  *
  20  * This source file may contain code comments that adapt, include, and/or
  21  * incorporate Multics program code and/or documentation distributed under
  22  * the Multics License.  In the event of any discrepancy between code
  23  * comments herein and the original Multics materials, the original Multics
  24  * materials should be considered authoritative unless otherwise noted.
  25  * For more details and historical background, see the LICENSE.md file at
  26  * the top-level directory of this distribution.
  27  *
  28  * ---------------------------------------------------------------------------
  29  */
  30 
  31 //-V::1048
  32 
  33 #include <stdio.h>
  34 #include <ctype.h>
  35 #include <signal.h>
  36 #include <unistd.h>
  37 #include <string.h>
  38 #include <stdlib.h>
  39 #include <sys/stat.h>
  40 
  41 #include "dps8.h"
  42 #include "dps8_iom.h"
  43 #include "dps8_crdpun.h"
  44 #include "dps8_sys.h"
  45 #include "dps8_cable.h"
  46 #include "dps8_cpu.h"
  47 #include "dps8_scu.h"
  48 #include "dps8_faults.h"
  49 #include "dps8_utils.h"
  50 #include "utfile.h"
  51 #include "safemove.h"
  52 
  53 #if defined(__MINGW64__) || defined(__MINGW32__)
  54 # include "bsd_random.h"
  55 # define random  bsd_random
  56 # define srandom bsd_srandom
  57 #endif /* if defined(__MINGW64__) || defined(__MINGW32__) */
  58 
  59 #define DBG_CTR 1
  60 
  61 #if defined(FREE)
  62 # undef FREE
  63 #endif /* if defined(FREE) */
  64 #define FREE(p) do  \
  65   {                 \
  66     free((p));      \
  67     (p) = NULL;     \
  68   } while(0)
  69 
  70 //-- // XXX We use this where we assume there is only one unit
  71 //-- #define ASSUME0 0
  72 //--
  73 
  74 #define N_PUN_UNITS 1 // default
  75 
  76 static t_stat pun_reset (DEVICE * dptr);
  77 static t_stat pun_show_nunits (FILE *st, UNIT *uptr, int val, const void *desc);
  78 static t_stat pun_set_nunits (UNIT * uptr, int32 value, const char * cptr, void * desc);
  79 static t_stat pun_show_device_name (FILE *st, UNIT *uptr, int val, const void *desc);
  80 static t_stat pun_set_device_name (UNIT * uptr, int32 value, const char * cptr, void * desc);
  81 static t_stat pun_show_path (UNUSED FILE * st, UNIT * uptr, UNUSED int val,
  82                              UNUSED const void * desc);
  83 static t_stat pun_set_path (UNUSED UNIT * uptr, UNUSED int32 value, const UNUSED char * cptr,
  84                             UNUSED void * desc);
  85 static t_stat pun_set_config (UNUSED UNIT *  uptr, UNUSED int32 value, const char * cptr,
  86                               UNUSED void * desc);
  87 static t_stat pun_show_config (UNUSED FILE * st, UNUSED UNIT * uptr, UNUSED int val,
  88                                UNUSED const void * desc);
  89 
  90 #define UNIT_FLAGS ( UNIT_FIX | UNIT_ATTABLE | UNIT_ROABLE | UNIT_DISABLE | UNIT_IDLE )
  91 
  92 UNIT pun_unit [N_PUN_UNITS_MAX] =
  93   {
  94     {UDATA (NULL, UNIT_FLAGS, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL},
  95     {UDATA (NULL, UNIT_FLAGS, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL},
  96     {UDATA (NULL, UNIT_FLAGS, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL},
  97     {UDATA (NULL, UNIT_FLAGS, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL},
  98     {UDATA (NULL, UNIT_FLAGS, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL},
  99     {UDATA (NULL, UNIT_FLAGS, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL},
 100     {UDATA (NULL, UNIT_FLAGS, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL},
 101     {UDATA (NULL, UNIT_FLAGS, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL},
 102     {UDATA (NULL, UNIT_FLAGS, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL},
 103     {UDATA (NULL, UNIT_FLAGS, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL},
 104     {UDATA (NULL, UNIT_FLAGS, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL},
 105     {UDATA (NULL, UNIT_FLAGS, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL},
 106     {UDATA (NULL, UNIT_FLAGS, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL},
 107     {UDATA (NULL, UNIT_FLAGS, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL},
 108     {UDATA (NULL, UNIT_FLAGS, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL},
 109     {UDATA (NULL, UNIT_FLAGS, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL}
 110   };
 111 
 112 #define PUN_UNIT_NUM(uptr) ((uptr) - pun_unit)
 113 
 114 static DEBTAB pun_dt [] =
 115   {
 116     { "NOTIFY", DBG_NOTIFY, NULL },
 117     { "INFO",   DBG_INFO,   NULL },
 118     { "ERR",    DBG_ERR,    NULL },
 119     { "WARN",   DBG_WARN,   NULL },
 120     { "DEBUG",  DBG_DEBUG,  NULL },
 121     { "ALL",    DBG_ALL,    NULL }, // Don't move as it messes up DBG message
 122     { NULL,     0,          NULL }
 123   };
 124 
 125 #define UNIT_WATCH UNIT_V_UF
 126 
 127 static MTAB pun_mod [] =
 128   {
 129 #if !defined(SPEED)
 130     { UNIT_WATCH, 1, "WATCH",   "WATCH",   0, 0, NULL, NULL },
 131     { UNIT_WATCH, 0, "NOWATCH", "NOWATCH", 0, 0, NULL, NULL },
 132 #endif /* if !defined(SPEED) */
 133     {
 134       MTAB_XTD | MTAB_VDV | \
 135       MTAB_NMO | MTAB_VALR,                 /* Mask               */
 136       0,                                    /* Match              */
 137       "NUNITS",                             /* Print string       */
 138       "NUNITS",                             /* Match string       */
 139       pun_set_nunits,                       /* Validation routine */
 140       pun_show_nunits,                      /* Display routine    */
 141       "Number of PUN units in the system",  /* Value descriptor   */
 142       NULL                                  /* Help               */
 143     },
 144     {
 145       MTAB_XTD | MTAB_VUN | \
 146       MTAB_VALR | MTAB_NC,                  /* Mask               */
 147       0,                                    /* Match              */
 148       "NAME",                               /* Print string       */
 149       "NAME",                               /* Match string       */
 150       pun_set_device_name,                  /* Validation routine */
 151       pun_show_device_name,                 /* Display routine    */
 152       "Set the punch device name",          /* Value descriptor   */
 153       NULL                                  /* Help               */
 154     },
 155     {
 156       MTAB_XTD | MTAB_VDV | MTAB_NMO | \
 157       MTAB_VALR | MTAB_NC,                  /* Mask               */
 158       0,                                    /* Match              */
 159       "PATH",                               /* Print string       */
 160       "PATH",                               /* Match string       */
 161       pun_set_path,                         /* Validation Routine */
 162       pun_show_path,                        /* Display Routine    */
 163       "Path to card punch directories",     /* Value Descriptor   */
 164       NULL                                  /* Help               */
 165     },
 166     {
 167       MTAB_XTD | MTAB_VUN,                  /* Mask               */
 168       0,                                    /* Match              */
 169       (char *) "CONFIG",                    /* Print string       */
 170       (char *) "CONFIG",                    /* Match string       */
 171       pun_set_config,                       /* Validation routine */
 172       pun_show_config,                      /* Display routine    */
 173       NULL,                                 /* Value descriptor   */
 174       NULL,                                 /* Help               */
 175     },
 176 
 177     { 0, 0, NULL, NULL, 0, 0, NULL, NULL }
 178   };
 179 
 180 DEVICE pun_dev = {
 181     "PUN",        /* Name                */
 182     pun_unit,     /* Units               */
 183     NULL,         /* Registers           */
 184     pun_mod,      /* Modifiers           */
 185     N_PUN_UNITS,  /* #Units              */
 186     10,           /* Address radix       */
 187     24,           /* Address width       */
 188     1,            /* Address increment   */
 189     8,            /* Data radix          */
 190     36,           /* Data width          */
 191     NULL,         /* Examine             */
 192     NULL,         /* Deposit             */
 193     pun_reset,    /* Reset               */
 194     NULL,         /* Boot                */
 195     NULL,         /* Attach              */
 196     NULL,         /* Detach              */
 197     NULL,         /* Context             */
 198     DEV_DEBUG,    /* Flags               */
 199     0,            /* Debug control flags */
 200     pun_dt,       /* Debug flag names    */
 201     NULL,         /* Memory size change  */
 202     NULL,         /* Logical name        */
 203     NULL,         /* Help                */
 204     NULL,         /* Attach help         */
 205     NULL,         /* Attach context      */
 206     NULL,         /* Description         */
 207     NULL          /* End                 */
 208 };
 209 
 210 static config_value_list_t cfg_on_off[] =
 211   {
 212     { "off",     0 },
 213     { "on",      1 },
 214     { "disable", 0 },
 215     { "enable",  1 },
 216     { NULL,      0 }
 217   };
 218 
 219 static config_list_t pun_config_list[] =
 220   {
 221    { "logcards", 0, 1, cfg_on_off },
 222    { NULL,       0, 0, NULL       }
 223   };
 224 
 225 #define WORDS_PER_CARD 27
 226 #define MAX_GLYPH_BUFFER_LEN 1024
 227 #define CARD_COL_COUNT 80
 228 #define NIBBLES_PER_COL 3
 229 #define GLYPHS_PER_CARD 22
 230 #define CHAR_MATRIX_BYTES 5
 231 
 232 enum parse_state {
 233     Idle, StartingJob, PunchGlyphLookup, EndOfHeader, CacheCard, EndOfDeck, EndOfJob
 234 };
 235 
 236 enum parse_event {
 237     NoEvent, BannerCard, EndOfDeckCard, Card, Done
 238 };
 239 
 240 typedef struct card_cache_node CARD_CACHE_ENTRY;
 241 
 242 struct card_cache_node
 243   {
 244       word12 tally;
 245       word36 card_data[WORDS_PER_CARD];
 246       CARD_CACHE_ENTRY *next_entry;
 247   };
 248 
 249 typedef struct
 250   {
 251     char device_name[MAX_DEV_NAME_LEN];
 252     int  punfile_raw;                   // fd of file to get all cards in punch code (w/banner cards)
 253     bool log_cards;                     // Flag to log card images
 254     enum parse_state current_state;
 255     char raw_file_name [SIR_MAXPATH + 1];  // Name associated with punfile_raw
 256     char filename [SIR_MAXPATH];
 257     char glyph_buffer[MAX_GLYPH_BUFFER_LEN];
 258     CARD_CACHE_ENTRY *first_cached_card;
 259     CARD_CACHE_ENTRY *last_cached_card;
 260     enum pun_mode { punNoMode, punWrBin } ioMode;
 261   } pun_state_t ;
 262 
 263 static pun_state_t pun_state[N_PUN_UNITS_MAX];
 264 static char pun_path_prefix[SIR_MAXPATH - 68];   // The -68 is to leave room for file name
 265 
 266 /*
 267  * pun_init()
 268  */
 269 
 270 // Once-only initialization
 271 
 272 void pun_init (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 273   {
 274     (void)memset (pun_path_prefix, 0, sizeof (pun_path_prefix));
 275     (void)memset (pun_state, 0, sizeof (pun_state));
 276     for (int i = 0; i < N_PUN_UNITS_MAX; i ++)
 277       {
 278         pun_state [i] . punfile_raw   = -1;
 279         pun_state [i] . current_state = Idle;
 280         pun_state [i] . filename [0]  = 0;
 281       }
 282   }
 283 
 284 static t_stat pun_reset (UNUSED DEVICE * dptr)
     /* [previous][next][first][last][top][bottom][index][help] */
 285   {
 286     return SCPE_OK;
 287   }
 288 
 289 //                       *****  *   *  ****          *****  *****
 290 //                       *      **  *  *   *         *   *  *
 291 //                       ****   * * *  *   *         *   *  ****
 292 //                       *      *  **  *   *         *   *  *
 293 //                       *****  *   *  ****          *****  *
 294 //
 295 //                              ****   *****  *****  *   *
 296 //                              *   *  *      *      *  *
 297 //*                             *   *  ****   *      ***                         *
 298 //                              *   *  *      *      *  *
 299 //*                             ****   *****  *****  *   *                       *
 300 
 301 static word36 eodCard [WORDS_PER_CARD] =
 302   {
 303     0000500000000llu,
 304     0000000000000llu,
 305     0000000000000llu,
 306     0000000000000llu,
 307     0000000000000llu,
 308     0000000002000llu,
 309     0240024002400llu,
 310     0370000000000llu,
 311     0372121122104llu,
 312     0210437370000llu,
 313     0000000210021llu,
 314     0002100210037llu,
 315     0000000001621llu,
 316     0212521252125llu,
 317     0373700000000llu,
 318     0371602210421llu,
 319     0102137370000llu,
 320     0000021002500llu,
 321     0250025003700llu,
 322     0000000000000llu,
 323     0000000000000llu,
 324     0000000000000llu,
 325     0000000000000llu,
 326     0000000000000llu,
 327     0000000000000llu,
 328     0000000000000llu,
 329     0000000050000llu
 330   };
 331 
 332 //    *****         *****         *****         *****         *****         *****
 333 //    *****         *****         *****         *****         *****         *****
 334 //    *****         *****         *****         *****         *****         *****
 335 //    *****   ***   *****   ***   *****   ***   *****   ***   *****   ***   *****
 336 //    *****         *****         *****         *****         *****         *****
 337 //    *****         *****         *****         *****         *****         *****
 338 //           *****         *****         *****         *****         *****
 339 //           *****         *****         *****         *****         *****
 340 //           *****         *****         *****         *****         *****
 341 // *   ***   *****   ***   *****   ***   *****   ***   *****   ***   *****   ***  *
 342 //           *****         *****         *****         *****         *****
 343 // *         *****         *****         *****         *****         *****        *
 344 
 345 static word36 bannerCard [WORDS_PER_CARD] =
 346   {
 347     0000500000000llu,
 348     0770077047704llu,
 349     0770477000000llu,
 350     0000000770477llu,
 351     0047704770077llu,
 352     0000000007700llu,
 353     0770477047704llu,
 354     0770000000000llu,
 355     0007704770477llu,
 356     0047700770000llu,
 357     0000077007704llu,
 358     0770477047700llu,
 359     0000000000077llu,
 360     0047704770477llu,
 361     0007700000000llu,
 362     0770077047704llu,
 363     0770477000000llu,
 364     0000000770477llu,
 365     0047704770077llu,
 366     0000000007700llu,
 367     0770477047704llu,
 368     0770000000000llu,
 369     0007704770477llu,
 370     0047700770000llu,
 371     0000077007704llu,
 372     0770477047700llu,
 373     0000000050000llu
 374   };
 375 
 376 /*
 377  *                  Glyph Pattern Lookup
 378  * This is the parsing of the "lace" cards and extracting the ASCII characters
 379  * have been punched into the cards (as glyphs) so the operator knows how to
 380  * deliver the deck.
 381  */
 382 
 383 #define NUM_GLYPH_CHAR_PATTERNS 45
 384 
 385 static uint8 glyph_char_patterns [NUM_GLYPH_CHAR_PATTERNS][CHAR_MATRIX_BYTES] =
 386   {
 387       // Asterisk
 388       { 037, 037, 037, 037, 037 },
 389       // Space
 390       { 000, 000, 000, 000, 000 },
 391       // Period
 392       { 000, 003, 003, 003, 000 },
 393       // >
 394       { 021, 000, 012, 000, 004 },
 395       // A
 396       { 037, 024, 024, 024, 037 },
 397       // B
 398       { 037, 025, 025, 025, 012 },
 399       // C
 400       { 037, 021, 021, 021, 021 },
 401       // D
 402       { 037, 021, 021, 021, 016 },
 403       // E
 404       { 037, 025, 025, 025, 021 },
 405       // F
 406       { 037, 024, 024, 024, 020 },
 407       // G
 408       { 037, 021, 021, 025, 027 },
 409       // H
 410       { 037, 004, 004, 004, 037 },
 411       // I
 412       { 021, 021, 037, 021, 021 },
 413       // J
 414       { 003, 001, 001, 001, 037 },
 415       // K
 416       { 037, 004, 004, 012, 021 },
 417       // L
 418       { 037, 001, 001, 001, 001 },
 419       // M
 420       { 037, 010, 004, 010, 037 },
 421       // N
 422       { 037, 010, 004, 002, 037 },
 423       // O
 424       { 037, 021, 021, 021, 037 },
 425       // P
 426       { 037, 024, 024, 024, 034 },
 427       // Q
 428       { 037, 021, 025, 023, 037 },
 429       // R
 430       { 037, 024, 024, 026, 035 },
 431       // S
 432       { 035, 025, 025, 025, 027 },
 433       // T
 434       { 020, 020, 037, 020, 020 },
 435       // U
 436       { 037, 001, 001, 001, 037 },
 437       // V
 438       { 030, 006, 001, 006, 030 },
 439       // W
 440       { 037, 002, 004, 002, 037 },
 441       // X
 442       { 021, 012, 004, 012, 021 },
 443       // Y
 444       { 020, 010, 007, 010, 020 },
 445       // Z
 446       { 021, 027, 025, 035, 021 },
 447       // 0
 448       { 016, 021, 021, 021, 016 },
 449       // 1
 450       { 000, 010, 000, 037, 000 },
 451       // 2
 452       { 023, 025, 025, 025, 035 },
 453       // 3
 454       { 021, 025, 025, 025, 037 },
 455       // 4
 456       { 034, 004, 004, 004, 037 },
 457       // 5
 458       { 035, 025, 025, 025, 022 },
 459       // 6
 460       { 037, 005, 005, 005, 007 },
 461       // 7
 462       { 020, 021, 022, 024, 030 },
 463       // 8
 464       { 012, 025, 025, 025, 012 },
 465       // 9
 466       { 034, 024, 024, 024, 037 },
 467       // Underscore
 468       { 001, 001, 001, 001, 001 },
 469       // Hyphen
 470       { 000, 004, 004, 004, 000 },
 471       // (
 472       { 000, 004, 012, 021, 000 },
 473       // )
 474       { 000, 021, 012, 004, 000 },
 475       // /
 476       { 001, 002, 004, 010, 020 }
 477   };
 478 
 479 static char glyph_chars [NUM_GLYPH_CHAR_PATTERNS] =
 480   {
 481       '*', ' ', '.', '>', 'A', 'B', 'C', 'D', 'E', 'F',
 482       'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
 483       'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
 484       '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
 485       '_', '-', '(', ')', '/'
 486   };
 487 
 488 static uint8 glyph_char_word_offset [11] =
 489   {
 490       24, 22, 19, 17, 15, 12, 10, 8, 5, 3, 1
 491   };
 492 
 493 static uint8 glyph_nibble_offset [11] =
 494   {
 495        1,  2,  0,  1,  2,  0,  1, 2, 0, 1, 2
 496   };
 497 
 498 static void remove_spaces(char *str)
     /* [previous][next][first][last][top][bottom][index][help] */
 499 {
 500   int src = 0;
 501   int dest = 0;
 502   while (str[src])
 503     {
 504       if (str[src] != ' ')
 505         {
 506           str[dest++] = str[src];
 507         }
 508           src++;
 509         }
 510   str[dest] = 0;
 511 }
 512 
 513 static void log_char_matrix_pattern(uint8* char_matrix)
     /* [previous][next][first][last][top][bottom][index][help] */
 514   {
 515     sim_print("\r\nChar Matrix\r\n");
 516     for (uint col_offset = 0; col_offset < CHAR_MATRIX_BYTES; col_offset++)
 517       {
 518         sim_printf(" %03o\r\n", char_matrix[col_offset]);
 519       }
 520 
 521     sim_print("\r\n");
 522     for (uint row = 0; row < 5; row++)
 523       {
 524         for (uint col = 0; col < CHAR_MATRIX_BYTES; col++)
 525           {
 526             if ((char_matrix[col] >> (4 - row)) & 0x1)
 527               {
 528                 sim_print("*");
 529               }
 530             else
 531               {
 532                 sim_print(" ");
 533               }
 534           }
 535           sim_print("\r\n");
 536       }
 537     sim_print("\r\n");
 538 
 539   }
 540 
 541 static char search_glyph_patterns(uint8* matrix)
     /* [previous][next][first][last][top][bottom][index][help] */
 542   {
 543     for (int pattern = 0; pattern < NUM_GLYPH_CHAR_PATTERNS; pattern++)
 544       {
 545         if (memcmp(matrix, &glyph_char_patterns[pattern], CHAR_MATRIX_BYTES) == 0)
 546           {
 547             return glyph_chars[pattern];
 548           }
 549       }
 550 
 551       sim_warn("*** Warning: Punch found unknown block character pattern\r\n");
 552       log_char_matrix_pattern(matrix);
 553 
 554       return ' ';
 555   }
 556 
 557 static char get_lace_char(word36* buffer, uint char_pos)
     /* [previous][next][first][last][top][bottom][index][help] */
 558   {
 559     if (char_pos >= GLYPHS_PER_CARD)
 560       {
 561         sim_warn("*** Error: Attempt to read punch block character out of range (%u)\r\n", char_pos);
 562         return 0;
 563       }
 564 
 565     bool top           = char_pos < 11;                              // Top or bottom line of chars
 566     uint char_offset   = (char_pos < 11) ? char_pos : char_pos - 11; // Character num in the line
 567     uint word_offset   = glyph_char_word_offset[char_offset];        // Starting word in the buffer
 568     uint nibble_offset = glyph_nibble_offset[char_offset];           // Starting nibble in the word
 569     word12 col_buffer[5];                                            // Extracted 5 cols for char
 570 
 571     // Extract the five 12-bit words from the main buffer that make up the character
 572     // Note that in this process we reverse the character image so it reads normally
 573     // (characters are punched in reverse)
 574     for (uint col_offset = 0; col_offset < 5; col_offset++)
 575       {
 576         col_buffer[4 - col_offset] = (buffer[word_offset] >> (nibble_offset * 12)) & 0xFFF;
 577         if (nibble_offset == 0)
 578           {
 579             nibble_offset = 2;
 580             word_offset++;
 581           }
 582         else
 583           {
 584             nibble_offset--;
 585           }
 586       }
 587 
 588     // Now shift the characters into the 5x5 matrix buffer
 589     uint8 char_matrix[CHAR_MATRIX_BYTES];
 590 
 591     for (uint col_offset = 0; col_offset < CHAR_MATRIX_BYTES; col_offset++)
 592       {
 593         char_matrix[col_offset] = (col_buffer[col_offset] >> (top ? 6 : 0)) & 0x1F;
 594       }
 595 
 596     char c = search_glyph_patterns(char_matrix);
 597 
 598     return c;
 599   }
 600 
 601 static void scan_card_for_glyphs(pun_state_t * state, word36* buffer)
     /* [previous][next][first][last][top][bottom][index][help] */
 602   {
 603     for (uint c_pos = 0; c_pos < 22; c_pos++)
 604       {
 605         char c = get_lace_char(buffer, c_pos);
 606         uint current_length = (uint)strlen(state -> glyph_buffer);
 607         if (current_length < (sizeof(state -> glyph_buffer) - 1))
 608           {
 609             state -> glyph_buffer[current_length++] = c;
 610             state -> glyph_buffer[current_length]   = 0;
 611           }
 612       }
 613   }
 614 
 615 static void create_punch_file(pun_state_t * state)
     /* [previous][next][first][last][top][bottom][index][help] */
 616   {
 617     char template [4 * SIR_MAXPATH+1];
 618 
 619     if (state -> punfile_raw != -1)
 620       {
 621         sim_warn ("*** Error: Punch file already open when attempting to create new file, closing old file!\r\n");
 622         close (state -> punfile_raw);
 623         state -> punfile_raw = -1;
 624       }
 625 
 626     if (pun_path_prefix [0])
 627       {
 628         char dir_path [SIR_MAXPATH];
 629         (void)sprintf (dir_path, "%s%s", pun_path_prefix, state -> device_name);
 630         struct stat st = {0};
 631         if (stat (dir_path, & st) == -1)
 632           {
 633             if (x_mkdir_p (dir_path) == -1)
 634               {
 635                 sim_warn ("Failed to create punch output directory '%s': %s (Error %d)\r\n",
 636                           dir_path, xstrerror_l (errno), errno);
 637                 return;
 638               }
 639           }
 640 
 641         (void)sprintf (template, "%s/%s.spool.%s.XXXXXX.pun.OPEN",
 642                        dir_path,
 643                        state -> device_name,
 644                        state -> raw_file_name);
 645       }
 646     else
 647       {
 648         (void)sprintf (template, "%s.spool.%s.XXXXXX.pun.OPEN",
 649                        state -> device_name,
 650                        state -> raw_file_name);
 651       }
 652 
 653     state -> punfile_raw = utfile_mkstemps(template, 9);
 654     (void)snprintf (state -> filename, sizeof (state -> filename), "%s", template);
 655 
 656     if (state -> punfile_raw < 0)
 657       perror("creating punch '.pun' file\r\n");
 658 
 659   }
 660 
 661 static void write_punch_files (pun_state_t * state, word36* in_buffer, int word_count,
     /* [previous][next][first][last][top][bottom][index][help] */
 662                                bool banner_card)
 663   {
 664       if (word_count != WORDS_PER_CARD)
 665         {
 666           sim_warn ("Unable to interpret punch buffer due to wrong length, not writing output!\r\n");
 667           return;
 668         }
 669 
 670       uint8 byte_buffer[120];
 671       (void)memset(&byte_buffer, 0, sizeof(byte_buffer));
 672 
 673       word12 word12_buffer[80];
 674       (void)memset(&word12_buffer, 0, sizeof(word12_buffer));
 675 
 676       for (int nibble_index = 0; nibble_index < (CARD_COL_COUNT * NIBBLES_PER_COL); nibble_index++)
 677         {
 678           int byte_offset   = nibble_index / 2;
 679           int word36_offset = nibble_index / 9;
 680           int nibble_offset = nibble_index % 9;
 681           uint8 nibble = (in_buffer[word36_offset] >> ((8 - nibble_offset) * 4)) & 0xF;
 682 
 683           if (nibble_index & 0x1) // Low nibble of byte
 684             byte_buffer [byte_offset] |= nibble;
 685           else                    // High nibble of byte
 686             byte_buffer [byte_offset] |= (nibble << 4);
 687 
 688           int word12_offset        =      nibble_index / 3;
 689           int word12_nibble_offset = 2 - (nibble_index % 3);
 690 
 691           word12_buffer[word12_offset] |= nibble << (word12_nibble_offset * 4);
 692         }
 693 
 694       if (state->log_cards)
 695       {
 696         sim_printf("word12_buffer:\r\n");
 697         for (uint i = 0; i < 80; i++)
 698           {
 699             sim_printf("  %04o\r\n", word12_buffer[i]);
 700           }
 701         sim_printf("\r\n");
 702       }
 703 
 704       if (state -> punfile_raw >= 0)
 705         {
 706           if (write (state -> punfile_raw, byte_buffer,
 707                      sizeof(byte_buffer)) != sizeof(byte_buffer)) {
 708             sim_warn ("Failed to write to .raw card punch file!\r\n");
 709             perror("Writing .raw punch file\r\n");
 710           }
 711         }
 712 
 713   }
 714 
 715 static void log_card(word12 tally, word36 * buffer)
     /* [previous][next][first][last][top][bottom][index][help] */
 716   {
 717     sim_printf ("tally %d\r\n", tally);
 718 
 719     for (uint i = 0; i < tally; i ++)
 720       {
 721         sim_printf ("  %012llo\r\n", (unsigned long long)buffer [i]);
 722       }
 723     sim_printf ("\r\n");
 724 
 725     if (tally != WORDS_PER_CARD)
 726       {
 727         sim_warn("Unable to log punch card, tally is not 27 (%d)\r\n", tally);
 728         return;
 729       }
 730 
 731     for (uint row = 0; row < 12; row ++)
 732       {
 733         for (uint col = 0; col < 80; col ++)
 734           {
 735             // 3 cols/word
 736             uint wordno  = col / 3;
 737             uint fieldno = col % 3;
 738             word1 bit = getbits36_1 (buffer [wordno], fieldno * 12 + row);
 739             if (bit)
 740                 sim_printf ("*");
 741             else
 742                 sim_printf (" ");
 743           }
 744         sim_printf ("\r\n");
 745       }
 746     sim_printf ("\r\n");
 747 
 748     for (uint row = 0; row < 12; row ++)
 749       {
 750         //for (uint col = 0; col < 80; col ++)
 751         for (int col = 79; col >= 0; col --)
 752           {
 753             // 3 cols/word
 754             uint wordno  = (uint) col / 3;
 755             uint fieldno = (uint) col % 3;
 756             word1 bit = getbits36_1 (buffer [wordno], fieldno * 12 + row);
 757             if (bit)
 758                 sim_printf ("*");
 759             else
 760                 sim_printf (" ");
 761           }
 762         sim_printf ("\r\n");
 763       }
 764     sim_printf ("\r\n");
 765   }
 766 
 767 static void print_event(enum parse_event event)
     /* [previous][next][first][last][top][bottom][index][help] */
 768   {
 769     switch (event)
 770       {
 771         case NoEvent:
 772           sim_warn("[No Event]");
 773           break;
 774         case BannerCard:
 775           sim_warn("[Banner Card]");
 776           break;
 777         case EndOfDeckCard:
 778           sim_warn("[End Of Deck Card]");
 779           break;
 780         case Card:
 781           sim_warn("[Card]");
 782           break;
 783         case Done:
 784           sim_warn("[Done]");
 785           break;
 786         default:
 787           sim_warn("[unknown event %d]", event);
 788           break;
 789       }
 790   }
 791 
 792 static void print_state(enum parse_state state)
     /* [previous][next][first][last][top][bottom][index][help] */
 793   {
 794     switch (state)
 795       {
 796         case Idle:
 797           sim_warn("[Idle]");
 798           break;
 799         case StartingJob:
 800           sim_warn("[Starting Job]");
 801           break;
 802         case PunchGlyphLookup:
 803           sim_warn("[Punch Glyph Lookup]");
 804           break;
 805         case EndOfHeader:
 806           sim_warn("[End Of Header]");
 807           break;
 808         case CacheCard:
 809           sim_warn("[Cache Card]");
 810           break;
 811         case EndOfDeck:
 812           sim_warn("[End Of Deck]");
 813           break;
 814         case EndOfJob:
 815           sim_warn("[End Of Job]");
 816           break;
 817         default:
 818           sim_warn("[unknown state %d]", state);
 819           break;
 820       }
 821   }
 822 
 823 static void print_transition(enum parse_state old_state, enum parse_event event,
     /* [previous][next][first][last][top][bottom][index][help] */
 824                              enum parse_state new_state)
 825   {
 826       sim_warn(">>> Punch Transition: ");
 827       print_event(event);
 828       sim_warn(" = ");
 829       print_state(old_state);
 830       sim_warn(" -> ");
 831       print_state(new_state);
 832       sim_warn("\r\n");
 833   }
 834 
 835 static void clear_card_cache(pun_state_t * state)
     /* [previous][next][first][last][top][bottom][index][help] */
 836   {
 837     CARD_CACHE_ENTRY *current_entry = state -> first_cached_card;
 838     while (current_entry != NULL)
 839       {
 840         CARD_CACHE_ENTRY *old_entry = current_entry;
 841         current_entry               = current_entry->next_entry;
 842         FREE(old_entry);
 843       }
 844 
 845     state -> first_cached_card = NULL;
 846     state -> last_cached_card  = NULL;
 847   }
 848 
 849 static void save_card_in_cache(pun_state_t * state, word12 tally, word36 * card_buffer)
     /* [previous][next][first][last][top][bottom][index][help] */
 850   {
 851     CARD_CACHE_ENTRY *new_entry = malloc(sizeof(CARD_CACHE_ENTRY));
 852     if (!new_entry)
 853       {
 854         (void)fprintf(stderr, "\rFATAL: Out of memory! Aborting at %s[%s:%d]\r\n",
 855                       __func__, __FILE__, __LINE__);
 856 #if defined(USE_BACKTRACE)
 857 # if defined(SIGUSR2)
 858         (void)raise(SIGUSR2);
 859         /*NOTREACHED*/ /* unreachable */
 860 # endif /* if defined(SIGUSR2) */
 861 #endif /* if defined(USE_BACKTRACE) */
 862         abort();
 863       }
 864 
 865     new_entry -> tally = tally;
 866     memcpy(&new_entry -> card_data, card_buffer, sizeof(word36) * tally);
 867     new_entry -> next_entry = NULL;
 868 
 869     if (state -> first_cached_card == NULL)
 870       {
 871         state -> first_cached_card = new_entry;
 872         state -> last_cached_card  = new_entry;
 873       }
 874     else
 875       {
 876         state -> last_cached_card -> next_entry = new_entry;
 877         state -> last_cached_card               = new_entry;
 878       }
 879   }
 880 
 881 static void transition_state(enum parse_event event, pun_state_t * state,
     /* [previous][next][first][last][top][bottom][index][help] */
 882                              enum parse_state new_state)
 883   {
 884     if (state -> log_cards)
 885       {
 886         print_transition(state -> current_state, event, new_state);
 887       }
 888 
 889     state -> current_state = new_state;
 890   }
 891 
 892 static enum parse_event do_state_idle(enum parse_event event, pun_state_t * state)
     /* [previous][next][first][last][top][bottom][index][help] */
 893   {
 894     transition_state(event, state, Idle);
 895 
 896     // No Action
 897 
 898     return NoEvent;
 899   }
 900 
 901 static enum parse_event do_state_starting_job(enum parse_event event,
     /* [previous][next][first][last][top][bottom][index][help] */
 902                                               pun_state_t * state, word12 tally,
 903                                               word36 * card_buffer)
 904   {
 905     transition_state(event, state, StartingJob);
 906 
 907     clear_card_cache(state);                            // Clear card cache
 908     state -> glyph_buffer[0] = 0;                       // Clear Glyph Buffer
 909     save_card_in_cache(state, tally, card_buffer);      // Save card in cache
 910 
 911     return NoEvent;
 912   }
 913 
 914 static enum parse_event do_state_scan_card_for_glyphs(enum parse_event event,
     /* [previous][next][first][last][top][bottom][index][help] */
 915                                                       pun_state_t * state,
 916                                                       word12 tally,
 917                                                       word36 * card_buffer)
 918   {
 919     transition_state(event, state, PunchGlyphLookup);
 920 
 921     scan_card_for_glyphs(state, card_buffer);
 922 
 923     save_card_in_cache(state, tally, card_buffer);    // Save card in cache
 924 
 925     return NoEvent;
 926   }
 927 
 928 static enum parse_event do_state_end_of_header(enum parse_event event,
     /* [previous][next][first][last][top][bottom][index][help] */
 929                                                pun_state_t * state,
 930                                                word12 tally,
 931                                                word36 * card_buffer)
 932   {
 933     transition_state(event, state, EndOfHeader);
 934 
 935     save_card_in_cache(state, tally, card_buffer);      // Save card in cache
 936 
 937     if (state -> log_cards)
 938       {
 939         sim_printf("\r\n++++ Glyph Buffer ++++\r\n'%s'\r\n", state -> glyph_buffer);
 940       }
 941 
 942     char punch_file_name[SIR_MAXPATH+1];
 943     if (strlen(state -> glyph_buffer) < 86)
 944       {
 945         sim_warn ("*** Punch: glyph buffer too short, unable to parse file name '%s'\r\n",
 946                   state -> glyph_buffer);
 947         punch_file_name[0] = 0;
 948       }
 949     else
 950       {
 951         (void)sprintf (punch_file_name, "%5.5s.%22.22s",
 952                        &state -> glyph_buffer[14],
 953                        &state -> glyph_buffer[88]
 954         );
 955         remove_spaces(punch_file_name);
 956       }
 957 
 958     strncpy(state -> raw_file_name, punch_file_name, sizeof(state -> raw_file_name) - 1);
 959     state->raw_file_name[sizeof(state->raw_file_name) - 1] = '\0';
 960 
 961     create_punch_file(state);                           // Create spool file
 962 
 963     // Write cached cards to spool file
 964     CARD_CACHE_ENTRY *current_entry = state -> first_cached_card;
 965     while (current_entry != NULL)
 966       {
 967         write_punch_files (state, current_entry -> card_data, WORDS_PER_CARD, true);
 968         current_entry = current_entry->next_entry;
 969       }
 970 
 971     clear_card_cache(state);                            // Clear card cache
 972 
 973     return NoEvent;
 974   }
 975 
 976 static enum parse_event do_state_cache_card(enum parse_event event,
     /* [previous][next][first][last][top][bottom][index][help] */
 977                                             pun_state_t * state,
 978                                             word12 tally,
 979                                             word36 * card_buffer)
 980   {
 981     transition_state(event, state, CacheCard);
 982 
 983     save_card_in_cache(state, tally, card_buffer);      // Save card in cache
 984 
 985     return NoEvent;
 986   }
 987 
 988 static enum parse_event do_state_end_of_deck(enum parse_event event,
     /* [previous][next][first][last][top][bottom][index][help] */
 989                                              pun_state_t * state,
 990                                              word12 tally,
 991                                              word36 * card_buffer)
 992   {
 993     transition_state(event, state, EndOfDeck);
 994 
 995     save_card_in_cache(state, tally, card_buffer);      // Save card in cache
 996 
 997     return NoEvent;
 998   }
 999 
1000 static enum parse_event do_state_end_of_job(enum parse_event event,
     /* [previous][next][first][last][top][bottom][index][help] */
1001                                             pun_state_t * state,
1002                                             word12 tally,
1003                                             word36 * card_buffer)
1004   {
1005     transition_state(event, state, EndOfJob);
1006 
1007     // Write cached cards to spool file
1008     CARD_CACHE_ENTRY *current_entry = state -> first_cached_card;
1009     while (current_entry != NULL)
1010       {
1011         write_punch_files (state, current_entry -> card_data, WORDS_PER_CARD,
1012                 (current_entry -> next_entry == NULL));
1013         current_entry = current_entry->next_entry;
1014       }
1015 
1016     clear_card_cache(state);                                // Clear card cache
1017 
1018     write_punch_files (state, card_buffer, tally, true);    // Write card to spool file
1019 
1020     // Close punch files
1021     if (state -> punfile_raw >= 0)
1022       {
1023         close (state -> punfile_raw);
1024         state -> punfile_raw = -1;
1025 
1026         int rename_attempts           = 0;
1027         const int MAX_RENAME_ATTEMPTS = 10000;
1028         bool rename_successful        = false;
1029 
1030         char old_open_filename [SIR_MAXPATH];
1031         char new_target_filename [SIR_MAXPATH];
1032 
1033         strcpy (old_open_filename, state -> filename);
1034 
1035         char * open_ext_pos = strstr (old_open_filename, ".OPEN");
1036         if (open_ext_pos)
1037           {
1038             strncpy (new_target_filename, old_open_filename, open_ext_pos - old_open_filename);
1039             new_target_filename [open_ext_pos - old_open_filename] = '\0';
1040           }
1041         else
1042           strcpy (new_target_filename, old_open_filename);
1043 
1044         while (rename_attempts < MAX_RENAME_ATTEMPTS)
1045           {
1046             if (safe_rename (old_open_filename, new_target_filename) == 0)
1047               {
1048                 rename_successful = true;
1049                 break;
1050               }
1051 
1052             char new_random_part [7];
1053             const char charset []  = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
1054             const int charset_size = sizeof (charset) - 1;
1055 
1056             for (int i = 0; i < 6; ++i)
1057               new_random_part [i] = charset [random () % charset_size];
1058 
1059             new_random_part [6] = '\0';
1060 
1061             char * prt_ext_pos = strstr (new_target_filename, ".pun");
1062             if (prt_ext_pos)
1063               {
1064                 char * random_start_pos = prt_ext_pos - 6;
1065                 if (random_start_pos >= new_target_filename)
1066                   (void)memmove (random_start_pos, new_random_part, 6);
1067               }
1068 
1069             rename_attempts++;
1070           }
1071 
1072         if (! rename_successful)
1073           sim_warn ("Failed to rename '%s': %s (Error %d)\r\n", old_open_filename, xstrerror_l (errno), errno);
1074       }
1075 
1076     return Done;
1077   }
1078 
1079 static void unexpected_event(enum parse_event event, pun_state_t * state)
     /* [previous][next][first][last][top][bottom][index][help] */
1080   {
1081     sim_warn("*** Punch: Unexpected event ");
1082     print_event(event);
1083 
1084     sim_warn(" in state ");
1085     print_state(state -> current_state);
1086 
1087     sim_warn("***\r\n");
1088   }
1089 
1090 static void parse_card(pun_state_t * state, word12 tally, word36 * card_buffer)
     /* [previous][next][first][last][top][bottom][index][help] */
1091   {
1092     enum parse_event event = Card;
1093 
1094     if (tally == WORDS_PER_CARD && memcmp (card_buffer, eodCard, sizeof (eodCard)) == 0)
1095       {
1096         event = EndOfDeckCard;
1097       }
1098 
1099     if (tally == WORDS_PER_CARD && memcmp (card_buffer, bannerCard, sizeof (bannerCard)) == 0)
1100       {
1101         event = BannerCard;
1102       }
1103 
1104     while (event != NoEvent)
1105       {
1106         enum parse_event current_event = event;
1107         event = NoEvent;
1108 
1109         switch (current_event)
1110           {
1111             case BannerCard:
1112               switch (state -> current_state)
1113                 {
1114                   case Idle:
1115                     event = do_state_starting_job(current_event, state, tally, card_buffer);
1116                     break;
1117 
1118                   case PunchGlyphLookup:
1119                     event = do_state_end_of_header(current_event, state, tally, card_buffer);
1120                     break;
1121 
1122                   case EndOfDeck:
1123                     event = do_state_end_of_job(current_event, state, tally, card_buffer);
1124                     break;
1125 
1126                   default:
1127                     unexpected_event(current_event, state);
1128                     break;
1129                 }
1130               break;
1131 
1132             case EndOfDeckCard:
1133               switch (state -> current_state)
1134                 {
1135                   case StartingJob:
1136                     event = do_state_end_of_deck(current_event, state, tally, card_buffer);
1137                     break;
1138 
1139                   case PunchGlyphLookup:
1140                     event = do_state_end_of_deck(current_event, state, tally, card_buffer);
1141                     break;
1142 
1143                   case EndOfHeader:
1144                     event = do_state_end_of_deck(current_event, state, tally, card_buffer);
1145                     break;
1146 
1147                   case CacheCard:
1148                     event = do_state_end_of_deck(current_event, state, tally, card_buffer);
1149                     break;
1150 
1151                   case EndOfDeck:
1152                     event = do_state_end_of_deck(current_event, state, tally, card_buffer);
1153                     break;
1154 
1155                   default:
1156                     unexpected_event(current_event, state);
1157                     break;
1158                 }
1159               break;
1160 
1161             case Card:
1162               switch (state -> current_state)
1163                 {
1164                   case StartingJob:
1165                     event = do_state_scan_card_for_glyphs(current_event, state, tally, card_buffer); //-V1037
1166                     break;
1167 
1168                   case PunchGlyphLookup:
1169                     event = do_state_scan_card_for_glyphs(current_event, state, tally, card_buffer);
1170                     break;
1171 
1172                   case EndOfHeader:
1173                     event = do_state_cache_card(current_event, state, tally, card_buffer); //-V1037
1174                     break;
1175 
1176                   case CacheCard:
1177                     event = do_state_cache_card(current_event, state, tally, card_buffer);
1178                     break;
1179 
1180                   case EndOfDeck:
1181                     event = do_state_cache_card(current_event, state, tally, card_buffer);
1182                     break;
1183 
1184                   default:
1185                     unexpected_event(current_event, state);
1186                     break;
1187                 }
1188               break;
1189 
1190             case Done:
1191               switch (state -> current_state)
1192                 {
1193                   case EndOfJob:
1194                     event = do_state_idle(current_event, state);
1195                     break;
1196 
1197                   default:
1198                     unexpected_event(current_event, state);
1199                     break;
1200                 }
1201               break;
1202 
1203             default:
1204               sim_warn("*** Error: Punch received unknown event!\r\n");
1205               break;
1206           }
1207       }
1208 
1209   }
1210 
1211 static int punWriteRecord (uint iomUnitIdx, uint chan)
     /* [previous][next][first][last][top][bottom][index][help] */
1212   {
1213     iom_chan_data_t * p = & iom_chan_data [iomUnitIdx] [chan];
1214     uint dev_code       = p->IDCW_DEV_CODE;
1215     uint ctlr_unit_idx  = get_ctlr_idx (iomUnitIdx, chan);
1216     uint devUnitIdx     = cables->urp_to_urd[ctlr_unit_idx][dev_code].unit_idx;
1217     UNIT * unitp        = & pun_unit [devUnitIdx];
1218     long pun_unit_num   = PUN_UNIT_NUM (unitp);
1219 
1220     p -> isRead = false;
1221     if (p -> DDCW_TALLY != WORDS_PER_CARD)
1222       {
1223         sim_warn ("%s expected tally of 27\r\n", __func__);
1224         p -> chanStatus = chanStatIncorrectDCW;
1225         p -> stati = 05001; //-V536  // BUG: arbitrary error code; config switch
1226         return -1;
1227       }
1228 
1229 //dcl 1 raw aligned,    /* raw column binary card image */
1230 //    2 col (1:80) bit (12) unal,                             /* 80 columns */
1231 //    2 pad bit (12) unal;
1232 
1233     // Copy from core to buffer
1234     word36 buffer [p -> DDCW_TALLY];
1235     uint wordsProcessed = 0;
1236     iom_indirect_data_service (iomUnitIdx, chan, buffer, & wordsProcessed, false);
1237     p->initiate         = false;
1238 
1239     if (pun_state [pun_unit_num] . log_cards)
1240       {
1241         log_card(p -> DDCW_TALLY, buffer);
1242       }
1243 
1244     parse_card( &pun_state [pun_unit_num], p -> DDCW_TALLY, buffer);
1245 
1246     p -> stati = 04000; //-V536
1247     return 0;
1248   }
1249 
1250 iom_cmd_rc_t pun_iom_cmd (uint iomUnitIdx, uint chan) {
     /* [previous][next][first][last][top][bottom][index][help] */
1251   iom_cmd_rc_t rc     = IOM_CMD_PROCEED;
1252   iom_chan_data_t * p = & iom_chan_data[iomUnitIdx][chan];
1253   uint dev_code       = p->IDCW_DEV_CODE;
1254 #if defined(TESTING)
1255   cpu_state_t * cpup  = _cpup;
1256 
1257   sim_debug (DBG_TRACE, & pun_dev, "%s: PUN %c%02o_%02o\r\n",
1258           __func__, iomChar (iomUnitIdx), chan, dev_code);
1259 #endif
1260 
1261   uint ctlr_unit_idx   = get_ctlr_idx (iomUnitIdx, chan);
1262   uint devUnitIdx      = cables->urp_to_urd[ctlr_unit_idx][dev_code].unit_idx;
1263   pun_state_t * statep = & pun_state[devUnitIdx];
1264 
1265   // IDCW?
1266   if (IS_IDCW (p)) {
1267     // IDCW
1268     statep->ioMode = punNoMode;
1269     switch (p->IDCW_DEV_CMD) {
1270       case 011: // CMD 011 Punch binary
1271         sim_debug (DBG_DEBUG, & pun_dev, "%s: Punch Binary\r\n", __func__);
1272         statep->ioMode = punWrBin;
1273         p->stati       = 04000;
1274         break;
1275 
1276       case 031: // CMD 031 Set Diagnostic Mode (load_mpc.pl1)
1277         sim_debug (DBG_DEBUG, & pun_dev, "%s: Set Diagnostic Mode\r\n", __func__);
1278         p->stati = 04000;
1279         break;
1280 
1281       case 040: // CMD 40 Reset status
1282         sim_debug (DBG_DEBUG, & pun_dev, "%s: Reset Status\r\n", __func__);
1283         p->stati  = 04000;
1284         p->isRead = false;
1285         break;
1286 
1287       default:
1288         if (p->IDCW_DEV_CMD != 051) // ignore bootload console probe
1289           sim_warn ("%s: PUN unrecognized device command  %02o\r\n", __func__, p->IDCW_DEV_CMD);
1290         p->stati      = 04501; // cmd reject, invalid opcode
1291         p->chanStatus = chanStatIncorrectDCW;
1292         return IOM_CMD_ERROR;
1293     } // switch IDCW_DEV_CMD
1294     sim_debug (DBG_DEBUG, & pun_dev, "%s: stati %04o\r\n", __func__, p->stati);
1295     return IOM_CMD_PROCEED;
1296   } // IDCW
1297 
1298   // Not IDCW; TDCW are captured in IOM, so must be IOTD, IOTP or IOTNP
1299   switch (statep->ioMode) {
1300     case punNoMode:
1301       //sim_printf ("%s: Unexpected IOTx\r\n", __func__);
1302       //sim_warn ("%s: Unexpected IOTx\r\n", __func__);
1303       //return IOM_CMD_ERROR;
1304       break;
1305 
1306     case punWrBin: {
1307         int rc = punWriteRecord (iomUnitIdx, chan);
1308         if (rc)
1309           return IOM_CMD_ERROR;
1310       }
1311       break;
1312 
1313     default:
1314       sim_warn ("%s: Unrecognized ioMode %d\r\n", __func__, statep->ioMode);
1315       return IOM_CMD_ERROR;
1316   }
1317   return rc;
1318 }
1319 
1320 static t_stat pun_show_nunits (UNUSED FILE * st, UNUSED UNIT * uptr, UNUSED int val,
     /* [previous][next][first][last][top][bottom][index][help] */
1321                                UNUSED const void * desc)
1322   {
1323     sim_printf("Number of PUN units in system is %d\r\n", pun_dev . numunits);
1324     return SCPE_OK;
1325   }
1326 
1327 static t_stat pun_set_nunits (UNUSED UNIT * uptr, UNUSED int32 value, const char * cptr,
     /* [previous][next][first][last][top][bottom][index][help] */
1328                               UNUSED void * desc)
1329   {
1330     if (! cptr)
1331       return SCPE_ARG;
1332     int n = atoi (cptr);
1333     if (n < 1 || n > N_PUN_UNITS_MAX)
1334       return SCPE_ARG;
1335     pun_dev . numunits = (uint32) n;
1336     return SCPE_OK;
1337   }
1338 
1339 static t_stat pun_show_device_name (UNUSED FILE * st, UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
1340                                     UNUSED int val, UNUSED const void * desc)
1341   {
1342     long n = PUN_UNIT_NUM (uptr);
1343     if (n < 0 || n >= N_PUN_UNITS_MAX)
1344       return SCPE_ARG;
1345     sim_printf("name     : %s", pun_state [n] . device_name);
1346     return SCPE_OK;
1347   }
1348 
1349 static t_stat pun_set_device_name (UNUSED UNIT * uptr, UNUSED int32 value,
     /* [previous][next][first][last][top][bottom][index][help] */
1350                                    UNUSED const char * cptr, UNUSED void * desc)
1351   {
1352     long n = PUN_UNIT_NUM (uptr);
1353     if (n < 0 || n >= N_PUN_UNITS_MAX)
1354       return SCPE_ARG;
1355     if (cptr)
1356       {
1357         strncpy (pun_state [n] . device_name, cptr, MAX_DEV_NAME_LEN - 1);
1358         pun_state [n] . device_name [MAX_DEV_NAME_LEN - 1] = 0;
1359       }
1360     else
1361       pun_state [n] . device_name [0] = 0;
1362     return SCPE_OK;
1363   }
1364 
1365 static t_stat pun_set_path (UNUSED UNIT * uptr, UNUSED int32 value,
     /* [previous][next][first][last][top][bottom][index][help] */
1366                             const UNUSED char * cptr, UNUSED void * desc)
1367   {
1368     if (! cptr)
1369       return SCPE_ARG;
1370 
1371     size_t len = strlen(cptr);
1372 
1373     // Verify that we don't exceed the maximum prefix
1374     // size ( -2 for the null terminator and a possible '/')
1375     if (len >= (sizeof(pun_path_prefix) - 2))
1376       return SCPE_ARG;
1377 
1378     strncpy(pun_path_prefix, cptr, sizeof(pun_path_prefix) - 1);
1379     if (len > 0)
1380       {
1381         if (pun_path_prefix[len - 1] != '/')
1382           {
1383             pun_path_prefix[len++] = '/';
1384             pun_path_prefix[len] = 0;
1385           }
1386       }
1387     return SCPE_OK;
1388   }
1389 
1390 static t_stat pun_show_path (UNUSED FILE * st, UNUSED UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
1391                              UNUSED int val, UNUSED const void * desc)
1392   {
1393     if (pun_path_prefix [0])
1394       {
1395         sim_printf("\rPath to card punch directories is \"%s\".\r\n", pun_path_prefix);
1396       }
1397     else
1398       {
1399         sim_printf("\rPath to card punch directories is unset.\r\n");
1400       }
1401     return SCPE_OK;
1402   }
1403 
1404 static t_stat pun_set_config (UNUSED UNIT *  uptr, UNUSED int32 value,
     /* [previous][next][first][last][top][bottom][index][help] */
1405                               const char * cptr, UNUSED void * desc)
1406   {
1407     int devUnitIdx           = (int) PUN_UNIT_NUM (uptr);
1408     pun_state_t * psp        = pun_state + devUnitIdx;
1409     config_state_t cfg_state = { NULL, NULL };
1410 
1411     for (;;)
1412       {
1413         int64_t v;
1414         int rc = cfg_parse (__func__, cptr, pun_config_list, & cfg_state, & v);
1415         if (rc == -1) // done
1416           break;
1417 
1418         if (rc == -2) // error
1419           {
1420             cfg_parse_done (& cfg_state);
1421             return SCPE_ARG;
1422           }
1423         const char * p = pun_config_list[rc].name;
1424 
1425         if (strcmp (p, "logcards") == 0)
1426           {
1427             psp->log_cards = v != 0;
1428             continue;
1429           }
1430 
1431         sim_warn ("error: pun_set_config: Invalid cfg_parse rc <%ld>\r\n",
1432                   (long) rc);
1433         cfg_parse_done (& cfg_state);
1434         return SCPE_ARG;
1435       } // process statements
1436     cfg_parse_done (& cfg_state);
1437     return SCPE_OK;
1438   }
1439 
1440 static t_stat pun_show_config (UNUSED FILE * st, UNUSED UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
1441                                UNUSED int  val, UNUSED const void * desc)
1442   {
1443     int devUnitIdx = (int) PUN_UNIT_NUM (uptr);
1444     pun_state_t * psp = pun_state + devUnitIdx;
1445     sim_msg ("logcards : %d", psp->log_cards);
1446     return SCPE_OK;
1447   }

/* [previous][next][first][last][top][bottom][index][help] */