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) - 1);
 942     state->raw_file_name[sizeof(state->raw_file_name) - 1] = '\0';
 943 
 944     create_punch_file(state);                           // Create spool file
 945 
 946     // Write cached cards to spool file
 947     CARD_CACHE_ENTRY *current_entry = state -> first_cached_card;
 948     while (current_entry != NULL)
 949       {
 950         write_punch_files (state, current_entry -> card_data, WORDS_PER_CARD, true);
 951         current_entry = current_entry->next_entry;
 952       }
 953 
 954     clear_card_cache(state);                            // Clear card cache
 955 
 956     return NoEvent;
 957   }
 958 
 959 static enum parse_event do_state_cache_card(enum parse_event event,
     /* [previous][next][first][last][top][bottom][index][help] */
 960                                             pun_state_t * state,
 961                                             word12 tally,
 962                                             word36 * card_buffer)
 963   {
 964     transition_state(event, state, CacheCard);
 965 
 966     save_card_in_cache(state, tally, card_buffer);      // Save card in cache
 967 
 968     return NoEvent;
 969   }
 970 
 971 static enum parse_event do_state_end_of_deck(enum parse_event event,
     /* [previous][next][first][last][top][bottom][index][help] */
 972                                              pun_state_t * state,
 973                                              word12 tally,
 974                                              word36 * card_buffer)
 975   {
 976     transition_state(event, state, EndOfDeck);
 977 
 978     save_card_in_cache(state, tally, card_buffer);      // Save card in cache
 979 
 980     return NoEvent;
 981   }
 982 
 983 static enum parse_event do_state_end_of_job(enum parse_event event,
     /* [previous][next][first][last][top][bottom][index][help] */
 984                                             pun_state_t * state,
 985                                             word12 tally,
 986                                             word36 * card_buffer)
 987   {
 988     transition_state(event, state, EndOfJob);
 989 
 990     // Write cached cards to spool file
 991     CARD_CACHE_ENTRY *current_entry = state -> first_cached_card;
 992     while (current_entry != NULL)
 993       {
 994         write_punch_files (state, current_entry -> card_data, WORDS_PER_CARD,
 995                 (current_entry -> next_entry == NULL));
 996         current_entry = current_entry->next_entry;
 997       }
 998 
 999     clear_card_cache(state);                                // Clear card cache
1000 
1001     write_punch_files (state, card_buffer, tally, true);    // Write card to spool file
1002 
1003     // Close punch files
1004     if (state -> punfile_raw >= 0)
1005       {
1006         close (state -> punfile_raw);
1007         state -> punfile_raw = -1;
1008       }
1009 
1010     return Done;
1011   }
1012 
1013 static void unexpected_event(enum parse_event event, pun_state_t * state)
     /* [previous][next][first][last][top][bottom][index][help] */
1014   {
1015     sim_warn("*** Punch: Unexpected event ");
1016     print_event(event);
1017 
1018     sim_warn(" in state ");
1019     print_state(state -> current_state);
1020 
1021     sim_warn("***\n");
1022   }
1023 
1024 static void parse_card(pun_state_t * state, word12 tally, word36 * card_buffer)
     /* [previous][next][first][last][top][bottom][index][help] */
