1 /****^  ***********************************************************
   2         *                                                         *
   3         * Copyright, (C) Honeywell Bull Inc., 1987                *
   4         *                                                         *
   5         * Copyright, (C) Honeywell Information Systems Inc., 1982 *
   6         *                                                         *
   7         * Copyright (c) 1972 by Massachusetts Institute of        *
   8         * Technology and Honeywell Information Systems, Inc.      *
   9         *                                                         *
  10         *********************************************************** */
  11 
  12 
  13 /* format: style4,delnl,insnl,^ifthendo */
  14 polled_vip_mpx:
  15      proc;
  16 
  17 /* This procedure contains the non-privileged entries of the polled
  18    vip multiplexer.  These entries can be invoked at interrupt time
  19    and therefore must be wired.
  20 
  21    Coded December 1978 by J. Stern
  22 */
  23 /* Modified August 1982 by Robert Coren to handle "MASKED" interrupt */
  24 
  25 
  26 
  27 /* Parameters */
  28 
  29 dcl  pm_chain_ptr ptr;                                      /* ptr to write chain (input) */
  30 dcl  pm_code fixed bin (35);                                /* error code (output) */
  31 dcl  pm_infop ptr;                                          /* ptr to control order info structure (input) */
  32 dcl  pm_int_data bit (72) aligned;                          /* interrupt data (input) */
  33 dcl  pm_int_type fixed bin;                                 /* interrupt type (input) */
  34 dcl  pm_mclp ptr;                                           /* ptr to modes change list */
  35 dcl  pm_modes char (*);                                     /* mode string (output) */
  36 dcl  pm_more_input bit (1) aligned;                         /* ON if more input available after read (Output) */
  37 dcl  pm_order char (*);                                     /* control order name (input) */
  38 dcl  pm_pvmdp ptr;                                          /* ptr to multiplexer data base (input) */
  39 dcl  pm_subchan fixed bin;                                  /* subchannel number (input) */
  40 
  41 
  42 /* Automatic */
  43 
  44 dcl  adr bit (9);
  45 dcl  bmp ptr;
  46 dcl  chain_ptr ptr;
  47 dcl  code fixed bin (35);
  48 dcl  end_chain bit (1);
  49 dcl  eop_sw bit (1);
  50 dcl  ff_sent bit (1);
  51 dcl  found_text bit (1);
  52 dcl  1 frame_header aligned like frame_header_template;
  53 dcl  1 frame_trailer aligned like frame_trailer_template;
  54 dcl  headp ptr;
  55 dcl  i fixed bin;
  56 dcl  infop ptr;
  57 dcl  input_framep ptr;
  58 dcl  int_data bit (72) aligned;
  59 dcl  int_type fixed bin;
  60 
  61 dcl  1 lc_info aligned,                                     /* info structure for line control order */
  62        2 type fixed bin (17) unal,
  63        2 arg1 fixed bin (17) unal,
  64        2 station_mask (0:35) bit (1) unal;
  65 
  66 dcl  leftover_chain_ptr ptr;
  67 
  68 dcl  1 ls_info aligned,                                     /* info structure for line status interrupt */
  69        2 type fixed bin (17) unal,
  70        2 adr fixed bin (9) unsigned unal,
  71        2 sta char (1) unal,
  72        2 unused bit (36);
  73 
  74 dcl  meter_ptr ptr;
  75 dcl  nchars fixed bin;
  76 dcl  nmsg fixed bin;
  77 dcl  order char (32);
  78 dcl  output_restarted bit (1);
  79 dcl  prev_blockp ptr;
  80 dcl  saved_write_chan fixed bin;
  81 dcl  subchan fixed bin;
  82 dcl  tailp ptr;
  83 dcl  text_lrc bit (9);
  84 dcl  ttybp ptr;
  85 dcl  xflag bit (1);
  86 
  87 
  88 /* Based */
  89 
  90 dcl  1 abort_info aligned based (infop),
  91        2 resetwrite bit (1) unal,
  92        2 resetread bit (1) unal;
  93 
  94 dcl  1 break_msg based (bmp),
  95        2 soh char (1),
  96        2 adr char (1),
  97        2 sta char (1),
  98        2 fc1 char (1),
  99        2 fc2 char (1),
 100        2 stx char (1),
 101        2 text char (6);
 102 
 103 dcl  1 input_frame aligned based (input_framep),
 104        2 msg (nmsg) unal,
 105          3 soh char (1),
 106          3 adr char (1),
 107          3 sta char (1),
 108          3 fc1 char (1),
 109          3 fc2 char (1),
 110          3 stx char (1),
 111          3 etx char (1),
 112          3 lrc char (1),
 113        2 eot char (1) unal;
 114 
 115 dcl  1 write_status_info aligned based (infop),
 116        2 ev_chan fixed bin (71),
 117        2 output_pending bit (1);
 118 
 119 
 120 /* Internal static */
 121 
 122 dcl  et_action_not_performed fixed bin (35) int static;
 123 dcl  et_bad_mode fixed bin (35) int static;
 124 dcl  et_invalid_state fixed bin (35) int static;
 125 dcl  et_noalloc fixed bin (35) int static;
 126 dcl  et_undefined_order_request fixed bin (35) int static;
 127 dcl  et_unimplemented_version fixed bin (35) int static;
 128 
 129 /* External static */
 130 
 131 dcl  error_table_$action_not_performed fixed bin (35) ext static;
 132 dcl  error_table_$bad_mode fixed bin (35) ext static;
 133 dcl  error_table_$invalid_state fixed bin (35) ext static;
 134 dcl  error_table_$noalloc fixed bin (35) ext static;
 135 dcl  error_table_$undefined_order_request fixed bin (35) ext static;
 136 dcl  error_table_$unimplemented_version fixed bin (35) ext static;
 137 dcl  tty_buf$ ext static;
 138 
 139 
 140 /* Builtins */
 141 
 142 dcl  (addr, bin, bit, bool, divide, hbound, lbound, length, max, min, mod, null, ptr, rel, string, substr, translate,
 143      unspec, verify) builtin;
 144 
 145 
 146 /* Entries */
 147 
 148 dcl  pxss$ring_0_wakeup entry (bit (36) aligned, fixed bin (71), fixed bin (71), fixed bin (35));
 149 dcl  syserr entry options(variable);
 150 dcl  wire_proc$wire_me entry;
 151 
 152 
 153 /* Constants */
 154 
 155 dcl  (
 156      ACK char (1) init ("^F"),
 157      ADR_MASK bit (9) init ("740"b3),
 158      AWAIT_FIRST_RESPONSE fixed bin init (6),
 159      COMMON_LRC bit (9) init ("001"b3),
 160      DISPLAY bit (9) init ("140"b3),
 161      ETB char (1) init ("^W"),
 162      ETX char (1) init ("^C"),
 163      ETX_XOR_ETB bit (9) init ("024"b3),
 164      FF char (1) init ("^L"),
 165      NAK char (1) init ("^U"),
 166      NL char (1) init ("
 167 "),
 168      NUL char (1) init ("^@"),
 169      POLL bit (9) init ("040"b3),
 170      PRINTER bit (9) init ("150"b3),
 171      PRT char (1) init ("^Z"),
 172      RESTART_CHARS char (4) init ("@^M
 173 ^L"),                                                       /* @ CR LF FF */
 174      SELECT bit (9) init ("100"b3),
 175      SOH char (1) init ("^A"),
 176      STATION_POLL fixed bin init (1),
 177      WRITE_ABORT bit (2) init ("10"b),
 178      XOR bit (4) init ("0110"b)
 179      ) internal static options (constant);
 180 
 181 dcl  1 frame_header_template aligned int static options (constant),
 182        2 select_msg unal,
 183          3 syn (4) char (1) init ((4) (1)"^V"),
 184          3 soh char (1) init ("^A"),
 185          3 adr char (1),
 186          3 sta char (1) init ("^@"),
 187          3 fc1 char (1) init (" "),
 188          3 fc2 char (1) init (" "),
 189          3 stx char (1) init ("^B"),
 190          3 etx char (1) init ("^C"),
 191          3 lrc char (1),
 192        2 text_msg unal,
 193          3 syn (4) char (1) init ((4) (1)"^V"),
 194          3 soh char (1) init ("^A"),
 195          3 adr char (1),
 196          3 sta char (1) init ("^@"),
 197          3 fc1 char (1) init (" "),
 198          3 fc2 char (1) init (" "),
 199          3 stx char (1) init ("^B"),
 200          3 ff char (1) init ("^L"),
 201          3 nul char (1) init ("^@");
 202 
 203 dcl  1 frame_trailer_template aligned int static options (constant),
 204        2 text_msg unal,
 205          3 etx char (1) init ("^C"),
 206          3 lrc char (1),
 207        2 poll_msg unal,
 208          3 syn (4) char (1) init ((4) (1)"^V"),
 209          3 soh char (1) init ("^A"),
 210          3 adr char (1),
 211          3 sta char (1) init ("^@"),
 212          3 fc1 char (1) init (" "),
 213          3 fc2 char (1) init (" "),
 214          3 stx char (1) init ("^B"),
 215          3 etx char (1) init ("^C"),
 216          3 lrc char (1),
 217        2 end_frame unal,
 218          3 syn (4) char (1) init ((4) (1)"^V"),
 219          3 eof char (1) init ("^@");                        /* gets converted to EOT */
 220 ^L
 221 %include polled_vip_mpx_data;
 222 ^L
 223 %include polled_vip_load_info;
 224 ^L
 225 %include mcs_interrupt_info;
 226 ^L
 227 %include lct;
 228 ^L
 229 %include tty_buffer_block;
 230 ^L
 231 %include mcs_modes_change_list;
 232 ^L
 233 %include channel_manager_dcls;
 234 ^L
 235 %include tty_space_man_dcls;
 236 ^L
 237 %include polled_vip_mpx_meters;
 238 ^L
 239 %include pvip_subchan_meters;
 240 ^L
 241 %include get_comm_meters_info;
 242 ^L
 243 /* Entry to perform control orders. */
 244 
 245 control:
 246      entry (pm_pvmdp, pm_subchan, pm_order, pm_infop, pm_code);
 247 
 248           call setup_subchan;
 249           order = pm_order;
 250           pm_code = 0;
 251 
 252           if order = "listen"                               /* listen for subchan to dial up */
 253           then do;
 254                pvste.listen = "1"b;
 255                if pvmd.mpx_started & ^pvste.dialed & pvste.slave
 256                                                             /* have green light for dialups */
 257                then call signal_dialup;
 258           end;
 259 
 260           else if order = "hangup"                          /* pull the plug on this subchan */
 261           then do;
 262                pvste.listen, pvste.dialed = "0"b;
 263                call free_write_chain;
 264                call channel_manager$interrupt ((pvste.devx), HANGUP, ""b);
 265           end;
 266 
 267           else if order = "wru"                             /* who are you */
 268           then call channel_manager$interrupt ((pvste.devx), WRU_TIMEOUT, ""b);
 269                                                             /* no answerback, simulate timeout */
 270 
 271           else if order = "abort"
 272           then do;
 273                infop = pm_infop;
 274                if abort_info.resetwrite
 275                then call free_write_chain;
 276           end;
 277 
 278           else if order = "write_status"
 279           then do;
 280                infop = pm_infop;
 281                if (pvste.write_chain ^= 0) | (pvmd.write_chan = subchan)
 282                then write_status_info.output_pending = "1"b;
 283                else if pvste.printer & pvste.hold_output    /* write data sent, now waiting for line status */
 284                then write_status_info.output_pending = "1"b;/* in effect, we still have write data */
 285                else write_status_info.output_pending = "0"b;/* nope, no write data */
 286           end;
 287 
 288           else if order = "copy_meters"
 289           then pvste.saved_meters_ptr -> pvip_subchan_meters = pvste.meters;
 290 
 291           else if order = "get_meters"
 292           then do;
 293                infop = pm_infop;
 294                if infop -> get_comm_meters_info.version ^= GET_COMM_METERS_INFO_VERSION_1
 295                then pm_code = et_unimplemented_version;
 296                else do;
 297                     meter_ptr = infop -> get_comm_meters_info.parent_ptr;
 298                     if meter_ptr ^= null ()
 299                     then if meter_ptr -> pvip_subchan_meter_struc.version ^= PVIP_SUBCHAN_METERS_VERSION_1
 300                          then pm_code = et_unimplemented_version;
 301                          else do;
 302                               meter_ptr -> pvip_subchan_meter_struc.current_meters = pvste.meters;
 303                               meter_ptr -> pvip_subchan_meter_struc.saved_meters =
 304                                    pvste.saved_meters_ptr -> pvip_subchan_meters;
 305                               meter_ptr -> pvip_subchan_meter_struc.printer = pvste.printer;
 306                          end;
 307                end;
 308           end;
 309 
 310           else pm_code = et_undefined_order_request;
 311 
 312           return;
 313 
 314 
 315 
 316 /* Entry to validate a proposed mode setting */
 317 
 318 check_modes:
 319      entry (pm_pvmdp, pm_subchan, pm_mclp, pm_code);
 320 
 321           call setup_subchan;
 322 
 323           mclp = pm_mclp;
 324           do i = 1 to mcl.n_entries;
 325                mclep = addr (mcl.entries (i));
 326                mcle.mpx_mode = (mcle.mode_name = "hndlquit");
 327           end;
 328 
 329           pm_code = 0;
 330           return;
 331 
 332 
 333 
 334 /* Entry to set modes ON or OFF */
 335 
 336 set_modes:
 337      entry (pm_pvmdp, pm_subchan, pm_mclp, pm_code);
 338 
 339           call setup_subchan;
 340 
 341           pm_code = 0;
 342           mclp = pm_mclp;
 343           if mcl.init                                       /* all modes off */
 344           then pvste.hndlquit = "0"b;
 345 
 346           do i = 1 to mcl.n_entries;
 347                mclep = addr (mcl.entries (i));
 348                if mcle.mpx_mode                             /* this is a mode we want to set */
 349                then if mcle.mode_name = "hndlquit"
 350                     then pvste.hndlquit = mcle.mode_switch;
 351                     else do;                                /* should never happen, but ... */
 352                          mcle.error = "1"b;
 353                          pm_code = et_bad_mode;
 354                     end;
 355           end;
 356 
 357           return;
 358 
 359 
 360 
 361 /* Entry to obtain multiplexer-specific modes */
 362 
 363 get_modes:
 364      entry (pm_pvmdp, pm_subchan, pm_modes, pm_code);
 365 
 366           call setup_subchan;
 367           pm_modes = "";
 368 
 369           pm_code = 0;
 370           return;
 371 
 372 
 373 
 374 /* Entry to read input from a specified subchannel */
 375 
 376 read:
 377      entry (pm_pvmdp, pm_subchan, pm_chain_ptr, pm_more_input, pm_code);
 378 
 379           pm_chain_ptr = null;                              /* we never hold input */
 380           pm_more_input = "0"b;
 381           pm_code = 0;
 382           return;
 383 
 384 
 385 /* Entry to write output to a specified subchannel */
 386 
 387 write:
 388      entry (pm_pvmdp, pm_subchan, pm_chain_ptr, pm_code);
 389 
 390           ttybp = addr (tty_buf$);
 391           call setup_subchan;
 392           chain_ptr = pm_chain_ptr;
 393 
 394           if ^pvste.dialed
 395           then do;
 396                call tty_space_man$free_chain ((pvste.devx), OUTPUT, chain_ptr);
 397                pm_chain_ptr = null;
 398                pm_code = 0;
 399                return;
 400           end;
 401           if (pvste.write_chain ^= 0) | pvste.hold_output | (pvmd.write_chan = subchan)
 402                                                             /* can't take any output now */
 403           then do;                                          /* so give it all back */
 404                pm_code = 0;
 405                return;
 406           end;
 407 
 408           nchars = 0;
 409           blockp = chain_ptr;
 410           prev_blockp = null;
 411           end_chain = "0"b;                                 /* see how many chars in this write chain */
 412           do while (^end_chain);
 413                nchars = nchars + buffer.tally;
 414                if nchars > pvmd.max_text_len | buffer.next = 0
 415                then end_chain = "1"b;
 416                else do;
 417                     prev_blockp = blockp;
 418                     blockp = ptr (ttybp, buffer.next);
 419                end;
 420           end;
 421 
 422           if nchars > pvmd.max_text_len                     /* more than maximum text length */
 423           then do;                                          /* take only part of the chain */
 424                tailp = prev_blockp;
 425                tailp -> buffer.next = 0;                    /* break chain here */
 426                leftover_chain_ptr = blockp;                 /* we'll give back the rest */
 427           end;
 428           else do;
 429                tailp = blockp;
 430                leftover_chain_ptr = null;
 431           end;
 432 
 433           eop_sw = tailp -> buffer.end_of_page;             /* get end-of-page indicator */
 434           tailp -> buffer.end_of_page = "0"b;               /* turn it off in the buffer */
 435 
 436 /* Construct a standard VIP data frame consisting of:
 437    (1) select message
 438    (2) text message
 439    (3) poll message
 440    (4) EOT
 441 
 442    The write chain we were given constitutes the body of the text
 443    message.  A small buffer will be allocated to hold that part of
 444    the frame which precedes the text.  The last text buffer should
 445    always contain enoug unused space to accomodate all post-text
 446    control data.
 447 */
 448 
 449           call tty_space_man$get_buffer ((pvste.devx), 16, OUTPUT, headp);
 450           if headp = null
 451           then go to noalloc;
 452           headp -> buffer.next = bin (rel (chain_ptr), 18); /* thread new buffer on head of chain */
 453 
 454           call get_frame_header (nchars);
 455           substr (string (headp -> buffer.chars), 1, nchars) = substr (string (frame_header), 1, nchars);
 456                                                             /* put frame header in buffer */
 457           headp -> buffer.tally = nchars;
 458 
 459           blockp = chain_ptr;                               /* compute lrc for actual text buffers */
 460           end_chain = "0"b;
 461           do while (^end_chain);
 462                call verify_text;                            /* get rid of any illegal chars */
 463                text_lrc = bool (get_buffer_lrc (), text_lrc, XOR);
 464                if buffer.next = 0
 465                then end_chain = "1"b;
 466                else blockp = ptr (ttybp, buffer.next);
 467           end;
 468 
 469           nchars = (bin (tailp -> buffer.size_code, 3) + 1) * 16;
 470                                                             /* get size of last buffer (words) */
 471           nchars = (nchars - 1) * 4;                        /* get max number of data chars */
 472           i = bin (tailp -> buffer.tally, 9);
 473           nchars = nchars - i;                              /* get room left in buffer */
 474           nchars = min (nchars, length (string (frame_trailer)));
 475                                                             /* need only enough room to hold frame trailer */
 476           call get_frame_trailer;
 477           if pvmd.etb_mode & ^pvste.printer & ^(eop_sw | tailp -> buffer.break)
 478           then do;                                          /* not at end of user msg, use ETB instead of ETX */
 479                frame_trailer.text_msg.etx = ETB;
 480                unspec (frame_trailer.text_msg.lrc) = bool (unspec (frame_trailer.text_msg.lrc), ETX_XOR_ETB, XOR);
 481           end;
 482 
 483           if nchars > 0                                     /* have some room in last buffer, use it */
 484           then substr (string (tailp -> buffer.chars), i + 1, nchars) = substr (string (frame_trailer), 1, nchars);
 485           tailp -> buffer.tally = i + nchars;               /* update buffer tally for added chars */
 486 
 487           nchars = length (string (frame_trailer)) - nchars;/* get length of uncopied part of frame trailer */
 488           if nchars > 0                                     /* could not fit everything in last buffer */
 489           then do;                                          /* so we need another buffer */
 490                call tty_space_man$get_buffer ((pvste.devx), 16, OUTPUT, blockp);
 491                if blockp = null
 492                then do;
 493                     tailp -> buffer.tally = i;              /* reset tally of last buffer */
 494                     go to noalloc;
 495                end;
 496 
 497                tailp -> buffer.next = bin (rel (blockp), 18);
 498                                                             /* thread in new buffer */
 499                tailp = blockp;
 500                tailp -> buffer.next = 0;
 501 
 502                i = length (string (frame_trailer)) - nchars + 1;
 503                                                             /* get index of first uncopied char */
 504                substr (string (tailp -> buffer.chars), 1, nchars) = substr (string (frame_trailer), i, nchars);
 505                tailp -> buffer.tally = nchars;
 506           end;
 507 
 508           pvste.eop = "0"b;                                 /* have put FF in this frame if needed */
 509           headp -> buffer.end_of_page = eop_sw;
 510           pvste.write_chain = bin (rel (headp), 18);
 511           if pvmd.write_chan = 0
 512           then call write_frame (code);
 513 
 514           pm_chain_ptr = leftover_chain_ptr;
 515           pm_code = 0;
 516           return;
 517 
 518 noalloc:
 519           if headp ^= null
 520           then call tty_space_man$free_buffer ((pvste.devx), OUTPUT, headp);
 521           if leftover_chain_ptr = null
 522           then tailp -> buffer.next = 0;
 523           else tailp -> buffer.next = bin (rel (leftover_chain_ptr), 18);
 524           tailp -> buffer.end_of_page = eop_sw;
 525           pm_code = et_noalloc;
 526           return;
 527 
 528 
 529 
 530 /* Entry to process interrupts */
 531 
 532 interrupt:
 533      entry (pm_pvmdp, pm_int_type, pm_int_data);
 534 
 535           ttybp = addr (tty_buf$);
 536           pvmdp = pm_pvmdp;
 537           int_type = pm_int_type;
 538           int_data = pm_int_data;
 539 
 540           if int_type < lbound (INTERRUPT, 1) | int_type > hbound (INTERRUPT, 1)
 541           then return;
 542           go to INTERRUPT (int_type);
 543 
 544 
 545 INTERRUPT (1):                                              /* DIALUP - major channel has dialed up */
 546           pvmd.dialup_info = int_data;
 547           pvmd.mpx_loading = "0"b;                          /* indicate multiplexer bootload complete */
 548           pvmd.mpx_loaded = "1"b;
 549           call pxss$ring_0_wakeup (pvmd.load_proc_id, pvmd.load_ev_chan, PV_MPX_UP, code);
 550           return;
 551 
 552 
 553 INTERRUPT (2):                                              /* HANGUP - major channel has hung up */
 554           call crash_mpx;                                   /* it's all over */
 555           call pxss$ring_0_wakeup (pvmd.load_proc_id, pvmd.load_ev_chan, PV_MPX_DOWN, code);
 556           return;
 557 
 558 
 559 INTERRUPT (3):                                              /* CRASH - parent multiplexer has died */
 560           call crash_mpx;                                   /* simulate a crash */
 561           return;
 562 
 563 
 564 INTERRUPT (4):                                              /* SEND OUTPUT - it's safe to write next output frame now */
 565           if ^pvmd.mpx_loaded
 566           then return;
 567 
 568           pvmd.send_output = "1"b;
 569           if pvmd.writep ^= null                            /* we're in the middle of writing a frame */
 570           then do;
 571                call write_bchain (code);
 572                if code = 0
 573                then return;
 574           end;
 575 
 576           saved_write_chan = pvmd.write_chan;               /* remember channel for which output just completed */
 577           pvmd.write_chan = 0;
 578           call send_next_frame (saved_write_chan);          /* write next output frame */
 579 
 580           if saved_write_chan ^= 0                          /* we were waiting for output completion */
 581           then do;                                          /* request subchannnel to send more output now */
 582                pvstep = addr (pvmd.subchan_table (saved_write_chan));
 583                if pvste.pgofs > 0                           /* bad status on previous write */
 584                then do;
 585                     pvste.writes = pvste.writes + 1;        /* update frames written */
 586                     if pvste.writes > pvste.pgofs           /* wrote a frame without bad status */
 587                     then pvste.pgofs, pvste.writes = 0;     /* assume full recovery */
 588                end;
 589 
 590                if pvste.dialed & ^pvste.hold_output         /* ready for more output now */
 591                then call channel_manager$interrupt ((pvste.devx), SEND_OUTPUT, ""b);
 592           end;
 593 
 594           return;
 595 
 596 
 597 INTERRUPT (5):                                              /* INPUT AVAILABLE - ignore */
 598           return;
 599 
 600 
 601 INTERRUPT (6):                                              /* ACCEPT INPUT - process an input frame */
 602           if ^pvmd.mpx_loaded
 603           then return;
 604 
 605           unspec (rtx_info) = int_data;
 606           headp = ptr (ttybp, rtx_info.chain_head);
 607           input_framep = addr (headp -> buffer.chars);
 608 
 609 /* If the first message of the input frame is a poll message, then
 610    we can determine the station address from the poll address.
 611    Otherwise, the station address must be retrieved from the address
 612    field of the text message.  The input frame may contain one or two
 613    status messages before the text message.  Status messages are ignored.
 614 */
 615 
 616           i = 2;                                            /* start text search at msg 2 */
 617           adr = unspec (input_frame.msg (1).adr);           /* get first message address */
 618           if (adr & ADR_MASK) ^= POLL                       /* it's not a poll msg */
 619           then do;
 620                adr = ""b;                                   /* don't know station addr yet */
 621                i = 1;                                       /* start text search at msg 1 */
 622           end;
 623 
 624           found_text = "0"b;                                /* find the text message */
 625           do i = i to 4 while (^found_text);
 626                if input_frame.msg (i).soh ^= SOH            /* no more good messages */
 627                then go to discard_input;
 628                if input_frame.msg (i).sta = NUL             /* this is a text message */
 629                then do;
 630                     found_text = "1"b;
 631                     if adr = ""b
 632                     then adr = unspec (input_frame.msg (i).adr);
 633                     nmsg = i;                               /* remember number of messages */
 634                end;
 635           end;
 636           if ^found_text
 637           then do;
 638 discard_input:
 639                call tty_space_man$free_chain (pvmd.devx, INPUT, headp);
 640                return;
 641           end;
 642 
 643           i = bin (substr (adr, 5, 5), 5);                  /* get station address */
 644           subchan = pvmd.station_to_subchan (i).display;    /* get display subchan for station */
 645           if subchan = 0                                    /* not configured */
 646           then go to discard_input;
 647           pvstep = addr (pvmd.subchan_table (subchan));
 648           if ^pvste.dialed
 649           then do;
 650                if pvmd.mpx_started & pvste.listen & ^pvste.slave
 651                then call signal_dialup;
 652                go to discard_input;
 653           end;
 654 
 655           ff_sent = "0"b;
 656           output_restarted = "0"b;
 657           xflag = (substr (pvste.name, 1, 1) = "x");
 658 
 659 /* Check for quit indication */
 660 
 661           if ^xflag & (input_frame.msg (nmsg).fc1 = pvmd.quit)
 662           then do;
 663 quit:
 664                call channel_manager$interrupt ((pvste.devx), QUIT, ""b);
 665                if pvste.hndlquit                            /* discard any write data */
 666                then do;
 667                     if pvste.hold_output
 668                     then do;
 669                          pvste.hold_output = "0"b;          /* unblock output */
 670                          pvste.eop = "1"b;                  /* clear screen on next output */
 671                     end;
 672                     call free_write_chain;
 673                end;
 674                go to discard_input;
 675           end;
 676 
 677           if ^xflag & pvmd.gcos_break
 678           then do;
 679                bmp = addr (input_frame.msg (nmsg));         /* get break msg ptr */
 680                if headp -> buffer.next = 0                  /* whole frame in one buffer */
 681                then if bin (headp -> buffer.tally) = length (string (input_frame)) + 6
 682                     then if break_msg.text = "$*$BRK"       /* isn't that cute */
 683                          then go to quit;
 684           end;
 685 
 686 /* Check if output should be restarted */
 687 
 688           if pvste.hold_output                              /* we've been waiting for this */
 689           then do;
 690                pvste.hold_output = "0"b;                    /* don't hold back output any longer */
 691                pvste.eop = "1"b;                            /* put FF in front of next output */
 692                call channel_manager$interrupt ((pvste.devx), SEND_OUTPUT, ""b);
 693                if pvste.eop                                 /* still ON if no output happenned */
 694                then call write_ff (ff_sent);                /* so we should echo the formfeed */
 695                output_restarted = "1"b;
 696           end;
 697 
 698 /* Check for formfeed function code */
 699 
 700           if ^xflag & (input_frame.msg (nmsg).fc1 = pvmd.formfeed)
 701           then do;
 702                if output_restarted                          /* FF was for this purpose only */
 703                then go to discard_input;
 704                call write_ff (ff_sent);                     /* echo the formfeed */
 705                if headp -> buffer.next ^= 0                 /* discard all but first buffer */
 706                then call tty_space_man$free_chain (pvmd.devx, INPUT, ptr (ttybp, headp -> buffer.next));
 707                blockp = headp;
 708                buffer.next = 0;
 709                buffer.chars (0) = FF;                       /* put formfeed char in buffer */
 710                unspec (buffer.tally) = bit (bin (1, 9), 9);
 711                rtx_info.input_count = 1;
 712                rtx_info.formfeed_present = "1"b;
 713                rtx_info.break_char = "0"b;
 714           end;
 715 
 716 /* Only actual text from the text message is retained.  The rest of the
 717    input frame is discarded.  To accomplish this, the first and last
 718    buffers of the input frame are adjusted accordingly.
 719 */
 720 
 721           else do;
 722                i = length (string (input_frame)) - 3;       /* get input frame header length */
 723                nchars = bin (headp -> buffer.tally, 9) - i; /* shrink first buffer */
 724                if nchars < 3                                /* must be at least ETX, LRC, EOT left */
 725                then go to discard_input;
 726                if nchars = 3
 727                then if output_restarted | pvmd.omit_nl | xflag
 728                     then go to discard_input;               /* drop empty input msg */
 729 
 730                substr (string (headp -> buffer.chars), 1, nchars) =
 731                     substr (string (headp -> buffer.chars), i + 1, nchars);
 732                unspec (headp -> buffer.tally) = bit (bin (nchars, 9), 9);
 733 
 734                if output_restarted & (headp -> buffer.next = 0)
 735                then do;
 736                     if verify (substr (string (headp -> buffer.chars), 1, nchars - 3), RESTART_CHARS) = 0
 737                     then go to discard_input;               /* just wanted to restart output, not really input */
 738                end;
 739 
 740                prev_blockp = null;
 741                blockp = headp;
 742                end_chain = "0"b;
 743                do while (^end_chain);
 744                     if buffer.next = 0
 745                     then end_chain = "1"b;
 746                     else do;
 747                          prev_blockp = blockp;
 748                          blockp = ptr (ttybp, buffer.next);
 749                     end;
 750                end;
 751 
 752                if pvmd.omit_nl | xflag                      /* we can strip last 3 chars (ETX, LRC, EOT) */
 753                then i = 3;
 754                else if buffer.chars (nchars - 3) = ETX      /* end of text msg (not ETB) */
 755                then i = 2;                                  /* change ETX to NL and toss last 2 chars */
 756                else i = 3;
 757                nchars = buffer.tally;                       /* get tally of last buffer */
 758                if nchars <= i                               /* we can throw away the whole buffer */
 759                then do;
 760                     call tty_space_man$free_buffer ((pvste.devx), INPUT, blockp);
 761                     blockp = prev_blockp;
 762                     buffer.next = 0;
 763                     nchars = buffer.tally + nchars;
 764                end;
 765                nchars = nchars - i;                         /* strip final 2 or 3 chars (ETX, LRC, EOT) */
 766                unspec (buffer.tally) = bit (bin (nchars, 9), 9);
 767                if i = 2
 768                then buffer.chars (nchars - 1) = NL;         /* change final ETX to NL */
 769 
 770                rtx_info.break_char = "1"b;
 771                rtx_info.formfeed_present = "0"b;
 772                rtx_info.input_count = rtx_info.input_count + 1 - length (string (input_frame));
 773                                                             /* account for control data removal */
 774           end;
 775 
 776           rtx_info.output_in_fnp = "0"b;
 777           if ff_sent
 778           then rtx_info.output_in_ring_0 = "0"b;            /* formfeed echo doesn't count */
 779           else rtx_info.output_in_ring_0 = (pvste.write_chain ^= 0) | (pvmd.write_chan = subchan);
 780           rtx_info.chain_tail = rel (blockp);
 781           call channel_manager$interrupt ((pvste.devx), ACCEPT_INPUT, unspec (rtx_info));
 782 
 783           return;
 784 
 785 
 786 INTERRUPT (7):                                              /* INPUT REJECTED - ignore */
 787           return;
 788 
 789 
 790 INTERRUPT (8):                                              /* QUIT - ignore */
 791           return;
 792 
 793 
 794 INTERRUPT (9):                                              /* LINE STATUS - process fnp status message */
 795           if ^pvmd.mpx_loaded
 796           then return;
 797           unspec (ls_info) = int_data;
 798           if ls_info.type < lbound (LINE_STAT, 1) | ls_info.type > hbound (LINE_STAT, 1)
 799           then return;
 800           go to LINE_STAT (ls_info.type);
 801 
 802 
 803 LINE_STAT (1):                                              /* PRINTER STATUS */
 804           ls_info.adr = mod (ls_info.adr, 32);              /* turn off poll/select bits in station addr */
 805           subchan = pvmd.station_to_subchan (ls_info.adr).printer;
 806                                                             /* get printer subchan for station */
 807           if subchan = 0
 808           then return;
 809           pvstep = addr (pvmd.subchan_table (subchan));
 810           if ^pvste.dialed
 811           then return;
 812           if ^pvste.hold_output                             /* not expecting any status */
 813           then return;
 814 
 815           if ls_info.sta = ACK                              /* last output frame successfully printed */
 816           then do;
 817 ack:
 818                pvste.hold_output = "0"b;
 819                pvste.naks = 0;                              /* reset counter */
 820                call free_write_chain;                       /* last frame was retained until now */
 821                                                             /* ready for more output */
 822           end;
 823 
 824           else if ls_info.sta = NAK                         /* error on last output frame */
 825           then do;
 826                if pvste.write_chain = 0
 827                then go to ack;
 828                pvste.naks = pvste.naks + 1;                 /* count 'em */
 829                pvste.printer_naks = pvste.printer_naks + 1; /* and meter the count */
 830                if pvste.naks >= 3                           /* give up */
 831                then do;
 832                     pvste.discarded_printer_frame = pvste.discarded_printer_frame + 1;
 833                     go to ack;
 834                end;
 835 
 836                pvste.hold_output = "0"b;
 837                if pvmd.write_chan = 0
 838                then call write_frame (code);
 839           end;
 840 
 841           else do;                                          /* punt */
 842                call channel_manager$interrupt ((pvste.devx), QUIT, ""b);
 843                go to ack;
 844           end;
 845 
 846           return;
 847 
 848 
 849 LINE_STAT (2):                                              /* INPUT TIMEOUT */
 850           pvmd.input_timeouts = pvmd.input_timeouts + 1;
 851 
 852           if pvmd.controller_poll
 853           then do;                                          /* tell FNP to wait for first poll response */
 854                lc_info.type = AWAIT_FIRST_RESPONSE;
 855                call channel_manager$control (pvmd.devx, "line_control", addr (lc_info), code);
 856           end;
 857           else do;                                          /* stop polling the "dead" station */
 858                i = mod (ls_info.adr, 32);                   /* mask out poll/select bits */
 859                if i < 0 | i > 31
 860                then return;
 861                if ^pvmd.cur_station_mask (i)
 862                then return;
 863 
 864                pvmd.cur_station_mask (i) = "0"b;
 865                pvmd.cur_nstation = pvmd.cur_nstation - 1;
 866                if pvmd.cur_nstation = 0                     /* no stations left to poll */
 867                then do;                                     /* assume whole subsystem disabled, but line still up */
 868                     lc_info.type = AWAIT_FIRST_RESPONSE;    /* wait for subsystem to come back */
 869                     call channel_manager$control (pvmd.devx, "line_control", addr (lc_info), code);
 870                     if code ^= 0
 871                     then return;
 872                     pvmd.cur_station_mask = pvmd.station_mask;
 873                     pvmd.cur_nstation = pvmd.nstation;
 874                end;
 875 
 876                lc_info.type = STATION_POLL;
 877                lc_info.arg1 = pvmd.cur_nstation;
 878                lc_info.station_mask = pvmd.cur_station_mask;
 879                call channel_manager$control (pvmd.devx, "line_control", addr (lc_info), code);
 880           end;
 881 
 882           return;
 883 
 884 
 885 LINE_STAT (3):                                              /* INPUT LOST */
 886           pvmd.input_frames_lost = pvmd.input_frames_lost + 1;
 887           return;
 888 
 889 
 890 LINE_STAT (4):                                              /* OUTPUT LOST */
 891           pvmd.output_frames_lost = pvmd.output_frames_lost + 1;
 892           return;
 893 
 894 
 895 LINE_STAT (5):                                              /* BAD OUTPUT FRAME */
 896           pvmd.bad_output_frames = pvmd.bad_output_frames + 1;
 897           return;
 898 
 899 
 900 LINE_STAT (6):                                              /* OUTPUT TIMEOUT */
 901           pvmd.output_timeouts = pvmd.output_timeouts + 1;
 902           return;
 903 
 904 
 905 LINE_STAT (7):                                              /* DISPLAY STATUS */
 906           ls_info.adr = mod (ls_info.adr, 32);              /* turn off poll/select bits */
 907           subchan = pvmd.station_to_subchan (ls_info.adr).display;
 908           if subchan = 0
 909           then return;
 910           pvstep = addr (pvmd.subchan_table (subchan));
 911           if ^pvste.dialed
 912           then return;
 913 
 914           pvste.pgofs = pvste.pgofs + 1;                    /* page overflow is the only display status */
 915           pvste.display_pgofs = pvste.display_pgofs + 1;
 916           if pvste.pgofs >= 3                               /* better put a stop to this */
 917           then do;
 918                pvste.pgofs, pvste.writes = 0;
 919                pvste.pgof_limit_reached = pvste.pgof_limit_reached + 1;
 920                call channel_manager$interrupt ((pvste.devx), QUIT, ""b);
 921                pvste.hold_output = "0"b;
 922                pvste.eop = "1"b;
 923                call free_write_chain;                       /* dump any output */
 924           end;
 925           return;
 926 
 927 
 928 LINE_STAT(8):                                               /* BUILD MESSAGE FAILURE, fnp will hang up channel */
 929           call syserr(0, "Too many errors while trying to build a message, channel ^a will be hung up.", pvmd.name);
 930           return;
 931 
 932 
 933 INTERRUPT (10):                                             /* DIAL STATUS - ignore */
 934           return;
 935 
 936 
 937 INTERRUPT (11):                                             /* WRU TIMEOUT - ignore */
 938           return;
 939 
 940 
 941 INTERRUPT (12):                                             /* SPACE AVAILABLE - some buffer space was freed that we need */
 942           if ^pvmd.mpx_loaded
 943           then return;
 944 
 945 /* We only get this interrupt if we previously failed to write an
 946    output frame due to lack of space.  So try again now.
 947 */
 948 
 949           if ^pvmd.send_output
 950           then return;
 951 
 952           if pvmd.writep ^= null                            /* we have a frame waiting to be written */
 953           then do;
 954                call write_bchain (code);
 955                if code = 0
 956                then return;
 957           end;
 958 
 959           call send_next_frame (min (pvmd.write_chan - 1, 0));
 960                                                             /* starting with write_chan, find subchan with frame to write */
 961           return;
 962 
 963 INTERRUPT (13):
 964 INTERRUPT (14):
 965 INTERRUPT (15):
 966 INTERRUPT (16):                                             /* various interrupts not used by this multiplexer */
 967           return;
 968 
 969 INTERRUPT (17):                                             /* MASKED - treat like HANGUP but use different wakeup message */
 970           call crash_mpx;                                   /* it's all over */
 971           call pxss$ring_0_wakeup (pvmd.load_proc_id, pvmd.load_ev_chan, PV_MPX_MASKED, code);
 972           return;
 973 
 974 ^L
 975 /* Special entry to handle subchannel dialup (called by priv_polled_vip_mpx) */
 976 
 977 dialup:
 978      entry (pm_pvmdp, pm_subchan);
 979 
 980           pvmdp = pm_pvmdp;
 981           subchan = pm_subchan;
 982           pvstep = addr (pvmd.subchan_table (subchan));
 983 
 984           call signal_dialup;
 985           return;
 986 
 987 
 988 /* Special entry to crash the multiplexer (called by priv_polled_vip_mpx) */
 989 
 990 crash:
 991      entry (pm_pvmdp);
 992 
 993           ttybp = addr (tty_buf$);
 994           pvmdp = pm_pvmdp;
 995 
 996           call crash_mpx;
 997           return;
 998 
 999 
1000 /* Special entry to perform per system (rather than per channel) initialization (called by priv_polled_vip_mpx) */
1001 
1002 system_init:
1003      entry;
1004 
1005 /* copy error codes to wired linkage for reference at interrupt time */
1006 
1007           et_undefined_order_request = error_table_$undefined_order_request;
1008           et_noalloc = error_table_$noalloc;
1009           et_action_not_performed = error_table_$action_not_performed;
1010           et_bad_mode = error_table_$bad_mode;
1011           et_invalid_state = error_table_$invalid_state;
1012           et_unimplemented_version = error_table_$unimplemented_version;
1013 
1014           call wire_proc$wire_me;                           /* eat up some memory */
1015 
1016           return;
1017 
1018 
1019 ERROR_EXIT:
1020           return;
1021 ^L
1022 /* Subroutine to initialize subchannel data pointer from external entry parameters */
1023 
1024 setup_subchan:
1025      proc;
1026 
1027           pvmdp = pm_pvmdp;
1028           if ^pvmd.mpx_loaded
1029           then do;
1030                pm_code = et_action_not_performed;
1031                go to ERROR_EXIT;
1032           end;
1033 
1034           subchan = pm_subchan;
1035           pvstep = addr (pvmd.subchan_table (subchan));
1036           if ^pvmd.cur_station_mask (pvste.station_addr)
1037           then do;
1038                pm_code = et_invalid_state;
1039                go to ERROR_EXIT;
1040           end;
1041 
1042      end;
1043 
1044 
1045 
1046 
1047 
1048 /* Subroutine to  handle subchannel dialup */
1049 
1050 signal_dialup:
1051      proc;
1052 
1053           pvste.pgofs, pvste.writes = 0;
1054           pvste.hold_output, pvste.hndlquit = "0"b;
1055           if pvste.printer
1056           then pvste.eop = "0"b;
1057           else pvste.eop = "1"b;
1058           pvste.dialed = "1"b;
1059 
1060           unspec (dialup_info) = pvmd.dialup_info;
1061           dialup_info.buffer_pad = dialup_info.buffer_pad + length (string (frame_trailer));
1062           dialup_info.max_buf_size =
1063                min (dialup_info.max_buf_size,
1064                max (16, divide (pvmd.max_text_len + dialup_info.buffer_pad + 4, 64, 17, 0) * 16));
1065           if pvste.printer                                  /* make sure printer delays come out right */
1066           then dialup_info.baud_rate = pvste.baud_rate;
1067           call channel_manager$interrupt ((pvste.devx), DIALUP, unspec (dialup_info));
1068 
1069           call channel_manager$interrupt ((pvste.devx), SEND_OUTPUT, ""b);
1070                                                             /* authorize first output */
1071 
1072      end;
1073 ^L
1074 /* Subroutine to "erase" illegal characters from a buffer */
1075 
1076 verify_text:
1077      proc;
1078 
1079 dcl  btally fixed bin;
1080 dcl  bchars char (btally) based (addr (buffer.chars));
1081 
1082           btally = buffer.tally;
1083           bchars = translate (bchars, "^@^@^@^@^@^@", "^A^B^C^D^V^W");          /* SOH, STX, ETX, EOT, SYN, ETB -> NUL */
1084 
1085      end;
1086 
1087 
1088 
1089 /* Subroutine to find and send the next write frame (if any) */
1090 
1091 send_next_frame:
1092      proc (pm_last_write_chan);
1093 
1094 dcl  pm_last_write_chan fixed bin;                          /* chan for which output just completed, else 0 */
1095 
1096 dcl  last_write_chan fixed bin;
1097 dcl  found_chan bit (1);
1098 dcl  code fixed bin (35);
1099 
1100           last_write_chan = pm_last_write_chan;
1101 
1102 retry:
1103           found_chan = "0"b;                                /* look for a subchan with pending output */
1104           do i = last_write_chan + 1 to pvmd.nchan while (^found_chan), 1 to last_write_chan while (^found_chan);
1105                subchan = i;
1106                pvstep = addr (pvmd.subchan_table (subchan));
1107                if pvste.write_chain ^= 0 & ^pvste.hold_output
1108                then found_chan = "1"b;
1109           end;
1110           if ^found_chan
1111           then return;
1112 
1113           call write_frame (code);
1114           if code ^= 0                                      /* write failed, look for another frame */
1115           then do;
1116                last_write_chan = subchan;
1117                go to retry;
1118           end;
1119 
1120      end;
1121 ^L
1122 /* Subroutine to compute the longitudinal redundancy check (LRC) char for the text in a buffer */
1123 
1124 get_buffer_lrc:
1125      proc returns (bit (9));
1126 
1127 dcl  lrc bit (9);
1128 dcl  (nwords, nchars, btally, i) fixed bin;
1129 dcl  temp bit (36) aligned;
1130 dcl  p ptr;
1131 
1132 dcl  bwords (nwords) bit (36) aligned based;
1133 dcl  bchars (nchars) bit (9) unal based;
1134 dcl  temp_chars (4) bit (9) based (addr (temp));
1135 
1136           btally = buffer.tally;
1137           nwords = divide (btally, 4, 17, 0);
1138           nchars = mod (btally, 4);
1139           lrc = ""b;
1140 
1141           if nwords > 0
1142           then do;                                          /* for whole words, compute lrc word by word */
1143                temp = ""b;
1144                p = addr (buffer.chars);
1145                do i = 1 to nwords;
1146                     temp = bool (temp, p -> bwords (i), XOR);
1147                end;
1148 
1149                do i = 1 to 4;                               /* collapse temp into single char lrc */
1150                     lrc = bool (lrc, temp_chars (i), XOR);
1151                end;
1152           end;
1153 
1154           if nchars > 0
1155           then do;                                          /* pick up leftover chars */
1156                p = addr (buffer.chars (btally - nchars));
1157                do i = 1 to nchars;
1158                     lrc = bool (lrc, p -> bchars (i), XOR);
1159                end;
1160           end;
1161 
1162           return (lrc);
1163      end;
1164 ^L
1165 /* Subroutine to write a formfeed */
1166 
1167 write_ff:
1168      proc (ff_sent);
1169 
1170 dcl  ff_sent bit (1);                                       /* ON if we succeed */
1171 dcl  p ptr;
1172 dcl  (hlen, tlen) fixed bin;
1173 
1174           ff_sent = "0"b;
1175           pvste.eop = "0"b;
1176           if pvmd.omit_ff | (substr (pvste.name, 1, 1) = "x")
1177           then return;
1178           if (pvste.write_chain ^= 0) | pvmd.write_chan = subchan
1179           then return;                                      /* don't clear screen during output */
1180 
1181           call tty_space_man$get_buffer ((pvste.devx), 16, OUTPUT, p);
1182           if p = null                                       /* did not get buffer, tough luck */
1183           then return;
1184 
1185           pvste.eop = "1"b;                                 /* so frame header will have formfeed */
1186           call get_frame_header (hlen);
1187           pvste.eop = "0"b;
1188           substr (string (p -> buffer.chars), 1, hlen) = substr (string (frame_header), 1, hlen);
1189 
1190           call get_frame_trailer;
1191           tlen = length (string (frame_trailer));
1192           substr (string (p -> buffer.chars), hlen + 1, tlen) = string (frame_trailer);
1193 
1194           p -> buffer.tally = hlen + tlen;
1195           pvste.write_chain = bin (rel (p), 18);
1196           if pvmd.write_chan = 0
1197           then call write_frame (code);
1198 
1199           ff_sent = "1"b;
1200 
1201      end;
1202 ^L
1203 /* Subroutine to build a frame header */
1204 
1205 get_frame_header:
1206      proc (nchars);
1207 
1208 dcl  nchars fixed bin;                                      /* number of chars in frame header */
1209 dcl  adr bit (9);
1210 
1211 
1212           frame_header = frame_header_template;             /* init invariant part of frame header */
1213           adr = bit (pvste.station_addr, 9) | SELECT;       /* get select address */
1214           unspec (frame_header.select_msg.adr) = adr;
1215           unspec (frame_header.select_msg.lrc) = bool (COMMON_LRC, adr, XOR);
1216                                                             /* compute LRC */
1217 
1218           if pvste.printer                                  /* get text message address */
1219           then adr = PRINTER;
1220           else adr = DISPLAY;
1221           unspec (frame_header.text_msg.adr) = adr;
1222           text_lrc = bool (COMMON_LRC, adr, XOR);           /* factor adr into text msg lrc */
1223 
1224           if pvste.printer
1225           then do;
1226                frame_header.text_msg.sta = PRT;             /* make this a transparent printer message */
1227                text_lrc = bool (text_lrc, unspec (PRT), XOR);
1228           end;
1229 
1230           nchars = length (string (frame_header));
1231           if pvste.eop & ^pvmd.omit_ff & ^(substr (pvste.name, 1, 1) = "x")
1232                                                             /* cursor at end of page, must clear screen */
1233           then text_lrc = bool (text_lrc, unspec (FF), XOR);/* account for added FF */
1234           else nchars = nchars - 2;                         /* remove FF from frame header */
1235 
1236      end;
1237 
1238 
1239 
1240 /* Subroutine to build a frame trailer */
1241 
1242 get_frame_trailer:
1243      proc;
1244 
1245 dcl  adr bit (9);
1246 
1247 
1248           frame_trailer = frame_trailer_template;           /* init invariant parts of frame trailer */
1249           unspec (frame_trailer.text_msg.lrc) = text_lrc;
1250 
1251           adr = bit (pvste.station_addr, 9) | POLL;         /* get poll address */
1252           unspec (frame_trailer.poll_msg.adr) = adr;
1253           unspec (frame_trailer.poll_msg.lrc) = bool (COMMON_LRC, adr, XOR);
1254                                                             /* compute lrc */
1255 
1256      end;
1257 ^L
1258 /* Subroutine to write an output frame for a specified subchannel */
1259 
1260 write_frame:
1261      proc (code);
1262 
1263 dcl  code fixed bin (35);
1264 
1265 
1266           code = 0;
1267           pvmd.write_chan = subchan;
1268 
1269           if pvste.printer
1270           then do;
1271                call duplicate_write_chain ();
1272                if pvmd.writep = null                        /* couldn't get the space */
1273                then do;
1274                     call tty_space_man$needs_space (pvmd.devx);
1275                                                             /* find out when more space is available */
1276                     return;
1277                end;
1278                pvste.hold_output = "1"b;                    /* no more output until printer sends ACK */
1279           end;
1280           else do;
1281                pvmd.writep = ptr (ttybp, pvste.write_chain);
1282                pvste.write_chain = 0;
1283                pvste.hold_output = pvmd.writep -> buffer.end_of_page;
1284                                                             /* if at EOP, hold output until next input */
1285           end;
1286 
1287           pvmd.writep -> buffer.end_of_page = "0"b;
1288           call write_bchain (code);
1289 
1290      end;
1291 ^L
1292 /* Subroutine to write the current write buffer chain */
1293 
1294 write_bchain:
1295      proc (code);
1296 
1297 dcl  code fixed bin (35);
1298 
1299 
1300           call channel_manager$write (pvmd.devx, pvmd.writep, code);
1301           if code ^= 0
1302           then do;
1303                if code = et_noalloc                         /* parent multiplexer ran out of space */
1304                then do;
1305                     code = 0;
1306                     call tty_space_man$needs_space (pvmd.devx);
1307                                                             /* find out when more space is available */
1308                end;
1309                else do;                                     /* abort current write frame */
1310                     subchan = pvmd.write_chan;
1311                     pvstep = addr (pvmd.subchan_table (subchan));
1312                     pvste.hold_output = "0"b;
1313                     call free_bchain;
1314                     call free_write_chain;
1315                end;
1316 
1317                return;
1318           end;
1319 
1320           pvmd.send_output = "0"b;
1321      end;
1322 
1323 
1324 
1325 /* Subroutine to free the current write buffer chain */
1326 
1327 free_bchain:
1328      proc;
1329 
1330           call channel_manager$control (pvmd.devx, "abort", addr (WRITE_ABORT), 0);
1331           call tty_space_man$free_chain (pvmd.devx, OUTPUT, pvmd.writep);
1332           pvmd.writep = null;
1333           pvmd.write_chan = 0;
1334 
1335      end;
1336 ^L
1337 /* Subroutine to duplicate a write buffer chain */
1338 
1339 duplicate_write_chain:
1340      proc;
1341 
1342 dcl  (p, newp, prev_newp, new_headp) ptr;
1343 dcl  end_chain bit (1);
1344 dcl  nwords fixed bin;
1345 dcl  bwords (nwords) fixed bin based;
1346 
1347 
1348           p = ptr (ttybp, pvste.write_chain);               /* get ptr to head of write chain */
1349           prev_newp = null;
1350           end_chain = "0"b;
1351           do while (^end_chain);
1352                nwords = (bin (p -> buffer.size_code, 3) + 1) * 16;
1353                                                             /* get number of words in buffer */
1354                call tty_space_man$get_buffer ((pvste.devx), nwords, OUTPUT, newp);
1355                if newp = null                               /* failed to get buffer */
1356                then do;
1357                     if prev_newp ^= null
1358                     then do;
1359                          prev_newp -> buffer.next = 0;
1360                          call tty_space_man$free_chain ((pvste.devx), OUTPUT, prev_newp);
1361                     end;
1362                     return;
1363                end;
1364 
1365                newp -> bwords = p -> bwords;                /* duplicate buffer */
1366                if prev_newp = null                          /* this is first buffer of chain */
1367                then new_headp = newp;
1368                else prev_newp -> buffer.next = bin (rel (newp), 18);
1369                prev_newp = newp;                            /* thread new buffer onto duplicate chain */
1370 
1371                if p -> buffer.next = 0
1372                then end_chain = "1"b;
1373                else p = ptr (ttybp, p -> buffer.next);
1374           end;
1375 
1376           pvmd.writep = new_headp;
1377           return;
1378 
1379      end;
1380 ^L
1381 /* Subroutine to free the write chain for a specified subchannel */
1382 
1383 free_write_chain:
1384      proc;
1385 
1386           if pvste.write_chain ^= 0
1387           then do;
1388                call tty_space_man$free_chain ((pvste.devx), OUTPUT, ptr (addr (tty_buf$), pvste.write_chain));
1389                pvste.write_chain = 0;
1390           end;
1391 
1392           if pvste.dialed & ^pvste.hold_output & (pvmd.write_chan ^= subchan)
1393                                                             /* solicit more output */
1394           then call channel_manager$interrupt ((pvste.devx), SEND_OUTPUT, ""b);
1395 
1396      end;
1397 
1398 
1399 
1400 /* Subroutine to handle a mutliplexer crash */
1401 
1402 crash_mpx:
1403      proc;
1404 
1405 dcl  mpx_loaded bit (1);
1406 
1407           mpx_loaded = pvmd.mpx_loaded;
1408           pvmd.mpx_loading, pvmd.mpx_loaded = "0"b;
1409 
1410 
1411           if mpx_loaded
1412           then do;
1413                if pvmd.writep ^= null
1414                then call free_bchain;
1415                do subchan = 1 to pvmd.nchan;                /* clean up all the subchans */
1416                     pvstep = addr (pvmd.subchan_table (subchan));
1417                     pvste.dialed = "0"b;
1418                     call free_write_chain;
1419                     call channel_manager$interrupt ((pvste.devx), CRASH, ""b);
1420                end;
1421           end;
1422 
1423      end;
1424 
1425 
1426      end;                                                   /* polled_vip_mpx */