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

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