1025   {
1026     enum parse_event event = Card;
1027 
1028     if (tally == WORDS_PER_CARD && memcmp (card_buffer, eodCard, sizeof (eodCard)) == 0)
1029       {
1030         event = EndOfDeckCard;
1031       }
1032 
1033     if (tally == WORDS_PER_CARD && memcmp (card_buffer, bannerCard, sizeof (bannerCard)) == 0)
1034       {
1035         event = BannerCard;
1036       }
1037 
1038     while (event != NoEvent)
1039       {
1040         enum parse_event current_event = event;
1041         event = NoEvent;
1042 
1043         switch (current_event)
1044           {
1045             case BannerCard:
1046               switch (state -> current_state)
1047                 {
1048                   case Idle:
1049                     event = do_state_starting_job(current_event, state, tally, card_buffer);
1050                     break;
1051 
1052                   case PunchGlyphLookup:
1053                     event = do_state_end_of_header(current_event, state, tally, card_buffer);
1054                     break;
1055 
1056                   case EndOfDeck:
1057                     event = do_state_end_of_job(current_event, state, tally, card_buffer);
1058                     break;
1059 
1060                   default:
1061                     unexpected_event(current_event, state);
1062                     break;
1063                 }
1064               break;
1065 
1066             case EndOfDeckCard:
1067               switch (state -> current_state)
1068                 {
1069                   case StartingJob:
1070                     event = do_state_end_of_deck(current_event, state, tally, card_buffer);
1071                     break;
1072 
1073                   case PunchGlyphLookup:
1074                     event = do_state_end_of_deck(current_event, state, tally, card_buffer);
1075                     break;
1076 
1077                   case EndOfHeader:
1078                     event = do_state_end_of_deck(current_event, state, tally, card_buffer);
1079                     break;
1080 
1081                   case CacheCard:
1082                     event = do_state_end_of_deck(current_event, state, tally, card_buffer);
1083                     break;
1084 
1085                   case EndOfDeck:
1086                     event = do_state_end_of_deck(current_event, state, tally, card_buffer);
1087                     break;
1088 
1089                   default:
1090                     unexpected_event(current_event, state);
1091                     break;
1092                 }
1093               break;
1094 
1095             case Card:
1096               switch (state -> current_state)
1097                 {
1098                   case StartingJob:
1099                     event = do_state_scan_card_for_glyphs(current_event, state, tally, card_buffer); //-V1037
1100                     break;
1101 
1102                   case PunchGlyphLookup:
1103                     event = do_state_scan_card_for_glyphs(current_event, state, tally, card_buffer);
1104                     break;
1105 
1106                   case EndOfHeader:
1107                     event = do_state_cache_card(current_event, state, tally, card_buffer); //-V1037
1108                     break;
1109 
1110                   case CacheCard:
1111                     event = do_state_cache_card(current_event, state, tally, card_buffer);
1112                     break;
1113 
1114                   case EndOfDeck:
1115                     event = do_state_cache_card(current_event, state, tally, card_buffer);
1116                     break;
1117 
1118                   default:
1119                     unexpected_event(current_event, state);
1120                     break;
1121                 }
1122               break;
1123 
1124             case Done:
1125               switch (state -> current_state)
1126                 {
1127                   case EndOfJob:
1128                     event = do_state_idle(current_event, state);
1129                     break;
1130 
1131                   default:
1132                     unexpected_event(current_event, state);
1133                     break;
1134                 }
1135               break;
1136 
1137             default:
1138               sim_warn("*** Error: Punch received unknown event!\n");
1139               break;
1140           }
1141       }
1142 
1143   }
1144 
1145 static int punWriteRecord (uint iomUnitIdx, uint chan)
     /* [previous][next][first][last][top][bottom][index][help] */
