?? RIGHT := 110 ??
?? TITLE := 'NOS/VE Tape Scanner Management Routines' ??
MODULE iom$tape_scanner;

?? NEWTITLE := 'Global Declarations Referenced by This Module', EJECT ??
?? PUSH (LISTEXT := ON) ??
*copyc amt$label_type
*copyc bat$block_header
*copyc cmt$element_capabilities
*copyc cyd$run_time_error_condition
*copyc dme$tape_errors
*copyc dmt$error_condition_codes
*copyc gft$system_file_identifier
*copyc fst$ansi_vol1_label
*copyc fst$path_handle_name
*copyc ife$error_codes
*copyc ioc$tape_retry_limits
*copyc ioe$tape_io_conditions
*copyc ioe$tape_io_conditions
*copyc iot$no_of_tape_units
*copyc iot$io_id
*copyc iot$tape_io_status
*copyc jmt$system_supplied_name
*copyc jmt$user_supplied_name
*copyc ofd$type_definition
*copyc ofe$error_codes
*copyc ofp$report_status_error
*copyc oss$task_shared
*copyc oss$task_private
*copyc ost$caller_identifier
*copyc ost$name
*copyc ost$status
*copyc ost$wait
*copyc pmd$system_log_interface
*copyc pmt$condition
*copyc pmt$established_handler
*copyc rmd$tape_declarations
*copyc rmd$volume_declarations
*copyc rme$condition_codes
*copyc tmt$system_task_id
?? POP ??

*copyc avp$configuration_administrator
*copyc avp$removable_media_operator
*copyc avp$system_displays
*copyc cmp$get_element_name_via_lun
*copyc cmp$lock_lun_entry
*copyc cmp$process_state_change
*copyc cmp$state_change_pending
*copyc cmp$unlock_lun_entry
*copyc iop$any_task_waiting_assignment
*copyc iop$change_tape_scan_freq_113
*copyc iop$initialize_tape_ud
*copyc iop$rdy_task_waiting_assignment
*copyc iop$read_lock_tusl_entry
*copyc iop$read_tape_scan
*copyc iop$rewind_tape_scan
*copyc iop$tape_initialize_unit_scan
*copyc iop$tape_internal_request_stat
*copyc iop$tape_terminate_io_scan
*copyc iop$write_unlock_tusl_entry
*copyc osp$establish_condition_handler
*copyc osp$format_message
*copyc osp$generate_log_message
*copyc osp$set_status_abnormal
*copyc osp$set_status_from_condition
*copyc osp$translate_bytes
*copyc osp$verify_system_privilege
*copyc pmp$continue_to_cause
*copyc pmp$delay
*copyc pmp$log
*copyc pmp$log_ascii
*copyc pmp$wait
*copyc tmp$save_system_task_id

*copyc osv$ebcdic_to_ascii
*copyc osv$task_private_heap
*copyc oss$task_private
*copyc cmv$logical_pp_table_p
*copyc cmv$logical_unit_table
*copyc iov$number_of_tape_units
*copyc iov$tape_completion_q_table
*copyc iov$tape_scan_frequency
*copyc iov$tusl_p

?? TITLE := 'Global Declarations Declared by This Module', EJECT ??

  CONST
    expected_io_completion_time = 2 * 1000000 {microseconds}, {expect tape I/O completion in < 2 seconds}
    expected_tape_scan_time = 2 {seconds}, {expect all tape units to be scanned in < 2 seconds}
    max_lock_attempts = 10,
    max_tusl_entry_lock_attempts = 30,
    one_second = 1000 {milliseconds},
    one_tenth_second = 100 {milliseconds},
    timeout_limit = 5 * 60 * 1000000 {microseconds}; {5 minute timeout for response to tape request}

?? PUSH (LIST := ON) ??

