/****^ *********************************************************** * * * Copyright, (C) Honeywell Bull Inc., 1987 * * * * Copyright, (C) Honeywell Information Systems Inc., 1982 * * * * Copyright (c) 1972 by Massachusetts Institute of * * Technology and Honeywell Information Systems, Inc. * * * *********************************************************** */ /****^ HISTORY COMMENTS: 1) change(86-09-01,Beattie), approve(86-09-01,MCR7528), audit(86-09-11,Brunelle), install(86-09-16,MR12.0-1159): Change name of the "raw3270" mode to "raw3270io". END HISTORY COMMENTS */ /* format: style4 */ /* IBM3270_MPX - The active portion of the ibm3270 multiplexer */ /* Written May 1979 by Larry Johnson */ /* Modified August 1982 by Robert Coren to handle "MASKED" interrupt */ /* Bug fixes from Ford adopted by Robert Coren, October 1983 */ /* Modified 1985-02-21, EJ Sharpe: use syserr_binary_def.incl.pl1, add format */ ibm3270_mpx: proc; /* Parameters */ dcl arg_mdp ptr; dcl arg_int_type fixed bin; dcl arg_int_data bit (72) aligned; dcl arg_subchan fixed bin; dcl arg_order char (*); dcl arg_infop ptr; dcl arg_code fixed bin (35); dcl arg_chain_ptr ptr; dcl arg_mclp ptr; dcl arg_modes char (*); dcl arg_more_input bit (1) aligned; /* Automatic */ dcl int_type fixed bin; dcl code fixed bin (35); dcl order char (32); dcl infop ptr; dcl chain_ptr ptr; dcl textp ptr; dcl textl fixed bin; dcl i fixed bin; dcl position fixed bin; dcl (pos_char1, pos_char2) char (1); dcl dev_addr fixed bin; dcl status bit (12); dcl subchan fixed bin; dcl header_blockp ptr; dcl chain_len fixed bin; dcl rest_chain_ptr ptr; dcl end_chain_ptr ptr; dcl column fixed bin; dcl c char (1); dcl save_raw_in_effect bit (1); dcl targetp ptr; dcl targetl fixed bin; dcl 1 wcc unal, /* Write control code in output messages */ 2 printer_format bit (2), 2 start_printer bit (1), 2 sound_alarm bit (1), 2 keyboard_restore bit (1), 2 reset_modify bit (1); /* Based */ dcl target char (targetl) based (targetp); dcl text char (textl) based (textp); dcl text_array (textl) char (1) unal based (textp); dcl bit_text_array (textl) bit (9) unal based (textp); dcl 1 status_msg unal based (textp), /* Format of status and test_req */ 2 soh char (1), 2 percent char (1), /* Should be "%", untranlated */ 2 type char (1), /* "/" for test_req, "R" for status */ 2 stx char (1), 2 controller_address char (1), 2 device_address char (1), 2 status1 char (1), 2 status2 char (1), 2 etx char (1); dcl 1 text_msg unal based (textp), /* Format of normal text start */ 2 stx char (1), 2 controller_address char (1), 2 device_address char (1), 2 aid char (1), /* Reason for input (which key) */ 2 cursor1 char (1), 2 cursor2 char (1); dcl 1 abort_info aligned based (infop), /* Data for abort order */ 2 resetwrite bit (1) unal, 2 resetread bit (1) unal, 2 pad bit (34) unal; /* Constants */ dcl name char (11) int static options (constant) init ("ibm3270_mpx"); dcl max_chain_len fixed bin int static options (constant) init (1950); dcl max_raw_chain_len fixed bin int static options (constant) init (4000); dcl ascii_address_table (0:63) bit (9) unal int static options (constant) init ( "040"b3, "101"b3, "102"b3, "103"b3, "104"b3, "105"b3, "106"b3, "107"b3, "110"b3, "111"b3, "133"b3, "056"b3, "074"b3, "050"b3, "053"b3, "041"b3, "046"b3, "112"b3, "113"b3, "114"b3, "115"b3, "116"b3, "117"b3, "120"b3, "121"b3, "122"b3, "135"b3, "044"b3, "052"b3, "051"b3, "073"b3, "136"b3, "055"b3, "057"b3, "123"b3, "124"b3, "125"b3, "126"b3, "127"b3, "130"b3, "131"b3, "132"b3, "174"b3, "054"b3, "045"b3, "137"b3, "076"b3, "077"b3, "060"b3, "061"b3, "062"b3, "063"b3, "064"b3, "065"b3, "066"b3, "067"b3, "070"b3, "071"b3, "072"b3, "043"b3, "100"b3, "047"b3, "075"b3, "042"b3); dcl ebcdic_address_table (0:63) bit (9) unal int static options (constant) init ( "100"b3, "301"b3, "302"b3, "303"b3, "304"b3, "305"b3, "306"b3, "307"b3, "310"b3, "311"b3, "112"b3, "113"b3, "114"b3, "115"b3, "116"b3, "117"b3, "120"b3, "321"b3, "322"b3, "323"b3, "324"b3, "325"b3, "326"b3, "327"b3, "330"b3, "331"b3, "132"b3, "133"b3, "134"b3, "135"b3, "136"b3, "137"b3, "140"b3, "141"b3, "342"b3, "343"b3, "344"b3, "345"b3, "346"b3, "347"b3, "350"b3, "351"b3, "152"b3, "153"b3, "154"b3, "155"b3, "156"b3, "157"b3, "360"b3, "361"b3, "362"b3, "363"b3, "364"b3, "365"b3, "366"b3, "367"b3, "370"b3, "371"b3, "172"b3, "173"b3, "174"b3, "175"b3, "176"b3, "177"b3); dcl ( SYSERR_CRASH_SYSTEM init (1), /* Crash the system, and bleat plaintively. */ BEEP init (3), /* Beep and print the message on the console. */ ANNOUNCE init (0), /* Just print the message on the console. */ JUST_LOG init (5) /* Just try to log the message, and discard it if it can't be */ ) fixed bin internal static options (constant); /* Interal static */ dcl et_undefined_order_request fixed bin (35) int static; dcl et_improper_data_format fixed bin (35) int static; dcl et_noalloc fixed bin (35) int static; dcl et_bad_mode fixed bin (35) int static; /* External */ dcl pxss$ring_0_wakeup entry (bit (36) aligned, fixed bin (71), fixed bin (71), fixed bin (35)); dcl syserr entry options (variable); dcl syserr$binary entry options (variable); dcl wire_proc$wire_me entry; dcl error_table_$undefined_order_request ext fixed bin (35); dcl error_table_$noalloc ext fixed bin (35); dcl error_table_$improper_data_format ext fixed bin (35); dcl error_table_$bad_mode ext fixed bin (35); dcl (addr, bin, bit, hbound, index, lbound, low, min, max, mod, null, ptr, rel, string, substr, unspec, size) builtin; dcl cleanup condition; %page; /* Control entry point */ control: entry (arg_mdp, arg_subchan, arg_order, arg_infop, arg_code); mdp = arg_mdp; subchan = arg_subchan; mdep = addr (md.mde_entry (subchan)); order = arg_order; infop = arg_infop; ttybp = addr (tty_buf$); if order = "enter_receive" then do; if ^mde.raw3270_in_effect then do; mde.keyboard_restore = "1"b; call queue_control; /* Schedule the unlocking */ call process_write; /* In case it can be done now */ end; code = 0; end; else if order = "abort" then do; if abort_info.resetwrite then call reset_channel; code = 0; end; else if order = "listen" then do; mde.listen = "1"b; if mde.printer & ^mde.dialed & md.started then call dialup_channel; code = 0; end; else if order = "hangup" then do; mde.listen, mde.dialed = "0"b; call reset_channel; mde.keyboard_restore = "1"b; call channel_manager$interrupt (mde.devx, HANGUP, "0"b); call queue_control; call process_write; code = 0; end; else if order = "wru" then do; call channel_manager$interrupt (mde.devx, WRU_TIMEOUT, "0"b); code = 0; end; else if order = "printer_off" then code = 0; else if order = "printer_on" then code = 0; else code = et_undefined_order_request; arg_code = code; return; /* Read entry. We have no data. */ read: entry (arg_mdp, arg_subchan, arg_chain_ptr, arg_more_input, arg_code); arg_chain_ptr = null (); arg_more_input = "0"b; arg_code = 0; return; %page; /* Entries that dial with modes. We only watch the setting of hndlquit */ check_modes: entry (arg_mdp, arg_subchan, arg_mclp, arg_code); mdp = arg_mdp; subchan = arg_subchan; mdep = addr (md.mde_entry (subchan)); mclp = arg_mclp; ttybp = addr (tty_buf$); do i = 1 to mcl.n_entries; mclep = addr (mcl.entries (i)); mcle.mpx_mode = "0"b; if mcle.mode_name = "hndlquit" | mcle.mode_name = "rawi" | mcle.mode_name = "rawo" then mcle.mpx_mode = "1"b; /* Monitor changes to these modes */ else if mcle.mode_name = "raw3270io" then if (^md.allow_raw3270 & mcle.mode_switch) then mcle.error = "1"b; else mcle.mpx_mode = "1"b; end; arg_code = 0; return; set_modes: entry (arg_mdp, arg_subchan, arg_mclp, arg_code); mdp = arg_mdp; subchan = arg_subchan; mdep = addr (md.mde_entry (subchan)); mclp = arg_mclp; arg_code = 0; ttybp = addr (tty_buf$); save_raw_in_effect = mde.raw3270_in_effect; if mcl.init then mde.hndlquit, mde.rawi, mde.rawo, mde.raw3270 = "0"b; do i = 1 to mcl.n_entries; mclep = addr (mcl.entries (i)); if mcle.mpx_mode then do; if mcle.mode_name = "hndlquit" then mde.hndlquit = mcle.mode_switch; else if mcle.mode_name = "rawi" then mde.rawi = mcle.mode_switch; else if mcle.mode_name = "rawo" then mde.rawo = mcle.mode_switch; else if mcle.mode_name = "raw3270io" then mde.raw3270 = mcle.mode_switch; else do; mcle.error = "1"b; arg_code = et_bad_mode; end; end; end; mde.raw3270_in_effect = mde.rawi & mde.rawo & mde.raw3270; if save_raw_in_effect & ^mde.raw3270_in_effect & ^mde.write_queued & mde.write_chain_ptr ^= null () then call reset_channel; return; get_modes: entry (arg_mdp, arg_subchan, arg_modes, arg_code); mdp = arg_mdp; subchan = arg_subchan; mdep = addr (md.mde_entry (subchan)); ttybp = addr (tty_buf$); if mde.raw3270 then arg_modes = "raw3270io"; else arg_modes = "^raw3270io"; arg_code = 0; return; %page; /* Write entry point */ write: entry (arg_mdp, arg_subchan, arg_chain_ptr, arg_code); mdp = arg_mdp; subchan = arg_subchan; mdep = addr (md.mde_entry (subchan)); chain_ptr = arg_chain_ptr; end_chain_ptr, rest_chain_ptr, header_blockp = null (); arg_code = 0; ttybp = addr (tty_buf$); /* The following section handles writing in raw3270 mode */ if mde.raw3270_in_effect then do; if mde.write_chain_ptr ^= null () then do; /* Must merge */ do blockp = mde.write_chain_ptr repeat (ptr (ttybp, buffer.next)) while (buffer.next ^= 0); end; /* Find end */ buffer.next = bin (rel (chain_ptr)); chain_ptr = mde.write_chain_ptr; mde.write_chain_ptr = null (); end; /* Look for end of first command as signalled by ETX. */ position = 0; blockp = chain_ptr; do while (position = 0 & (rel (blockp) ^= "0"b)); textp = addr (buffer.chars); textl = buffer.tally; position = index (text, md.etx); end_chain_ptr = blockp; if position = 0 then do; blockp = ptr (ttybp, buffer.next); end; end; if position = -1 /* No ETX in message anywhere. */ then do; if ^end_chain_ptr -> buffer.break then do; /* Don't have complete message yet. */ mde.write_chain_ptr = chain_ptr; arg_chain_ptr = null (); arg_code = 0; call channel_manager$interrupt (mde.devx, SEND_OUTPUT, "0"b); return; end; else do; goto write_format_error; end; end; /* ETX is somewhere in message. */ if position = buffer.tally /* ETX is at end of buffer. Don't need to split buffer. */ then do; if buffer.next = 0 then ; /* ETX is at end of chain. Don't need to split chain. */ else do; /* Need to split chain, but not buffer. */ rest_chain_ptr = ptr (ttybp, buffer.next); buffer.next = 0; end; end; else do; /* Need to split buffer and split chain. */ call tty_space_man$get_buffer (mde.devx, 16 * (buffer.size_code + 1), OUTPUT, rest_chain_ptr); if rest_chain_ptr = null () then do; arg_code = et_noalloc; return; end; rest_chain_ptr -> buffer.next = buffer.next; rest_chain_ptr -> buffer.flags = buffer.flags; rest_chain_ptr -> buffer.size_code = buffer.size_code; rest_chain_ptr -> buffer.tally = (buffer.tally - position); buffer.tally = position; buffer.next = 0; buffer.break = "1"b; textp = addr (buffer.chars (position)); textl = rest_chain_ptr -> buffer.tally; targetp = addr (rest_chain_ptr -> buffer.chars); targetl = textl; target = text; end; /* Check max chain length. */ chain_len = 0; do blockp = chain_ptr repeat (ptr (ttybp, buffer.next)) while (rel (blockp) ^= "0"b); chain_len = chain_len + buffer.tally; end_chain_ptr = blockp; end; if chain_len > max_raw_chain_len then do; go to write_format_error; end; /* Check for proper message header. */ blockp = chain_ptr; if buffer.chars (0) ^= md.stx then do; go to write_format_error; end; if buffer.chars (1) ^= md.esc then do; go to write_format_error; end; if (buffer.chars (2) = md.write) | (buffer.chars (2) = md.erase_write) then ; /* Ok */ else do; if buffer.chars (2) = md.copy then do; if md.allow_copy then ; /* Ok */ else do; go to write_format_error; end; end; else do; goto write_format_error; end; end; /* Check for trailing ETX. */ blockp = end_chain_ptr; if buffer.chars (buffer.tally - 1) ^= md.etx then go to write_format_error; /* Seem to have a good message; queue it up. */ header_blockp = chain_ptr; go to queue_write_data; end; /* Rest of code is for case of ^raw3270 mode */ /* First, be sure a buffer is available for the header */ call tty_space_man$get_buffer (mde.devx, 16, OUTPUT, header_blockp); if header_blockp = null () then do; arg_code = et_noalloc; /* Give up if no space */ return; end; /* Take as much of the chain as can fit in a single bisync message. We must be careful not to split a NL-NUL-NUL sequence. */ chain_len = 0; do blockp = chain_ptr repeat (ptr (ttybp, buffer.next)) while (rel (blockp) ^= "0"b); chain_len = chain_len + buffer.tally; if chain_len > max_chain_len then do; rest_chain_ptr = blockp; /* First buffer of rest of chain */ blockp = end_chain_ptr; buffer.next = 0; /* Break chain */ if mde.printer then go to chain_split; textp = addr (buffer.chars); textl = buffer.tally; do i = 2 to 1 by -1 while (textl > (2 - i)); /* Check last 2 characters */ c = substr (text, textl - 2 + i, 1); if c = md.nl | c = md.cr | c = md.bs then do; call check_buffer_tally (i); /* This buffer must have room */ buffer.tally = buffer.tally + i; textl = textl + i; substr (text, textl - i + 1, i) = low (i); blockp = rest_chain_ptr; if buffer.tally > i then do; /* Trim stuff from front */ textp = addr (buffer.chars); textl = buffer.tally; text = substr (text, i + 1); buffer.tally = buffer.tally - i; end; go to chain_split; end; end; go to chain_split; end; end_chain_ptr = blockp; end; chain_split: if end_chain_ptr = null () then end_chain_ptr = chain_ptr; /* The output chain should have been formatted by tty_write so that each new_line is followed by two nulls. Loop thru the chain replacing each such sequence by a cursor addressing sequence */ blockp = chain_ptr; textp = addr (buffer.chars); textl = buffer.tally; if mde.printer then mde.erase_req = "1"b; if mde.erase_req then mde.position = 0; position = mde.position; if ^mde.printer then do while (textp ^= null ()); /* Loop over entire message */ i = 0; /* Index to next interesting char */ call find_next (i, md.nl); call find_next (i, md.cr); call find_next (i, md.bs); if i = 0 then do; position = mod (position + textl, mde.screen_size); call adv_text (textl); end; else do; column = mod (position, mde.line_size); position = position - column; if i > 1 then do; /* Data before new line */ column = column + i - 1; call adv_text (i - 1); end; c = substr (text, 1, 1); substr (text, 1, 1) = md.sba; /* Replace nl by set-buffer-address */ call adv_text (1); if textp = null () then go to write_format_error; /* Should be followed by 2 nulls */ if substr (text, 1, 1) ^= low (1) then go to write_format_error; if c = md.nl then do; if (column = 0) | (mod (column, mde.line_size) ^= 0) then column = column - mod (column, mde.line_size) + mde.line_size; end; else if c = md.cr then column = 0; else if c = md.bs then column = max (column - 1, 0); position = mod (position + column, mde.screen_size); call get_position_chars (position); substr (text, 1, 1) = pos_char1; /* Replace first null () */ call adv_text (1); /* To second null */ if textp = null then go to write_format_error; if substr (text, 1, 1) ^= low (1) then go to write_format_error; substr (text, 1, 1) = pos_char2; call adv_text (1); end; end; /* Format the header block with STX-ESC-WRITE-WCC-SBA-POS-POS */ blockp = header_blockp; if ^mde.printer then mde.end_of_page = end_chain_ptr -> buffer.end_of_page; end_chain_ptr -> buffer.end_of_page = "0"b; if mde.end_of_page then mde.keyboard_restore = "1"b; call build_header; buffer.next = bin (rel (chain_ptr)); /* Thread to head of chain */ /* Two characters must be added to end of last buffer, IC (insert cursor) and ETX */ blockp = end_chain_ptr; call check_buffer_tally (2); /* Need space for 2 chars */ textp = addr (buffer.chars); /* Add necessary stuff to end of last buffer */ textl = buffer.tally; textl = textl + 2; if mde.printer then substr (text, textl - 1, 1) = md.em; else substr (text, textl - 1, 1) = md.ic; /* Insert cursor */ substr (text, textl, 1) = md.etx; buffer.tally = textl; /* Data is now completely formated and ready to transmit */ mde.position = position; /* Where we left the cursor */ queue_write_data: call queue_write; call process_write; /* Start this write if possible */ arg_chain_ptr = rest_chain_ptr; return; write_format_error: if header_blockp ^= null () then call tty_space_man$free_buffer (mde.devx, OUTPUT, header_blockp); call tty_space_man$free_chain (mde.devx, OUTPUT, chain_ptr); if rest_chain_ptr ^= null () then call tty_space_man$free_chain (mde.devx, OUTPUT, rest_chain_ptr); md.write_format_error = md.write_format_error + 1; arg_code = 0; /* A code would be better, but users cant handle it */ arg_chain_ptr = null (); return; %page; /* Interrupt entry point */ interrupt: entry (arg_mdp, arg_int_type, arg_int_data); mdp = arg_mdp; int_type = arg_int_type; interrupt_info = arg_int_data; ttybp = addr (tty_buf$); if int_type < lbound (INTERRUPT, 1) | int_type > hbound (INTERRUPT, 1) then do; call syserr (ANNOUNCE, "^a: Unrecognized interrupt for ^a. ^d ^.3b", name, md.name, int_type, interrupt_info); return; end; go to INTERRUPT (int_type); /* DIALUP interrupt - This means that the major channel has dialed up and the multiplexer is now loaded */ INTERRUPT (1): if ^md.loading then return; md.loading = "0"b; md.loaded = "1"b; unspec (dialup_info) = interrupt_info; md.line_type = dialup_info.line_type; md.baud_rate = dialup_info.baud_rate; md.max_buf_size = dialup_info.max_buf_size; md.buffer_pad = dialup_info.buffer_pad; call pxss$ring_0_wakeup (md.processid, md.event_channel, IBM3270_MPX_UP, code); return; /* HANGUP interrupt - This means that we have lost the phone and the multiplexer is considered crashed */ INTERRUPT (2): if ^md.loaded then return; call crash_mpx; call pxss$ring_0_wakeup (md.processid, md.event_channel, IBM3270_MPX_DOWN, code); return; /* CRASH interrupt - Parent multiplexer has crashed. We must propagate the information */ INTERRUPT (3): if ^md.loaded then return; call crash_mpx; return; /* SEND_OUTPUT interrupt - We are allowed to send more output */ INTERRUPT (4): if ^md.loaded then return; md.send_output = "1"b; /* Will save for later */ if md.message_in_progress then do; /* Complete partially written msg */ call send_more_message; return; end; if ^md.output_in_progress then call process_write;/* Just in case */ return; %page; /* INPUT_AVAILABLE Interrupt - Not used */ INTERRUPT (5): return; /* ACCEPT_INPUT - Real data to processes */ INTERRUPT (6): if ^md.loaded then return; md.poll_in_progress = "0"b; /* Any input suspends polling */ unspec (rtx_info) = interrupt_info; chain_ptr = ptr (ttybp, rtx_info.chain_head); /* Start of input chain */ if ^md.loaded then go to discard_input; blockp = chain_ptr; textp = addr (buffer.chars); textl = buffer.tally; /* Set up to look at start */ if substr (text, 1, 1) = md.eot then do; /* EOT means end of current poll operation */ md.poll_in_progress = "0"b; call tty_space_man$free_chain (md.devx, INPUT, chain_ptr); call process_input; call process_polls; call process_write; return; end; if substr (text, 1, 1) = md.soh then do; /* Status or test_req */ if textl < 5 then go to bad_input; if status_msg.percent ^= md.percent then /* Should start % */ go to bad_input; if status_msg.type = slash then go to discard_input; /* Ignore test_reqq */ if status_msg.type ^= md.letter_R then /* R means status */ go to bad_input; if textl < 9 then go to bad_input; if status_msg.stx ^= md.stx then go to bad_input; dev_addr = getbin (status_msg.device_address); if dev_addr < lbound (md.chan_map, 1) | dev_addr > hbound (md.chan_map, 1) then go to bad_dev_addr; subchan = md.chan_map (dev_addr); if subchan <= 0 then go to bad_device; mdep = addr (md.mde_entry (subchan)); if ^mde.dialed then go to discard_input; /* Dont care if hungup */ if ^mde.printer then go to discard_input; /* Dont care except for printers */ if mde.waiting_for_ready then do; /* Looking for printer to go ready */ substr (status, 1, 6) = getbit (status_msg.status1); substr (status, 7, 6) = getbit (status_msg.status2); if status = "0200"b3 then do; /* Really ready status */ mde.waiting_for_ready = "0"b; call channel_manager$interrupt (mde.devx, SEND_OUTPUT, "0"b); end; end; go to discard_input; /* Throw away status */ end; /* Accumulate this block onto the current input chain */ if text_msg.stx ^= md.stx then go to bad_input; if textl < 2 then go to bad_input; if md.input_chain_ptr ^= null () then do; /* Trim etb off previous block */ call trim_chain_end (md.input_chain_ptr, 1); md.input_count = md.input_count - 1; end; if md.input_chain_ptr ^= null () then do; /* Thread blocks */ call trim_chain_start (chain_ptr, 1); /* Throw away new stx */ do blockp = md.input_chain_ptr repeat (ptr (ttybp, buffer.next)) while (buffer.next ^= 0); end; buffer.next = bin (rel (chain_ptr)); md.input_count = md.input_count + rtx_info.input_count - 1; end; else do; /* First block */ md.input_chain_ptr = chain_ptr; md.input_count = rtx_info.input_count; end; do blockp = chain_ptr repeat (ptr (ttybp, buffer.next)) while (buffer.next ^= 0); end; /* Find last block */ if buffer.chars (buffer.tally - 1) = md.etx then call process_input; return; bad_input: textl = min (textl, 8); if md.debug then call syserr (ANNOUNCE, "^a: Unrecognized input for ^a:^( ^.3b^)", name, md.name, bit_text_array); md.bad_input = md.bad_input + 1; go to discard_input; bad_dev_addr: /* This is so bad we cannot even mask it off, because its not in the table */ call syserr (ANNOUNCE, "^a: Input for illegal device address ^d on ^a", name, dev_addr, md.name); md.bad_device = md.bad_device + 1; go to discard_input; bad_device: if subchan < 0 then go to discard_input; /* Once per bootload */ call syserr (ANNOUNCE, "^a: Input for unconfigured device ^d on ^a", name, dev_addr, md.name); md.bad_device = md.bad_device + 1; md.chan_map (dev_addr) = -1; /* So wont get printed again */ discard_input: call tty_space_man$free_chain (md.devx, INPUT, chain_ptr); return; /* INPUT REJECTED interrupt - Ignore */ INTERRUPT (7): if ^md.loaded then return; md.input_reject = md.input_reject + 1; return; /* QUIT interrupt - Ignore */ INTERRUPT (8): return; /* DIAL STATUS interrupt - Ignore */ INTERRUPT (10): return; /* WRU TIMEOUT interrupt - Ignore */ INTERRUPT (11): return; /* SPACE AVAILABLE interrupt - Retry suspended write operation. */ INTERRUPT (12): if ^md.loaded then return; md.space_available = md.space_available + 1; if md.message_in_progress then call send_more_message; else call process_write; return; /* various others - ignore */ INTERRUPT (13): INTERRUPT (14): INTERRUPT (15): INTERRUPT (16): return; /* MASKED interrupt - Treat like hangup but use different wakeup message */ INTERRUPT (17): if ^md.loaded then return; call crash_mpx; call pxss$ring_0_wakeup (md.processid, md.event_channel, IBM3270_MPX_MASKED, code); return; %page; /* LINE STATUS interrupt - Decode and act upon in */ INTERRUPT (9): if ^md.loaded then return; unspec (line_stat) = interrupt_info; if line_stat.op < lbound (LINE_STAT, 1) | line_stat.op > hbound (LINE_STAT, 1) then return; go to LINE_STAT (line_stat.op); LINE_STAT (1): /* No response to poll */ md.poll_failed = md.poll_failed + 1; md.poll_in_progress = "0"b; call process_polls; call process_write; return; LINE_STAT (2): /* Badly formated output block */ md.bad_output = md.bad_output + 1; go to line_stat_output_complete; LINE_STAT (3): /* Rvi - device has status */ if md.output_in_progress then do; subchan = md.cur_write_chan; mdep = addr (md.mde_entry (subchan)); call queue_poll; end; go to line_stat_output_complete; LINE_STAT (4): /* Too many naks */ return; LINE_STAT (5): /* Write status - can't happen */ return; LINE_STAT (6): /* 3270 write complete */ line_stat_output_complete: if md.output_in_progress then do; md.output_in_progress, md.message_in_progress = "0"b; mdep = addr (md.mde_entry (md.cur_write_chan)); if ^mde.end_of_page then call channel_manager$interrupt (mde.devx, SEND_OUTPUT, "0"b); end; line_stat_continue: call process_polls; call process_write; return; LINE_STAT (7): /* 3270 wack msg - printer going busy */ if ^md.output_in_progress then go to line_stat_continue; md.output_in_progress, md.message_in_progress = "0"b; mdep = addr (md.mde_entry (md.cur_write_chan)); if mde.printer then mde.waiting_for_ready = "1"b; else call channel_manager$interrupt (mde.devx, SEND_OUTPUT, "0"b); go to line_stat_continue; LINE_STAT (8): /* Ibm3270 write eot */ md.write_eot = md.write_eot + 1; go to line_stat_output_complete; LINE_STAT (9): md.write_abort = md.write_abort + 1; go to line_stat_output_complete; LINE_STAT (10): md.select_failed = md.select_failed + 1; go to line_stat_output_complete; LINE_STAT (11): md.wack_select = md.wack_select + 1; go to line_stat_output_complete; LINE_STAT (12): md.nak_output = md.nak_output + 1; go to line_stat_output_complete; %page; /* Internal procedure to adv pointer down a buffer chain. If the current buffer is exhausted, step to next */ adv_text: proc (n); dcl n fixed bin; textp = addr (text_array (n + 1)); /* Bump pointer */ textl = textl - n; /* Reduce tally */ if textl > 0 then return; /* More in buffer */ if buffer.next = 0 then do; /* End of chain */ textp = null (); return; end; blockp = ptr (ttybp, buffer.next); /* Next in chain */ textp = addr (buffer.chars); textl = buffer.tally; return; end adv_text; /* Procedure for finding next occurance of specified character */ find_next: proc (ix, c); dcl ix fixed bin; dcl c char (1) unal; dcl i fixed bin; if ix = 0 then ix = index (text, c); /* No interesting chars yet */ else if ix = 1 then ; /* Nothing if another interesting char first */ else do; i = index (substr (text, 1, ix - 1), c); if i ^= 0 then ix = i; end; return; end find_next; /* This procedure computes a two character addressing sequence, given a position */ get_position_chars: proc (pos); dcl pos fixed bin; dcl posbit bit (12); posbit = bit (bin (pos, 12), 12); /* Need two 6-but pieces */ pos_char1 = address_table (bin (substr (posbit, 1, 6))); pos_char2 = address_table (bin (substr (posbit, 7, 6))); return; end get_position_chars; /* Decode screen position from characters */ get_position: proc (c1, c2) returns (fixed bin); dcl (c1, c2) char (1); return (bin (getbit (c1) || getbit (c2))); end get_position; /* Functions that map status chars back into usefull stuff */ getbin: proc (c) returns (fixed bin (6)); dcl c char (1); return (bin (substr (unspec (c), 4, 6), 6)); end getbin; getbit: proc (c) returns (bit (6)); dcl c char (1); return (substr (unspec (c), 4, 6)); end getbit; %page; /* Internal procedure to queue a channel with data to write on the mpx write queue */ queue_write: proc; dcl p ptr; if mde.write_queued then do; call syserr$binary ( JUST_LOG, /* Log, discard if not possible */ mdep, SB_ibm3270_mde, size (mde), /* Size of an MDE */ "^a: Attempt to queue write while write queued ^a.^a", name, md.name, mde.name ); mde.write_chain_ptr = header_blockp; return; end; if md.first_write_chan = 0 then do; md.first_write_chan = subchan; md.last_write_chan = subchan; end; else do; p = addr (md.mde_entry (md.last_write_chan)); p -> mde.next_write_chan = subchan; md.last_write_chan = subchan; end; mde.next_write_chan = 0; mde.write_chain_ptr = header_blockp; mde.write_queued = "1"b; return; end queue_write; /* Procedure to process the next piece of output */ process_write: proc; mdep = null (); if md.output_in_progress then return; /* Doing someone else */ if md.poll_in_progress then return; if md.first_poll_chan ^= 0 then return; /* Polling has priority */ if md.first_control_chan ^= 0 then do; call setup_control_chan; if mdep ^= null () then go to write_join; /* Found one */ end; if md.first_write_chan = 0 then return; /* Nothing to do anyway */ subchan = md.first_write_chan; mdep = addr (md.mde_entry (subchan)); md.first_write_chan = mde.next_write_chan; /* Dequeue */ if md.first_write_chan = 0 then md.last_write_chan = 0; mde.next_write_chan = 0; mde.write_queued = "0"b; write_join: md.write_chain_ptr = mde.write_chain_ptr; /* Pick up data from channel */ mde.write_chain_ptr = null (); md.cur_write_chan = subchan; md.eot_sent = "0"b; md.output_in_progress = "1"b; md.message_in_progress = "1"b; call select; call send_more_message; mdep = null (); return; end process_write; %page; /* Procedure to send the next piece of the current output chain down the pike */ send_more_message: proc; dcl p ptr; if ^md.send_output then return; /* Dont have permission */ if md.write_chain_ptr ^= null () then do; /* Have a chain */ p = md.write_chain_ptr; send_chain: call channel_manager$write (md.devx, p, code); if code ^= 0 then do; if code ^= et_noalloc then go to write_fails; md.needs_space = md.needs_space + 1; call tty_space_man$needs_space (md.devx); return; end; md.send_output = "0"b; md.write_chain_ptr = p; /* Remember whats left */ return; end; if ^md.eot_sent then do; /* Still must send an eot */ call tty_space_man$get_buffer (md.devx, 16, OUTPUT, blockp); if blockp = null () then do; md.needs_space = md.needs_space + 1; call tty_space_man$needs_space (md.devx); return; end; buffer.tally = 1; buffer.chars (0) = md.eot; p = blockp; md.eot_sent = "1"b; go to send_chain; end; md.message_in_progress = "0"b; return; write_fails: return; /* Probably a crash coming soon */ end send_more_message; %page; /* Build write header in current buffer */ build_header: proc; buffer.chars (0) = md.stx; buffer.chars (1) = md.esc; if mde.erase_req then buffer.chars (2) = md.erase_write; else buffer.chars (2) = md.write; /* Write function */ mde.erase_req = "0"b; string (wcc) = "0"b; /* No special functions */ wcc.keyboard_restore = mde.keyboard_restore; mde.keyboard_restore = "0"b; wcc.sound_alarm = mde.sound_alarm; mde.sound_alarm = "0"b; wcc.start_printer = mde.printer; buffer.chars (3) = address_table (bin (string (wcc))); buffer.chars (4) = md.sba; /* Position cursor where I think i should be */ call get_position_chars (mde.position); buffer.chars (5) = pos_char1; buffer.chars (6) = pos_char2; buffer.tally = 7; return; end build_header; %page; /* Procedure to process input once it has arrived in its entirity */ process_input: proc; dcl delta_position fixed bin; dcl save_bit bit (1); if md.input_chain_ptr = null () then return; chain_ptr = md.input_chain_ptr; md.input_chain_ptr = null (); blockp = chain_ptr; textp = addr (buffer.chars); textl = buffer.tally; dev_addr = getbin (text_msg.device_address); if dev_addr < lbound (md.chan_map, 1) | dev_addr > hbound (md.chan_map, 1) then go to bad_dev_addr; subchan = md.chan_map (dev_addr); if subchan <= 0 then go to bad_device; /* Address wasn't configured */ mdep = addr (md.mde_entry (subchan)); if ^mde.dialed then do; /* Iirst input */ if mde.listen & md.started then do; /* And we are accepting dials */ mde.erase_req = "1"b; call dialup_channel; end; go to discard_input; end; if textl < 5 then go to bad_input; if text_msg.aid = md.quit_key then do; /* Function code for quit */ if mde.raw3270_in_effect & ^mde.hndlquit then go to send_raw_input; save_bit = mde.end_of_page; mde.end_of_page = "0"b; if save_bit then do; mde.position = 0; mde.erase_req = "1"b; end; call channel_manager$interrupt (mde.devx, QUIT, "0"b); if mde.hndlquit then if mde.write_chain_ptr ^= null () then do; call reset_channel; save_bit = "0"b; end; if save_bit then call channel_manager$interrupt (mde.devx, SEND_OUTPUT, "0"b); go to discard_input; end; if mde.raw3270_in_effect then go to send_raw_input; if text_msg.aid = md.formfeed_key then do; /* Function for ff for new page */ mde.erase_req, mde.keyboard_restore = "1"b; mde.position = 0; call queue_control; if mde.end_of_page then do; mde.end_of_page = "0"b; call channel_manager$interrupt (mde.devx, SEND_OUTPUT, "0"b); end; else do; unspec (rtx_info) = "0"b; rtx_info.formfeed_present = "1"b; call channel_manager$interrupt (mde.devx, ACCEPT_INPUT, unspec (rtx_info)); end; go to discard_input; end; if text_msg.aid ^= md.enter then go to discard_input; if textl < 7 then go to bad_input; position = get_position (text_msg.cursor1, text_msg.cursor2); delta_position = position - mde.position; /* Amount cursor moved */ if (delta_position > (md.input_count - 7)) | (delta_position < 0) then do; /* Cant parse it */ mde.position = position; mde.sound_alarm = "1"b; mde.keyboard_restore = "1"b; call queue_control; go to discard_input; end; call trim_chain_start (chain_ptr, md.input_count - (delta_position + 1)); /* Throw away leading junk */ do blockp = chain_ptr repeat (ptr (ttybp, buffer.next)) while (buffer.next ^= 0); end; /* Find end */ buffer.chars (buffer.tally - 1) = md.nl; /* Put new-line at end */ unspec (rtx_info) = "0"b; rtx_info.chain_head = rel (chain_ptr); rtx_info.chain_tail = rel (blockp); rtx_info.input_count = delta_position + 1; rtx_info.break_char = "1"b; mde.position = mod (position - mod (position, mde.line_size) + mde.line_size, mde.screen_size); call channel_manager$interrupt (mde.devx, ACCEPT_INPUT, unspec (rtx_info)); return; /* Here in raw mode to foward stuff */ send_raw_input: do blockp = chain_ptr repeat (ptr (ttybp, buffer.next)) while (buffer.next ^= 0); end; /* Find end of chain */ unspec (rtx_info) = "0"b; rtx_info.chain_head = rel (chain_ptr); rtx_info.chain_tail = rel (blockp); rtx_info.input_count = md.input_count; rtx_info.break_char = "1"b; call channel_manager$interrupt (mde.devx, ACCEPT_INPUT, unspec (rtx_info)); return; end process_input; %page; /* Procedure to trim a specified number of characters off start of chain */ trim_chain_start: proc (p, arg_n); dcl p ptr; dcl arg_n fixed bin; dcl q ptr; dcl n fixed bin; dcl textp ptr; dcl textl fixed bin; dcl text char (textl) based (textp); n = arg_n; do while (n > 0); if p -> buffer.tally <= n then do; q = p; n = n - p -> buffer.tally; if p -> buffer.next = 0 then if n > 0 then go to trim_failure; /* More than in chain */ else p = null (); /* Exactly size of chain */ else p = ptr (ttybp, p -> buffer.next); call tty_space_man$free_buffer (md.devx, INPUT, q); end; else do; textp = addr (p -> buffer.chars); textl = p -> buffer.tally; text = substr (text, n + 1); p -> buffer.tally = p -> buffer.tally - n; n = 0; end; end; return; end trim_chain_start; %page; /* Trim characters from end of buffer chain */ trim_chain_end: proc (arg_p, arg_n); dcl arg_p ptr; dcl arg_n fixed bin; dcl p ptr; dcl n fixed bin; dcl q ptr; n = arg_n; do while (n > 0); q = null (); p = arg_p; do while (p -> buffer.next ^= 0); q = p; p = ptr (ttybp, p -> buffer.next); end; if n < p -> buffer.tally then do; p -> buffer.tally = p -> buffer.tally - n; n = 0; end; else do; n = n - p -> buffer.tally; if q ^= null () then q -> buffer.next = 0; else if n > 0 then go to trim_failure; else arg_p = null (); call tty_space_man$free_buffer (md.devx, INPUT, p); end; end; return; end trim_chain_end; trim_failure: call syserr (SYSERR_CRASH_SYSTEM, "^a: Error trimming buffer chain for ^a", name, md.name); go to trim_failure; %page; /* Queue a poll request */ queue_poll: proc; dcl p ptr; if md.first_poll_chan = 0 then do; md.first_poll_chan = subchan; md.last_poll_chan = subchan; end; else do; p = addr (md.mde_entry (md.last_poll_chan)); p -> mde.next_poll_chan = subchan; md.last_poll_chan = subchan; end; mde.next_poll_chan = 0; return; end queue_poll; process_polls: proc; if md.output_in_progress then return; if md.poll_in_progress then return; if md.first_poll_chan ^= 0 then do; subchan = md.first_poll_chan; mdep = addr (md.mde_entry (subchan)); md.first_poll_chan = mde.next_poll_chan; if md.first_poll_chan = 0 then md.last_poll_chan = 0; md.poll_in_progress = "1"b; call poll; return; end; if md.first_write_chan = 0 then call general_poll; return; end process_polls; %page; /* Internal procedure to handle poll/select addressing and initiation */ poll: proc; dcl auto_poll fixed bin init (0); dcl cont char (1); /* Controller */ dcl dev char (1); /* The device */ dev = mde.device_address; poll_join: line_ctl.op = SET_POLLING_ADDR; cont = md.controller_poll_address; if dev ^= md.last_poll_address then do; md.last_poll_address = dev; select_join: line_ctl.val = 0; valchar.data_len = 4; substr (valchar.data, 1, 1) = cont; substr (valchar.data, 2, 1) = cont; substr (valchar.data, 3, 1) = dev; substr (valchar.data, 4, 1) = dev; call channel_manager$control (md.devx, "line_control", addr (line_ctl), code); end; if line_ctl.op = SET_POLLING_ADDR then do; line_ctl.op = START_POLL; line_ctl.val = 0; line_ctl.val (1) = auto_poll; call channel_manager$control (md.devx, "line_control", addr (line_ctl), code); end; return; general_poll: entry; dev = md.general_poll_address; auto_poll = 1; go to poll_join; select: entry; dev = mde.device_address; if dev = md.last_select_address then return; md.last_select_address = dev; cont = md.controller_select_address; line_ctl.op = SET_SELECT_ADDR; go to select_join; end poll; %page; /* Handle control function queue */ queue_control: proc; dcl p ptr; if mde.control_queued then return; if md.first_control_chan = 0 then do; md.first_control_chan = subchan; md.last_control_chan = subchan; end; else do; p = addr (md.mde_entry (md.last_control_chan)); p -> mde.next_control_chan = subchan; md.last_control_chan = subchan; end; mde.next_control_chan = 0; mde.control_queued = "1"b; return; end queue_control; %page; /* Setup message to perform control operation on the next channel */ setup_control_chan: proc; dcl prev_subchan fixed bin; dcl p ptr; prev_subchan = 0; subchan = md.first_control_chan; search_control_queue: if subchan = 0 then do; mdep = null (); return; end; mdep = addr (md.mde_entry (subchan)); if mde.write_chain_ptr ^= null () then do; /* Must finish output first */ prev_subchan = subchan; subchan = mde.next_control_chan; go to search_control_queue; end; call tty_space_man$get_buffer (mde.devx, 16, OUTPUT, blockp); /* To build msg */ if blockp = null () then do; md.needs_space = md.needs_space + 1; call tty_space_man$needs_space (md.devx); mdep = null (); return; end; if prev_subchan = 0 then do; /* We were first in queue */ md.first_control_chan = mde.next_control_chan; if md.first_control_chan = 0 then md.last_control_chan = 0; end; else do; p = addr (md.mde_entry (prev_subchan)); p -> mde.next_control_chan = mde.next_control_chan; if md.last_control_chan = subchan then md.last_control_chan = prev_subchan; end; mde.next_control_chan = 0; mde.control_queued = "0"b; call build_header; /* Build header with kybd restore */ call check_buffer_tally (2); buffer.chars (buffer.tally) = md.ic; /* Insert cursor */ buffer.chars (buffer.tally + 1) = md.etx; /* Finish msg */ buffer.tally = buffer.tally + 2; mde.write_chain_ptr = blockp; /* Write this chain */ return; end setup_control_chan; %page; /* Internal procedure to crash the multiplexer */ crash_mpx: proc; dcl loaded bit (1); loaded = md.loaded; md.loaded, md.loading = "0"b; if loaded then do subchan = 1 to md.nchan; /* Was loaded at time of crash */ mdep = addr (md.mde_entry (subchan)); mde.listen, mde.dialed = "0"b; call reset_channel; call channel_manager$interrupt (mde.devx, CRASH, "0"b); end; return; end crash_mpx; dialup_channel: proc; mde.position = 0; if ^mde.printer then do; mde.erase_req = "1"b; call queue_control; end; unspec (dialup_info) = "0"b; dialup_info.line_type = md.line_type; dialup_info.baud_rate = md.baud_rate; dialup_info.max_buf_size = md.max_buf_size; dialup_info.buffer_pad = md.buffer_pad + 4; dialup_info.receive_mode_device = ^mde.printer; call channel_manager$interrupt (mde.devx, DIALUP, unspec (dialup_info)); mde.dialed = "1"b; call channel_manager$interrupt (mde.devx, SEND_OUTPUT, "0"b); return; end dialup_channel; reset_channel: proc; dcl (p, q) ptr; dcl loop_count fixed bin; dcl save_subchan fixed bin; if mde.write_chain_ptr = null () then return; p = mde.write_chain_ptr; mde.write_chain_ptr = null (); call tty_space_man$free_chain (mde.devx, OUTPUT, p); if ^mde.write_queued then return; if md.first_write_chan = 0 then goto reset_channel_failed; /* Wonder where the write went */ if md.first_write_chan = subchan then do; /* First in chain */ md.first_write_chan = mde.next_write_chan; if md.first_write_chan = 0 then md.last_write_chan = 0; end; else do; loop_count = 0; q = addr (md.mde_entry (md.first_write_chan)); /* Find channel before this one */ save_subchan = md.first_write_chan; do while (q -> mde.next_write_chan ^= subchan); save_subchan = q -> mde.next_write_chan; q = addr (md.mde_entry (q -> mde.next_write_chan)); /* Make sure we are not looping */ if loop_count > md.nchan | save_subchan = 0 then goto reset_channel_failed; loop_count = loop_count + 1; end; q -> mde.next_write_chan = mde.next_write_chan; if subchan = md.last_write_chan then md.last_write_chan = save_subchan; end; reset_channel_restart: mde.next_write_chan = 0; mde.write_queued = "0"b; mde.end_of_page = "0"b; if ^md.loaded | ^mde.dialed then return; call channel_manager$interrupt (mde.devx, SEND_OUTPUT, "0"b); return; reset_channel_failed: call syserr (BEEP, "^a: Could not find queued write on ^a.", name, md.name); goto reset_channel_restart; end reset_channel; /* Test the current buffer for room for additional characters */ check_buffer_tally: proc (n); dcl n fixed bin; if (buffer.tally + n) > (max_buffer_tally (buffer.size_code) - md.buffer_pad) then call syserr (SYSERR_CRASH_SYSTEM, "^a: Buffer tally error on ^a.", name, md.name); else return; end check_buffer_tally; %page; /* This entry is called when mpx_data is being built to set the address of the translation table */ /* This is necessary because the actual data is in the text of this module, but is used by priv_ibm3270_mpx */ set_address_table: entry (arg_mdp); mdp = arg_mdp; if md.ascii then md.address_tablep = addr (ascii_address_table); else md.address_tablep = addr (ebcdic_address_table); return; /* Initialization entry. Called by priv_ibm3270_mpx once per Multics bootload the first time an ibm3270 multiplexer is initialized. */ init: entry; et_undefined_order_request = error_table_$undefined_order_request; et_improper_data_format = error_table_$improper_data_format; et_noalloc = error_table_$noalloc; et_bad_mode = error_table_$bad_mode; call wire_proc$wire_me; return; /* Entry to dialup a subchannel */ dialup: entry (arg_mdp, arg_subchan); mdp = arg_mdp; subchan = arg_subchan; mdep = addr (md.mde_entry (subchan)); call dialup_channel; return; /* Entry called at bootload time to kick off general polling */ start_general_poll: entry (arg_mdp); mdp = arg_mdp; mdep = null (); /* A precaution */ call general_poll; return; /* Entry to crash the multiplexer */ crash: entry (arg_mdp); mdp = arg_mdp; call crash_mpx; return; %page; /* BEGIN MESSAGE DOCUMENTATION Message: ibm3270_mpx: Unrecognized interrupt for CHANNEL. INT_TYPE INTERRUPT_INFO S: $info T: $run M: An interrupt was received from the FNP which does not have a defined action. The type of interrupt received is INT_TYPE and the information supplied with the interrupt is INTERRUPT_INFO. A: $inform Message: ibm3270_mpx: Unrecognized input for CHANNEL: DATA S: $info T: $run M: DATA received from CHANNEL does not meet certain format requirements. This message is only displayed if in debug mode. A: $inform Message: ibm3270_mpx: Input for illegal device address DEV_ADDRESS on CHANNEL S: $info T: $run M: Received input for a device whose address (DEV_ADDRESS) is not in the channel map for CHANNEL. A: $inform Message: ibm3270_mpx: Input for unconfigured device DEV_ADDRESS on CHANNEL S: $info T: $run M: Received input for a device (DEV_ADDRESS) whose subchannel was <= 0 in the channel map for CHANNEL. A: $inform Message: ibm3270_mpx: Attempt to queue write while write queued CHANNEL.SUBCHANNEL S: $log T: $run M: An attempt was made to queue a write while a previous write was still queued. A dump of the subchannel entry as defined by the mde structure in ibm3270_mpx_data.incl.pl1 is included with this message. A: $ignore Message: ibm3270_mpx: Error trimming buffer chain for CHANNEL S: $crash T: $run M: An inconsistency was found while trimming the buffer chain for CHANNEL which is stored in tty_buf. A: $inform Message: ibm3270_mpx: Could not find queued write on CHANNEL. S: $beep T: $run M: The reset_channel internal procedure could not find a queued write on CHANNEL or it detected itself looping in releasing queued writes for CHANNEL. Will attempt to continue. A: $inform Message: ibm3270_mpx: Buffer tally error on CHANNEL. S: $crash T: $run M: An attempt was made to add characters to a buffer which should have fit but couldn't. A: $inform END MESSAGE DOCUMENTATION */ %page; %include tty_buf; %page; %include mcs_interrupt_info; %page; %include channel_manager_dcls; %page; %include tty_space_man_dcls; %page; %include tty_buffer_block; %page; %include mcs_modes_change_list; %page; %include bisync_line_data; %page; %include ibm3270_mpx_load_data; %page; %include ibm3270_mpx_data; %page; %include ibm3270_meters; %page; %include syserr_binary_def; end ibm3270_mpx; */ ----------------------------------------------------------- Historical Background This edition of the Multics software materials and documentation is provided and donated to Massachusetts Institute of Technology by Group Bull including Bull HN Information Systems Inc. as a contribution to computer science knowledge. This donation is made also to give evidence of the common contributions of Massachusetts Institute of Technology, Bell Laboratories, General Electric, Honeywell Information Systems Inc., Honeywell Bull Inc., Groupe Bull and Bull HN Information Systems Inc. to the development of this operating system. Multics development was initiated by Massachusetts Institute of Technology Project MAC (1963-1970), renamed the MIT Laboratory for Computer Science and Artificial Intelligence in the mid 1970s, under the leadership of Professor Fernando Jose Corbato. Users consider that Multics provided the best software architecture for managing computer hardware properly and for executing programs. Many subsequent operating systems incorporated Multics principles. Multics was distributed in 1975 to 2000 by Group Bull in Europe , and in the U.S. by Bull HN Information Systems Inc., as successor in interest by change in name only to Honeywell Bull Inc. and Honeywell Information Systems Inc. . ----------------------------------------------------------- Permission to use, copy, modify, and distribute these programs and their documentation for any purpose and without fee is hereby granted,provided that the below copyright notice and historical background appear in all copies and that both the copyright notice and historical background and this permission notice appear in supporting documentation, and that the names of MIT, HIS, Bull or Bull HN not be used in advertising or publicity pertaining to distribution of the programs without specific prior written permission. Copyright 1972 by Massachusetts Institute of Technology and Honeywell Information Systems Inc. Copyright 2006 by Bull HN Information Systems Inc. Copyright 2006 by Bull SAS All Rights Reserved */