1146   {
1147     iom_chan_data_t * p = & iom_chan_data [iomUnitIdx] [chan];
1148     uint dev_code       = p->IDCW_DEV_CODE;
1149     uint ctlr_unit_idx  = get_ctlr_idx (iomUnitIdx, chan);
1150     uint devUnitIdx     = cables->urp_to_urd[ctlr_unit_idx][dev_code].unit_idx;
1151     UNIT * unitp        = & pun_unit [devUnitIdx];
1152     long pun_unit_num   = PUN_UNIT_NUM (unitp);
1153 
1154     p -> isRead = false;
1155     if (p -> DDCW_TALLY != WORDS_PER_CARD)
1156       {
1157         sim_warn ("%s expected tally of 27\n", __func__);
1158         p -> chanStatus = chanStatIncorrectDCW;
1159         p -> stati = 05001; //-V536  // BUG: arbitrary error code; config switch
1160         return -1;
1161       }
1162 
1163 //dcl 1 raw aligned,    /* raw column binary card image */
1164 //    2 col (1:80) bit (12) unal,                             /* 80 columns */
1165 //    2 pad bit (12) unal;
1166 
1167     // Copy from core to buffer
1168     word36 buffer [p -> DDCW_TALLY];
1169     uint wordsProcessed = 0;
1170     iom_indirect_data_service (iomUnitIdx, chan, buffer, & wordsProcessed, false);
1171     p->initiate         = false;
1172 
1173     if (pun_state [pun_unit_num] . log_cards)
1174       {
1175         log_card(p -> DDCW_TALLY, buffer);
1176       }
1177 
1178     parse_card( &pun_state [pun_unit_num], p -> DDCW_TALLY, buffer);
1179 
1180     p -> stati = 04000; //-V536
1181     return 0;
1182   }
1183 
1184 iom_cmd_rc_t pun_iom_cmd (uint iomUnitIdx, uint chan) {
     /* [previous][next][first][last][top][bottom][index][help] */
1185   iom_cmd_rc_t rc     = IOM_CMD_PROCEED;
1186   iom_chan_data_t * p = & iom_chan_data[iomUnitIdx][chan];
1187   uint dev_code       = p->IDCW_DEV_CODE;
1188 #if defined(TESTING)
1189   cpu_state_t * cpup  = _cpup;
1190 
1191   sim_debug (DBG_TRACE, & pun_dev, "%s: PUN %c%02o_%02o\n",
1192           __func__, iomChar (iomUnitIdx), chan, dev_code);
1193 #endif
1194 
1195   uint ctlr_unit_idx   = get_ctlr_idx (iomUnitIdx, chan);
1196   uint devUnitIdx      = cables->urp_to_urd[ctlr_unit_idx][dev_code].unit_idx;
1197   pun_state_t * statep = & pun_state[devUnitIdx];
1198 
1199   // IDCW?
1200   if (IS_IDCW (p)) {
1201     // IDCW
1202     statep->ioMode = punNoMode;
1203     switch (p->IDCW_DEV_CMD) {
1204       case 011: // CMD 011 Punch binary
1205         sim_debug (DBG_DEBUG, & pun_dev, "%s: Punch Binary\n", __func__);
1206         statep->ioMode = punWrBin;
1207         p->stati       = 04000;
1208         break;
1209 
1210       case 031: // CMD 031 Set Diagnostic Mode (load_mpc.pl1)
1211         sim_debug (DBG_DEBUG, & pun_dev, "%s: Set Diagnostic Mode\n", __func__);
1212         p->stati = 04000;
1213         break;
1214 
1215       case 040: // CMD 40 Reset status
1216         sim_debug (DBG_DEBUG, & pun_dev, "%s: Reset Status\n", __func__);
1217         p->stati  = 04000;
1218         p->isRead = false;
1219         break;
1220 
1221       default:
1222         if (p->IDCW_DEV_CMD != 051) // ignore bootload console probe
1223           sim_warn ("%s: PUN unrecognized device command  %02o\n", __func__, p->IDCW_DEV_CMD);
1224         p->stati      = 04501; // cmd reject, invalid opcode
1225         p->chanStatus = chanStatIncorrectDCW;
1226         return IOM_CMD_ERROR;
1227     } // switch IDCW_DEV_CMD
1228     sim_debug (DBG_DEBUG, & pun_dev, "%s: stati %04o\n", __func__, p->stati);
1229     return IOM_CMD_PROCEED;
1230   } // IDCW
1231 
1232   // Not IDCW; TDCW are captured in IOM, so must be IOTD, IOTP or IOTNP
1233   switch (statep->ioMode) {
1234     case punNoMode:
1235       //sim_printf ("%s: Unexpected IOTx\n", __func__);
1236       //sim_warn ("%s: Unexpected IOTx\n", __func__);
1237       //return IOM_CMD_ERROR;
1238       break;
1239 
1240     case punWrBin: {
1241         int rc = punWriteRecord (iomUnitIdx, chan);
1242         if (rc)
1243           return IOM_CMD_ERROR;
1244       }
1245       break;
1246 
1247     default:
1248       sim_warn ("%s: Unrecognized ioMode %d\n", __func__, statep->ioMode);
1249       return IOM_CMD_ERROR;
1250   }
1251   return rc;
1252 }
1253 
1254 static t_stat pun_show_nunits (UNUSED FILE * st, UNUSED UNIT * uptr, UNUSED int val,
     /* [previous][next][first][last][top][bottom][index][help] */
1255                                UNUSED const void * desc)
1256   {
1257     sim_printf("Number of PUN units in system is %d\n", pun_dev . numunits);
1258     return SCPE_OK;
1259   }
1260 
1261 static t_stat pun_set_nunits (UNUSED UNIT * uptr, UNUSED int32 value, const char * cptr,
     /* [previous][next][first][last][top][bottom][index][help] */
1262                               UNUSED void * desc)
1263   {
1264     if (! cptr)
1265       return SCPE_ARG;
1266     int n = atoi (cptr);
1267     if (n < 1 || n > N_PUN_UNITS_MAX)
1268       return SCPE_ARG;
1269     pun_dev . numunits = (uint32) n;
1270     return SCPE_OK;
1271   }
1272 
1273 static t_stat pun_show_device_name (UNUSED FILE * st, UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
1274                                     UNUSED int val, UNUSED const void * desc)
1275   {
1276     long n = PUN_UNIT_NUM (uptr);
1277     if (n < 0 || n >= N_PUN_UNITS_MAX)
1278       return SCPE_ARG;
1279     sim_printf("name     : %s", pun_state [n] . device_name);
1280     return SCPE_OK;
1281   }
1282 
1283 static t_stat pun_set_device_name (UNUSED UNIT * uptr, UNUSED int32 value,
     /* [previous][next][first][last][top][bottom][index][help] */
1284                                    UNUSED const char * cptr, UNUSED void * desc)
1285   {
1286     long n = PUN_UNIT_NUM (uptr);
1287     if (n < 0 || n >= N_PUN_UNITS_MAX)
1288       return SCPE_ARG;
1289     if (cptr)
1290       {
1291         strncpy (pun_state [n] . device_name, cptr, MAX_DEV_NAME_LEN - 1);
1292         pun_state [n] . device_name [MAX_DEV_NAME_LEN - 1] = 0;
1293       }
1294     else
1295       pun_state [n] . device_name [0] = 0;
1296     return SCPE_OK;
1297   }
1298 
1299 static t_stat pun_set_path (UNUSED UNIT * uptr, UNUSED int32 value,
     /* [previous][next][first][last][top][bottom][index][help] */
1300                             const UNUSED char * cptr, UNUSED void * desc)
1301   {
1302     if (! cptr)
1303       return SCPE_ARG;
1304 
1305     size_t len = strlen(cptr);
1306 
1307     // Verify that we don't exceed the maximum prefix
1308     // size ( -2 for the null terminator and a possible '/')
1309     if (len >= (sizeof(pun_path_prefix) - 2))
1310       return SCPE_ARG;
1311 
1312     strncpy(pun_path_prefix, cptr, sizeof(pun_path_prefix) - 1);
1313     if (len > 0)
1314       {
1315         if (pun_path_prefix[len - 1] != '/')
1316           {
1317             pun_path_prefix[len++] = '/';
1318             pun_path_prefix[len] = 0;
1319           }
1320       }
1321     return SCPE_OK;
1322   }
1323 
1324 static t_stat pun_show_path (UNUSED FILE * st, UNUSED UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
1325                              UNUSED int val, UNUSED const void * desc)
1326   {
1327     if (pun_path_prefix [0])
1328       {
1329         sim_printf("\rPath to card punch directories is \"%s\".\r\n", pun_path_prefix);
1330       }
1331     else
1332       {
1333         sim_printf("\rPath to card punch directories is unset.\r\n");
1334       }
1335     return SCPE_OK;
1336   }
1337 
1338 static t_stat pun_set_config (UNUSED UNIT *  uptr, UNUSED int32 value,
     /* [previous][next][first][last][top][bottom][index][help] */
1339                               const char * cptr, UNUSED void * desc)
1340   {
1341     int devUnitIdx           = (int) PUN_UNIT_NUM (uptr);
1342     pun_state_t * psp        = pun_state + devUnitIdx;
1343     config_state_t cfg_state = { NULL, NULL };
1344 
1345     for (;;)
1346       {
1347         int64_t v;
1348         int rc = cfg_parse (__func__, cptr, pun_config_list, & cfg_state, & v);
1349         if (rc == -1) // done
1350           break;
1351 
1352         if (rc == -2) // error
1353           {
1354             cfg_parse_done (& cfg_state);
1355             return SCPE_ARG;
1356           }
1357         const char * p = pun_config_list[rc].name;
1358 
1359         if (strcmp (p, "logcards") == 0)
1360           {
1361             psp->log_cards = v != 0;
1362             continue;
1363           }
1364 
1365         sim_warn ("error: pun_set_config: Invalid cfg_parse rc <%ld>\n",
1366                   (long) rc);
1367         cfg_parse_done (& cfg_state);
1368         return SCPE_ARG;
1369       } // process statements
1370     cfg_parse_done (& cfg_state);
1371     return SCPE_OK;
1372   }
1373 
1374 static t_stat pun_show_config (UNUSED FILE * st, UNUSED UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
1375                                UNUSED int  val, UNUSED const void * desc)
1376   {
1377     int devUnitIdx = (int) PUN_UNIT_NUM (uptr);
1378     pun_state_t * psp = pun_state + devUnitIdx;
1379     sim_msg ("logcards : %d", psp->log_cards);
1380     return SCPE_OK;
1381   }

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