?? OLDTITLE ??
?? NEWTITLE := ' iop$change_tape_scan_freq_23d ' ??
?? EJECT ??

  PROCEDURE [XDCL, #GATE] iop$change_tape_scan_freq_23d
    (    scan_frequency: integer;
     VAR status: ost$status);

    VAR
      caller_id: ost$caller_identifier;

    #CALLER_ID (caller_id);
    status.normal := TRUE;
    IF caller_id.ring > osc$tsrv_ring THEN
      IF NOT avp$configuration_administrator () THEN
        osp$set_status_abnormal ('OF', ofe$sou_not_active, 'configuration_administration', status);
        RETURN;
      IFEND;
    IFEND;
    osp$verify_system_privilege;

    iop$change_tape_scan_freq_113 (scan_frequency);

  PROCEND iop$change_tape_scan_freq_23d;

?? OLDTITLE ??
?? NEWTITLE := ' iop$fetch_tape_scan_frequency ' ??
?? EJECT ??

  PROCEDURE [XDCL, #GATE] iop$fetch_tape_scan_frequency
    (VAR scan_frequency: integer;
     VAR status: ost$status);

    VAR
      caller_id: ost$caller_identifier;

    #CALLER_ID (caller_id);
    status.normal := TRUE;
    IF caller_id.ring > osc$tsrv_ring THEN
      IF NOT (avp$configuration_administrator () OR avp$system_displays () OR
               avp$removable_media_operator ()) THEN
        osp$set_status_abnormal ('OF', ofe$sou_not_active,
             'configuration_administration, system_displays, removable_media_operation', status);
        RETURN;
      IFEND;
      osp$verify_system_privilege;
    IFEND;

    scan_frequency := iov$tape_scan_frequency;

  PROCEND iop$fetch_tape_scan_frequency;

?? OLDTITLE ??
?? NEWTITLE := 'iop$tape_scanner', EJECT ??

  PROCEDURE [XDCL, #GATE] iop$tape_scanner;

{ Purpose: This procedure contains one of the processes that provide Automatic Volume Recognition
{           of Labelled Tapes.
{
{ Flow:  The Tape Scanner is a System_Task that, once activated, will scan all unassigned/configured Tape
{        Units and then either go into a short_wait mode (periodically scan until all tape mount requests are
{        satisfied), or an indefinite_wait mode (scanner goes to sleep until another tape mount is
{        requested or an operator command activates the scanner for one scan).
{
{        The scanner is called when a tape file is opened, or by the refreshing of the Tape Status Display.
{
{        The procedure  'iop$tape_scanner' contains a CASE statement with a case_selector named
{          'next_scan_mode'. The CASE statement is the key to the flow of the scanner as indicated in the
{          following Structured English layout of the scanner.
{
{        PROCEDURE iop$tape_scanner;
{
{          TYPE
{          iot$tape_scanner_mode = (status_all_units, scan_rvl, short_wait, indefinite_wait);
{
{                 next_scan_mode: iot$tape_scanner_mode;
{                 Initialize scanner (Pass through here at deadstart or scanner restart).
{                 ALLOCATE necessary Task_Private containers (Read, Transfer_count, Array of rvsns_online.
{
{                 WHILE TRUE DO
{                   scanner_active := TRUE;
{                   next_scan_mode := status_all_units;
{
{                -->WHILE scanner_active DO
{                !
{                !
{                !    CASE next_scan_mode OF
{                !
{                !    = status_all_units =
{                !      FOR i := 1 TO max_units DO
{                !        status and read 1st record of an unassigned/ready unit.
{                !        update tape_status display entries.
{                !        set rvsn_online in array if labelled tape exits.
{                !      FOREND;
{                !
{                !      next_scan_mode := scan_rvl;
{                !      IF rvsns_online^[1] <> rmc$unspecified_vsn THEN
{                !       {Call procedure that determines if an assignment should be made,
{                !         {and that procedure will do a ready_task on the job that gets the tape assignment.
{                !      IFEND;
{                !
{                !    = scan_rvl =
{                !      tasks_are_waiting := FALSE; (task waiting for a tape mount)
{                !      call procedure to scan rvl and the procedure sets tasks_are_waiting accordingly.
{                !      IF NOT tasks_are_waiting THEN
{                !        next_scan_mode := indefinite_wait;
{                !      ELSE
{                !        next_scan_mode := short_wait;
{                !      IFEND;
{                !
{                !    = short_wait =
{                !      next_scan_mode := status_all_units;
{                !      pmp$ (10 seconds)
{                !
{                !    = indefinite_wait =
{                !      scanner_active := FALSE;
{                !
{                !    ELSE
{                !    CASEND;
{                -->WHILEND;
{                   pmp$wait(0ffffffffffff(16),0ffffffffffff(16)); sleep_mode
{                 WHILEND;

*if false
{* ... Begin debug code

    CONST
      c$mode_time_stamp_count = 10,
      c$status_all_units_time_stamps1 = 10 * 12, {10 entries for all 12 units
      c$status_all_units_time_stamps2 = 10 * 6;  {10 entries for all "up" units

    TYPE
      t$mode_time_stamps = array [iot$tape_scanner_mode] of array [0 .. c$mode_time_stamp_count - 1]
            of integer,
      t$mode_time_stamp_index = array [iot$tape_scanner_mode] of 0..0ff(16),
      t$status_all_units_time_stamps1 = array [0 ..c$status_all_units_time_stamps1 - 1] of
            t$status_all_units_time_stamp,
      t$status_all_units_time_stamps2 = array [0 ..c$status_all_units_time_stamps2 - 1] of
            t$status_all_units_time_stamp,
      t$status_all_units_time_stamp = record
        lun: iot$logical_unit,
        clock: integer,
        disabled: boolean,
      recend,
      t$ts_time_stamps = record
        mode_time_stamp_index: t$mode_time_stamp_index,
        mode_time_stamps: t$mode_time_stamps,
        time_stamps1_index: 0..0ff(16),
        time_stamps2_index: 0..0ff(16),
        status_all_units_time_stamps1: t$status_all_units_time_stamps1,
        status_all_units_time_stamps2: t$status_all_units_time_stamps2,
      recend;

    VAR
      iov$ts_time_stamps_p: [XDCL, #GATE, oss$task_private] ^t$ts_time_stamps := NIL;

{* ... End debug code
*ifend

    TYPE
      iot$tape_scanner_mode = (status_all_units, scan_rvl, short_wait, indefinite_wait);

    VAR
      all_tape_mounts_found: boolean,
      current_time: integer,
      block: iot$read_tape_description,
      block_transfer_length: ^iot$tape_transfer_count,
      byte: 0 .. 0ff(16),
      byte_pointer: ^array [1 .. 4128] of 0 .. 0ff(16),
      element_capability: cmt$element_capabilities,
      element_descriptor: cmt$element_descriptor,
      error: ost$error,
      evsn: rmt$external_vsn,
      input_buffer: ^SEQ (REP 4128 of cell),
      i: iot$no_of_tape_units,
      j: iot$no_of_tape_units,
      ignore_status: ost$status,
      io_id: iot$io_id,
      io_status: iot$tape_io_status,
      last_scan_time: integer,
      local_status: ost$status,
      lock_attempts: integer,
      logset: pmt$ascii_logset,
      lpp: 1 .. 4,
      lpp_found: boolean,
      lun: iot$logical_unit,
      lun_lock_obtained: boolean,
      lun_lock_released: boolean,
      next_scan_mode: iot$tape_scanner_mode,
      previous_unit_ready_status_p: ^array [1 .. * ] of boolean,
      rewind_write_ring_status: boolean,
      rvsns_online_p: iot$new_vsns_online,
      scanner_active: boolean,
      status: ost$status,
      tape_init_record: dmt$tape_initialization_record,
      tasks_are_waiting: boolean,
      timeout_start: integer,
      tusl_entry_locked: boolean,
      tusl_template: iot$tape_unit_status_entry,
      vol1_p: ^fst$ansi_vol1_label,
      volume_id: rmt$recorded_vsn;

?? NEWTITLE := 'clean_up', EJECT ??

    PROCEDURE clean_up
      (    terminate_io_scan: boolean;
           issue_log_message: boolean);

    VAR
      local_status: ost$status;

      IF tusl_entry_locked THEN
        REPEAT
          iop$write_unlock_tusl_entry ({write_entry} FALSE, i, tusl_template, local_status);
          IF NOT local_status.normal AND (local_status.condition = dme$unable_to_lock_tape_table) THEN
            pmp$wait (one_second, one_second);
          IFEND;
        UNTIL local_status.normal OR (local_status.condition <> dme$unable_to_lock_tape_table);
        tusl_entry_locked := NOT local_status.normal;
        #SPOIL (tusl_entry_locked);
      IFEND;

      IF terminate_io_scan THEN
        iop$tape_terminate_io_scan (lun);
      IFEND;

      cmp$unlock_lun_entry (lun, lun_lock_released);
      lun_lock_obtained := FALSE;
      IF issue_log_message THEN
        osp$generate_log_message (logset, status, ignore_status);
      IFEND;
    PROCEND clean_up;
?? OLDTITLE ??
?? NEWTITLE := 'tape_scanner_handler', EJECT ??

    PROCEDURE tape_scanner_handler
      (    condition: pmt$condition;
           p_condition_info: ^pmt$condition_information;
           p_stack: ^ost$stack_frame_save_area;
       VAR condition_status: ost$status);

    VAR
      ignore_status: ost$status,
      local_status: ost$status;

      IF condition.selector = pmc$user_defined_condition THEN
        pmp$continue_to_cause (pmc$execute_standard_procedure, condition_status);
        RETURN; {----->
      IFEND;

      IF lun_lock_obtained THEN
        clean_up ({terminate_io_scan} TRUE, {issue_log_message} FALSE);
      IFEND;

      osp$set_status_from_condition ('  ', condition, p_stack, condition_status, local_status);
      IF NOT local_status.normal THEN
        osp$generate_log_message (logset, local_status, ignore_status);
      IFEND;

{Do we really want to eat all conditions? At least, we now emit a log message with the
{condition that brought us here. We can still add the following line if we know more:
{     pmp$continue_to_cause (pmc$execute_standard_procedure, condition_status);

    PROCEND tape_scanner_handler;
?? OLDTITLE ??
?? EJECT ??
*if false
{* ... Begin debug code
      ALLOCATE iov$ts_time_stamps_p in osv$task_private_heap^;

      iov$ts_time_stamps_p^.time_stamps1_index:= 0;
      iov$ts_time_stamps_p^.time_stamps2_index:= 0;

      FOR next_scan_mode := status_all_units TO indefinite_wait DO
        iov$ts_time_stamps_p^.mode_time_stamp_index [next_scan_mode] := 0;
        FOR byte := 0 to c$mode_time_stamp_count - 1 DO
          iov$ts_time_stamps_p^.mode_time_stamps [next_scan_mode] [byte] := 0;
          iov$ts_time_stamps_p^.mode_time_stamps [next_scan_mode] [byte] := 0;
        FOREND;
      FOREND;

      FOR byte := 0 to c$status_all_units_time_stamps1 - 1 DO
        iov$ts_time_stamps_p^.status_all_units_time_stamps1 [byte].lun := 0;
        iov$ts_time_stamps_p^.status_all_units_time_stamps1 [byte].clock := 0;
        iov$ts_time_stamps_p^.status_all_units_time_stamps1 [byte].disabled := FALSE;
      FOREND;

      FOR byte := 0 to c$status_all_units_time_stamps2 - 1 DO
        iov$ts_time_stamps_p^.status_all_units_time_stamps2 [byte].lun := 0;
        iov$ts_time_stamps_p^.status_all_units_time_stamps2 [byte].clock := 0;
        iov$ts_time_stamps_p^.status_all_units_time_stamps2 [byte].disabled := FALSE;
      FOREND;

{* ... End debug code
*ifend

    element_capability := $cmt$element_capabilities [cmc$volume_assignment];
    element_descriptor.element_type := cmc$storage_device_element;
    last_scan_time := 0;
    logset := $pmt$ascii_logset [pmc$system_log];
    lun_lock_obtained := FALSE;
    tape_init_record.density := rmc$1600;
    tusl_entry_locked := FALSE;
    #SPOIL (tusl_entry_locked);
    status.normal := TRUE;

    osp$establish_condition_handler (^tape_scanner_handler, TRUE);

    tmp$save_system_task_id (tmc$stid_tape_scanner, {critical_task} FALSE, status);
    IF NOT status.normal THEN
      osp$generate_log_message (logset, status, ignore_status);
      RETURN;
    IFEND;

{Allocate boolean array to indicate last ready status of each scanned unit.
    PUSH previous_unit_ready_status_p: [1 .. UPPERBOUND (iov$tusl_p^)];

{Allocate array of possible rmt$recorded_vsn(s) that may come online.
    PUSH rvsns_online_p: [1 .. UPPERBOUND (iov$tusl_p^) + 1];

{Allocate buffer into which the label is read.

    ALLOCATE input_buffer IN osv$task_private_heap^;

{Allocate buffer into which the byte length of the record is placed.

    ALLOCATE block_transfer_length IN osv$task_private_heap^;
    block [1].buffer_area := input_buffer;
    block [1].block_transfer_length := block_transfer_length;
    byte_pointer := block [1].buffer_area;

    WHILE TRUE DO
      scanner_active := TRUE;
      next_scan_mode := status_all_units;

{ Initialize unit_ready_status array if scanner has not been called for IOV$TAPE_SCAN_FREQUENCY seconds.

      current_time := #FREE_RUNNING_CLOCK (0);
      IF current_time > last_scan_time + ((iov$tape_scan_frequency + expected_tape_scan_time) * 1000000) THEN
        FOR i := LOWERBOUND (previous_unit_ready_status_p^) TO UPPERBOUND (previous_unit_ready_status_p^) DO
          previous_unit_ready_status_p^ [i] := FALSE;
        FOREND;
      IFEND;

{ Main scanner loop.

      WHILE scanner_active DO

*if false
{* ... Begin debug code
        byte := iov$ts_time_stamps_p^.mode_time_stamp_index [next_scan_mode];
        iov$ts_time_stamps_p^.mode_time_stamp_index [next_scan_mode] :=
            (iov$ts_time_stamps_p^.mode_time_stamp_index [next_scan_mode] + 1) mod c$mode_time_stamp_count;

        iov$ts_time_stamps_p^.mode_time_stamps [next_scan_mode] [byte] := #FREE_RUNNING_CLOCK (0);
{* ... End debug code
*ifend

        status.normal := TRUE;

        CASE next_scan_mode OF
        = status_all_units =

          j := LOWERBOUND (rvsns_online_p^);

        /status_all_units_loop/
          FOR i := 1 TO iov$number_of_tape_units DO
            status.normal := TRUE;
            lun := iov$tape_completion_q_table^ [i].lun;

*if false
{* ... Begin debug code
        byte := iov$ts_time_stamps_p^.time_stamps1_index;
        iov$ts_time_stamps_p^.time_stamps1_index :=
            (iov$ts_time_stamps_p^.time_stamps1_index + 1) mod c$status_all_units_time_stamps1;

        iov$ts_time_stamps_p^.status_all_units_time_stamps1 [byte].lun := lun;
        iov$ts_time_stamps_p^.status_all_units_time_stamps1 [byte].clock := #FREE_RUNNING_CLOCK (0);
        iov$ts_time_stamps_p^.status_all_units_time_stamps1 [byte].disabled :=
              cmv$logical_unit_table^ [lun].unit_interface_table^.unit_status.disabled;
{* ... End debug code
*ifend

            IF NOT ((cmv$logical_unit_table^ [lun].configured) AND
                  (cmv$logical_unit_table^ [lun].element_capability >= element_capability) AND
                  NOT (cmv$logical_unit_table^ [lun].status.assigned)) OR
                   (cmv$logical_unit_table^ [lun].unit_interface_table^.unit_status.disabled = TRUE) THEN
              CYCLE /status_all_units_loop/;
            IFEND;

*if false
{* ... Begin debug code
        byte := iov$ts_time_stamps_p^.time_stamps2_index;
        iov$ts_time_stamps_p^.time_stamps2_index :=
            (iov$ts_time_stamps_p^.time_stamps2_index + 1) mod c$status_all_units_time_stamps2;

        iov$ts_time_stamps_p^.status_all_units_time_stamps2 [byte].lun := lun;
        iov$ts_time_stamps_p^.status_all_units_time_stamps2 [byte].clock := #FREE_RUNNING_CLOCK (0);
        iov$ts_time_stamps_p^.status_all_units_time_stamps2 [byte].disabled :=
              cmv$logical_unit_table^ [lun].unit_interface_table^.unit_status.disabled;
{* ... End debug code
*ifend

            element_descriptor.peripheral_descriptor.element_name := iov$tusl_p^ [i].element_name;
            IF cmp$state_change_pending (element_descriptor) THEN
              CYCLE /status_all_units_loop/;
            IFEND;

            tusl_entry_locked := FALSE;
            #SPOIL (tusl_entry_locked);

          /lock_unit_entries_to_initialize/
            FOR lock_attempts := 1 TO max_lock_attempts DO
              cmp$lock_lun_entry (lun, lun_lock_obtained);
              IF NOT lun_lock_obtained THEN
                pmp$wait (one_second, one_second);
                CYCLE /lock_unit_entries_to_initialize/
              IFEND;
              iop$read_lock_tusl_entry (i, tusl_template, local_status);
              tusl_entry_locked := local_status.normal;
              #SPOIL (tusl_entry_locked);
              IF NOT local_status.normal THEN
                cmp$unlock_lun_entry (lun, lun_lock_released);
                IF local_status.condition = dme$unable_to_lock_tape_table THEN
                  pmp$wait (one_second, one_second);
                  CYCLE /lock_unit_entries_to_initialize/
                ELSE
                  EXIT /lock_unit_entries_to_initialize/;
                IFEND;
              ELSE
                EXIT /lock_unit_entries_to_initialize/;
              IFEND;
            FOREND;
            IF NOT tusl_entry_locked THEN
              CYCLE /status_all_units_loop/;
            IFEND;

{ Make sure that there is at least one PP active for this unit.

            lpp_found := FALSE;

          /search_for_active_pp/
            FOR lpp := 1 TO 4 DO
              IF tusl_template.logical_pp [lpp] > 0 THEN
                IF cmv$logical_pp_table_p^ [tusl_template.logical_pp [lpp]].flags.pp_loaded THEN
                  lpp_found := TRUE;
                  EXIT /search_for_active_pp/;
                IFEND;
              IFEND;
            FOREND /search_for_active_pp/;

{Assure chosen unit still available after window time for lock_lun_entry

            IF ((cmv$logical_unit_table^ [lun].configured) AND
{             } (cmv$logical_unit_table^ [lun].element_capability >= element_capability) AND
{             } NOT (cmv$logical_unit_table^ [lun].status.assigned) AND
{             } (tusl_template.assignment_state = ioc$not_assigned) AND
{             } (lpp_found) AND
{             } (cmv$logical_unit_table^ [lun].unit_interface_table^.unit_status.disabled = FALSE)) THEN

{Set up structures for connecting to a tape unit

              tape_init_record.logical_unit_number := lun;
              iop$initialize_tape_ud (tape_init_record, {multiple_requests_possible} FALSE, status);
              IF NOT status.normal THEN
                clean_up ({terminate_io_scan} TRUE, {issue_log_message} TRUE);
                CYCLE /status_all_units_loop/;
              IFEND;

{Connect to the tape unit.

              iop$tape_initialize_unit_scan (lun, io_id, status);
              IF NOT status.normal THEN
                clean_up ({terminate_io_scan} TRUE, {issue_log_message} TRUE);
                CYCLE /status_all_units_loop/;
              IFEND;

{Obtain tape unit status from the format attempt.

              timeout_start := #FREE_RUNNING_CLOCK (0);
              io_status.normal_completion := FALSE;
              io_status.unit_ready := FALSE;
              pmp$wait (one_tenth_second, one_tenth_second);

            /unit_status_loop/
              WHILE (#FREE_RUNNING_CLOCK (0) - timeout_start) < timeout_limit DO
                iop$tape_internal_request_stat (lun, io_id, {buf_release =} TRUE, {bid_recovery =} FALSE,
                       {bid_update =} TRUE, osc$nowait, io_status, status);
                IF NOT status.normal THEN
                  clean_up ({terminate_io_scan} TRUE, {issue_log_message} TRUE);
                  CYCLE /status_all_units_loop/;
                IFEND;
                IF io_status.io_complete THEN
                  EXIT /unit_status_loop/;
                ELSE
                  IF (#FREE_RUNNING_CLOCK (0) - timeout_start) < expected_io_completion_time THEN
                    pmp$wait (one_tenth_second, one_tenth_second);
                    CYCLE /unit_status_loop/;
                  ELSE
                    pmp$wait (one_second, one_second);
                    CYCLE /unit_status_loop/;
                  IFEND;
                IFEND;
              WHILEND /unit_status_loop/;

              IF NOT io_status.io_complete THEN { unit is inoperable, so call CM to DOWN it }
                clean_up ({terminate_io_scan} FALSE, {issue_log_message} TRUE);
                down_tape_unit (lun);
                CYCLE /status_all_units_loop/;
              IFEND;

{Check if tape unit has ready status and capable of reading a tape record.

              IF NOT io_status.unit_ready THEN
                previous_unit_ready_status_p^ [i] := FALSE;
                IF io_status.normal_completion THEN
                  tusl_template.evsn := rmc$unspecified_vsn;
                  tusl_template.rvsn := rmc$unspecified_vsn;
                  tusl_template.unit_ready := FALSE;
                  tusl_template.read_error := FALSE;
                  REPEAT
                    iop$write_unlock_tusl_entry ({write_entry} TRUE, i, tusl_template, local_status);
                    IF NOT local_status.normal AND
                          (local_status.condition = dme$unable_to_lock_tape_table) THEN
                      pmp$wait (one_second, one_second);
                    IFEND;
                  UNTIL local_status.normal OR (local_status.condition <> dme$unable_to_lock_tape_table);
                  tusl_entry_locked := NOT local_status.normal;
                  #SPOIL (tusl_entry_locked);
                  clean_up ({terminate_io_scan} TRUE, {issue_log_message} FALSE);
                ELSE { unit is inoperable, so call CM to DOWN it }
                  clean_up ({terminate_io_scan} TRUE, {issue_log_message} FALSE);
                  down_tape_unit (lun);
                IFEND;
                CYCLE /status_all_units_loop/;
              ELSE {unit ready}
                IF NOT io_status.normal_completion THEN
                  clean_up ({terminate_io_scan} TRUE, {issue_log_message} FALSE);
                  down_tape_unit (lun);
                  CYCLE /status_all_units_loop/;
                IFEND;
              IFEND;

{ The following code is reached only if the unit is ready and no error occurred on rewind function.

              IF ((previous_unit_ready_status_p^ [i]) AND (tusl_template.unit_ready)) OR
                    (io_status.unit_busy) THEN
                IF NOT io_status.unit_busy AND (tusl_template.detected_tape_characteristics.label_type =
                      amc$labelled) THEN
                  rvsns_online_p^ [j].rvsn := tusl_template.rvsn;
                  rvsns_online_p^ [j].unit_type := cmv$logical_unit_table^ [lun].
                        unit_interface_table^.unit_type;
                  j := j + 1;
                IFEND;
                clean_up ({terminate_io_scan} TRUE, {issue_log_message} FALSE);
                CYCLE /status_all_units_loop/;
              IFEND;

              previous_unit_ready_status_p^ [i] := TRUE;
              rewind_write_ring_status := io_status.write_ring; { save ring status from rewind}

            /read_label/
              BEGIN
                byte := byte_pointer^ [1];      { Force buffer pages to memory
                byte := byte_pointer^ [4128];
                iop$read_tape_scan (lun, FALSE, 4128, ^block, 1, io_id, status);
                IF status.normal THEN
                  REPEAT
                    iop$write_unlock_tusl_entry ({write_entry} FALSE, i, tusl_template, local_status);
                    IF NOT local_status.normal AND
                          (local_status.condition = dme$unable_to_lock_tape_table) THEN
                      pmp$wait (one_second, one_second);
                    IFEND;
                  UNTIL local_status.normal OR (local_status.condition <> dme$unable_to_lock_tape_table);
                  tusl_entry_locked := NOT local_status.normal;
                  #SPOIL (tusl_entry_locked);
                  iop$tape_internal_request_stat (lun, io_id, {buf_release =} TRUE, {bid_recovery =} FALSE,
                        {bid_update =} TRUE,osc$wait, io_status, status);
                  IF NOT status.normal THEN
                    clean_up ({terminate_io_scan} TRUE, {issue_log_message} TRUE);
                    CYCLE /status_all_units_loop/;
                  IFEND;

{ Regain the TUSL lock and make sure the unit was not manually assigned during the read.

                /lock_tusl_entry_after_read/
                  FOR lock_attempts := 1 TO max_lock_attempts DO
                    iop$read_lock_tusl_entry (i, tusl_template, local_status);
                    IF NOT local_status.normal AND
                          (local_status.condition = dme$unable_to_lock_tape_table) THEN
                      pmp$wait (one_second, one_second);
                    ELSE
                      EXIT /lock_tusl_entry_after_read/;
                    IFEND;
                  FOREND;
                  tusl_entry_locked := local_status.normal;
                  #SPOIL (tusl_entry_locked);
                  IF NOT tusl_entry_locked THEN
                    clean_up ({terminate_io_scan} TRUE, {issue_log_message} TRUE);
                    CYCLE /status_all_units_loop/;
                  IFEND;
                  tusl_template.unit_ready := TRUE;
                  tusl_template.read_error := FALSE;
                  IF NOT (tusl_template.assignment_state = ioc$not_assigned) THEN
                    EXIT /read_label/;  { Release tape and exit, tape is assigned.
                  IFEND;
                  IF NOT (io_status.normal_completion) THEN
                    IF NOT ((io_status.completion_code = ioc$blank_tape) OR
                          (io_status.completion_code = ioc$indeterminate) OR
                          (io_status.completion_code = ioc$tape_medium_failure) OR
                          (io_status.completion_code = ioc$tapemark_read) OR
                          (io_status.completion_code = ioc$not_capable_of_density) OR
                          (io_status.completion_code = ioc$alert_condition_encountered) OR
                          (io_status.completion_code = ioc$unable_to_set_agc)) THEN
                      clean_up ({terminate_io_scan} TRUE, {issue_log_message} FALSE);
                      IF io_status.unit_ready THEN { down unit }
                        down_tape_unit (lun);
                      IFEND;
                      CYCLE /status_all_units_loop/;
                    ELSEIF NOT ((io_status.completion_code = ioc$blank_tape) OR
                          (io_status.completion_code = ioc$tapemark_read) OR
                          (io_status.completion_code = ioc$alert_condition_encountered)) THEN
                      tusl_template.read_error := TRUE;
                    IFEND;

                    {UPDATE TUSL ENTRY - INDICATE UNLABELLED
                    tusl_template.rvsn := rmc$unspecified_vsn;
                    tusl_template.detected_tape_characteristics.label_type := amc$unlabelled;
                    tusl_template.detected_tape_characteristics.character_set := amc$ascii;
                    tusl_template.detected_tape_characteristics.write_ring := rewind_write_ring_status;
                    EXIT /read_label/;
                  IFEND;
                ELSE
                  clean_up ({terminate_io_scan} TRUE, {issue_log_message} TRUE);
                  CYCLE /status_all_units_loop/;
                IFEND;

                IF (block_transfer_length^.length >= 80) THEN

{Check if this is a labelled tape.

                  RESET input_buffer;
                  NEXT vol1_p IN input_buffer;

                  IF (vol1_p^.label_identifier = 'VOL') AND (vol1_p^.label_number = '1') THEN
                    rvsns_online_p^ [j].rvsn := vol1_p^.volume_identifier;
                    rvsns_online_p^ [j].unit_type := cmv$logical_unit_table^ [lun].
                          unit_interface_table^.unit_type;
                    j := j + 1;
                    {UPDATE TSL TO INDICATE A LABELLED TAPE FOR THIS LUN
                    tusl_template.rvsn := vol1_p^.volume_identifier;
                    tusl_template.detected_tape_characteristics.label_type := amc$labelled;
                    tusl_template.detected_tape_characteristics.character_set := amc$ascii;
                    tusl_template.detected_tape_characteristics.write_ring := io_status.write_ring;

                  ELSE
                    osp$translate_bytes (vol1_p, 80, vol1_p, 80, ^osv$ebcdic_to_ascii, error);

                    IF (vol1_p^.label_identifier = 'VOL') AND (vol1_p^.label_number = '1') THEN
                      rvsns_online_p^ [j].rvsn := vol1_p^.volume_identifier;
                      rvsns_online_p^ [j].unit_type := cmv$logical_unit_table^ [lun].
                            unit_interface_table^.unit_type;
                      j := j + 1;

                      {UPDATE TSL TO INDICATE A LABELLED TAPE FOR THIS LUN
                      tusl_template.rvsn := vol1_p^.volume_identifier;
                      tusl_template.detected_tape_characteristics.label_type := amc$labelled;
                      tusl_template.detected_tape_characteristics.character_set := amc$ebcdic;
                      tusl_template.detected_tape_characteristics.write_ring := io_status.write_ring;

                    ELSE
                      {UPDATE TUSL ENTRY - INDICATE UNLABELLED

                      tusl_template.rvsn := rmc$unspecified_vsn;
                      tusl_template.detected_tape_characteristics.label_type := amc$unlabelled;
                      tusl_template.detected_tape_characteristics.character_set := amc$ascii;
                      tusl_template.detected_tape_characteristics.write_ring := io_status.write_ring;

                    IFEND;
                  IFEND;
                ELSE
                  {UPDATE TUSL ENTRY - INDICATE UNLABELLED

                  tusl_template.rvsn := rmc$unspecified_vsn;
                  tusl_template.detected_tape_characteristics.label_type := amc$unlabelled;
                  tusl_template.detected_tape_characteristics.character_set := amc$ascii;
                  tusl_template.detected_tape_characteristics.write_ring := io_status.write_ring;

                IFEND;
                EXIT /read_label/;
              END /read_label/;

              iop$rewind_tape_scan (lun, io_id, status);
              iop$tape_internal_request_stat (lun, io_id, {buf_release =} TRUE, {bid_recovery =} FALSE,
                    {bid_update =} TRUE, osc$wait, io_status, status);
              tusl_template.detected_tape_characteristics.density := io_status.unit_density;

              REPEAT
                iop$write_unlock_tusl_entry ({write_entry} TRUE, i, tusl_template, local_status);
                IF NOT local_status.normal AND
                      (local_status.condition = dme$unable_to_lock_tape_table) THEN
                  pmp$wait (one_second, one_second);
                IFEND;
              UNTIL local_status.normal OR (local_status.condition <> dme$unable_to_lock_tape_table);
              tusl_entry_locked := NOT local_status.normal;
              #SPOIL (tusl_entry_locked);
              clean_up ({terminate_io_scan} TRUE, {issue_log_message} TRUE);
            ELSE

              REPEAT
                iop$write_unlock_tusl_entry ({write_entry} FALSE, i, tusl_template, local_status);
                IF NOT local_status.normal AND
                      (local_status.condition = dme$unable_to_lock_tape_table) THEN
                  pmp$wait (one_second, one_second);
                IFEND;
              UNTIL local_status.normal OR (local_status.condition <> dme$unable_to_lock_tape_table);
              tusl_entry_locked := NOT local_status.normal;
              #SPOIL (tusl_entry_locked);
              cmp$unlock_lun_entry (lun, lun_lock_released);
              lun_lock_obtained := FALSE;
            IFEND;

          FOREND /status_all_units_loop/;

          rvsns_online_p^ [j].rvsn := rmc$unspecified_vsn;
          next_scan_mode := scan_rvl;
          IF rvsns_online_p^ [LOWERBOUND (rvsns_online_p^)].rvsn <> rmc$unspecified_vsn THEN
            REPEAT
              iop$rdy_task_waiting_assignment (rvsns_online_p, status);
              IF NOT status.normal AND (status.condition = dme$unable_to_lock_tape_table) THEN
                pmp$wait (one_second, one_second);
              IFEND;
            UNTIL status.normal OR (status.condition <> dme$unable_to_lock_tape_table);
            IF NOT status.normal THEN
              osp$generate_log_message (logset, status, ignore_status);
            IFEND;
          IFEND;

        = scan_rvl =

          pmp$wait (one_second, one_second);
          tasks_are_waiting := FALSE;
          REPEAT
            iop$any_task_waiting_assignment (tasks_are_waiting, status);
            IF NOT status.normal AND (status.condition = dme$unable_to_lock_tape_table) THEN
              pmp$wait (one_second, one_second);
            IFEND;
          UNTIL status.normal OR (status.condition <> dme$unable_to_lock_tape_table);
          IF NOT status.normal THEN
            osp$generate_log_message (logset, status, ignore_status);
          IFEND;
          IF tasks_are_waiting THEN
            next_scan_mode := short_wait;
          ELSE
            next_scan_mode := indefinite_wait;
          IFEND;

        = short_wait =
          next_scan_mode := status_all_units;
          pmp$wait (iov$tape_scan_frequency * 1000, iov$tape_scan_frequency * 1000);

        = indefinite_wait =
          scanner_active := FALSE;
        ELSE
        CASEND;
      WHILEND;

      last_scan_time := #FREE_RUNNING_CLOCK (0);
      pmp$wait (0ffffffffffff(16), 0ffffffffffff(16));

    WHILEND;

  PROCEND iop$tape_scanner;

?? TITLE := 'down_tape_unit', EJECT ??

  PROCEDURE down_tape_unit
    (    lun: iot$logical_unit);

    VAR
      element_descriptor: cmt$element_descriptor,
      element_name: cmt$element_name,
      ignore_status: ost$status,
      logset: pmt$ascii_logset,
      status: ost$status;

    status.normal := TRUE;
    logset := $pmt$ascii_logset [pmc$system_log];

  /main_program/
    BEGIN

      cmp$get_element_name_via_lun (lun, element_name, status);
      IF NOT status.normal THEN
        EXIT /main_program/;
      IFEND;
      element_descriptor.element_type := cmc$storage_device_element;
      element_descriptor.peripheral_descriptor.use_logical_identification := TRUE;
      element_descriptor.peripheral_descriptor.element_name := element_name;
      cmp$process_state_change ({tape_element=} TRUE, TRUE, element_descriptor,
            {System critical element} FALSE, cmc$on, cmc$down, status);
      IF NOT status.normal THEN
        EXIT /main_program/;
      IFEND;

    END /main_program/;

    IF NOT status.normal THEN
      osp$generate_log_message (logset, status, ignore_status);
    IFEND;

  PROCEND down_tape_unit;

MODEND iom$tape_scanner;
