?? RIGHT := 110 ??
?? NEWTITLE := 'IO: Monitor Manage PP Process' ??
MODULE iom$mtr_manage_pp_process;
?? RIGHT := 110 ??

{ PURPOSE:
{   This module contains the procedures used to manage the PP process in Monitor mode.

?? NEWTITLE := 'Global Declarations Referenced by This Module', EJECT ??
?? PUSH (LISTEXT := ON) ??
*copyc oss$mainframe_wired_literal
*copyc ioc$unsolicited_response_codes
*copyc ioe$st_errors
*copyc dft$moved_fs_response_buffer
*copyc iot$command
*copyc iot$disk_request
*copyc iot$idle_resume_action
*copyc iot$io_request
*copyc iot$moved_response_buffer
*copyc iot$pp_response
*copyc iot$request_heap_map
?? POP ??
*copyc dsp$mtr_process_hung_pp
*copyc iop$find_empty_request
*copyc iop$process_disk_response
*copyc iop$process_idle_response
*copyc iop$queue_pp_request
*copyc mtp$error_stop
*copyc mtp$set_status_abnormal
*copyc cmv$logical_pp_table_p
*copyc cmv$max_number_of_pp
*copyc dsv$turn_dft_logging_off
*copyc i#move
*copyc i#test_set_bit

  VAR
    iov$empty_requests: [XREF] ^iot$io_request,
    iov$empty_requests_end: [XREF] ^^iot$io_request,
    iov$empty_request_count: [XREF] integer,
    iov$request_heap_map: [XREF] iot$request_heap_map;

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

{ PURPOSE:
{   This procedure handshakes with all supported PPs.  If it detects a hung PP it initiates the Reload
{ process.

  PROCEDURE [XDCL] iop$check_active_pps;

    VAR
      count: 0 .. 0ff(16),
      current_time: integer,
      entry_p: ^cmt$logical_pp_table_entry,
      pp: iot$pp_number,
      previously_set: boolean,
      time: integer,
      timeout: integer;

    IF dsv$turn_dft_logging_off OR (cmv$logical_pp_table_p = NIL) THEN
      RETURN; {----->
    IFEND;

    current_time := #FREE_RUNNING_CLOCK (0);

    { Check all PPs.

  /check_active_pp/
    FOR pp := 1 TO cmv$max_number_of_pp DO
      entry_p := ^cmv$logical_pp_table_p^ [pp];
      IF NOT entry_p^.flags.pp_handshaking_supported THEN
        CYCLE /check_active_pp/; {----->
      IFEND;

      IF NOT entry_p^.flags.configured OR NOT entry_p^.flags.pp_loaded OR entry_p^.flags.disabled OR
            entry_p^.flags.pp_hung THEN
        CYCLE /check_active_pp/; {----->
      IFEND;

      IF entry_p^.pp_info.pp_interface_table_p = NIL THEN
        CYCLE /check_active_pp/; {----->
      IFEND;

      { Do not handshake with the slave.  The Master will do the handshaking with the slave.

      IF (entry_p^.pp_info.pp_type = cmc$lpt_disk_pp_type) OR
            (entry_p^.pp_info.pp_type = cmc$lpt_tape_pp_type) THEN
        IF (entry_p^.pp_info.logical_partner_pp_index > 0) AND
              entry_p^.pp_info.pp_communication_buffer_p^.slave THEN
          CYCLE /check_active_pp/; {----->
        IFEND;
      IFEND;

      { Check if the PP has NOT cleared the active_check flag.

      IF entry_p^.pp_info.pp_interface_table_p^.active_check THEN
        IF (current_time - entry_p^.active_check.timestamp) > entry_p^.active_check.timeout THEN
          dsp$mtr_process_hung_pp (pp);
        IFEND;
      ELSE

        { The PP has cleared the active_check flag.  Interlock the PP interface table and set
        { the active_check flag.

        time := #FREE_RUNNING_CLOCK (0);
        timeout := time + 2000000;
        count := 0;
        REPEAT
          i#test_set_bit (^entry_p^.pp_info.pp_interface_table_p^, ioc$pp_interface_table_lock_bit,
                previously_set);
          count := count + 1;
          IF count >= 100 THEN
            time := #FREE_RUNNING_CLOCK (0);
            count := 0;
          IFEND;
        UNTIL (NOT previously_set) OR (time > timeout);

        IF NOT previously_set THEN
          entry_p^.pp_info.pp_interface_table_p^.active_check := TRUE;
          entry_p^.pp_info.pp_interface_table_p^.lock := FALSE;
          entry_p^.active_check.timestamp := current_time;
        ELSE
          dsp$mtr_process_hung_pp (pp);
        IFEND;
      IFEND;
    FOREND /check_active_pp/;

  PROCEND iop$check_active_pps;
?? OLDTITLE ??
?? NEWTITLE := 'iop$check_idle_pps', EJECT ??

{ PURPOSE:
{   This procedure checks the idle status for PPs that do not support the new method of idle/resume requests.

  PROCEDURE [XDCL] iop$check_idle_pps;

    VAR
      completed_request_p: ^iot$disk_request,
      count: 0 .. 0ff(16),
      current_response_p: ^cell,
      in_pointer_offset: iot$response_buffer_offset,
      moved_response_buffer: iot$moved_response_buffer,
      one_word_response_p: ^dft$fs_pp_response,
      out_pointer_offset: integer,
      pp: iot$pp_number,
      pp_interface_table_p: ^iot$pp_interface_table,
      pp_response_p: ^iot$pp_response,
      previously_set: boolean,
      remain: integer,
      response_length: 0 .. 0ffff(16),
      rest: integer,
      short_response_p: ^iot$short_response,
      special_response: boolean,
      time: integer,
      timeout: integer,
      total_response_length: iot$response_buffer_offset;

    IF cmv$logical_pp_table_p = NIL THEN
      RETURN; {----->
    IFEND;

  /check_idle_pps/
    FOR pp := 1 TO cmv$max_number_of_pp DO
      IF cmv$logical_pp_table_p^ [pp].flags.pp_idle_resume_supported THEN
        CYCLE /check_idle_pps/; {----->
      IFEND;

      IF NOT cmv$logical_pp_table_p^ [pp].flags.configured OR NOT cmv$logical_pp_table_p^ [pp].flags.
            pp_loaded OR cmv$logical_pp_table_p^ [pp].flags.disabled THEN
        CYCLE /check_idle_pps/; {----->
      IFEND;

      IF cmv$logical_pp_table_p^ [pp].pp_info.pp_interface_table_p = NIL THEN
        CYCLE /check_idle_pps/; {----->
      IFEND;

      pp_interface_table_p := cmv$logical_pp_table_p^ [pp].pp_info.pp_interface_table_p;
      IF pp_interface_table_p^.inn = pp_interface_table_p^.out THEN
        CYCLE /check_idle_pps/; {----->
      IFEND;

      { Note, pp_interface_table_p^.inn must be moved to a different location because otherwise the
      { pp can change the value, and thereby, cause a problem.

      in_pointer_offset := pp_interface_table_p^.inn;
      out_pointer_offset := pp_interface_table_p^.out;
      IF in_pointer_offset > out_pointer_offset THEN
        total_response_length := in_pointer_offset - out_pointer_offset;
      ELSE
        total_response_length := pp_interface_table_p^.limit - out_pointer_offset + in_pointer_offset;
      IFEND;

      { Retrieve the pp_response.

    /retrieve_response/
      WHILE total_response_length > 0 DO
        current_response_p := ^pp_interface_table_p^.response_buffer^ [out_pointer_offset];

        { Look for short disk responses.

        IF cmv$logical_pp_table_p^ [pp].handlers.response_handler_p = ^iop$process_disk_response THEN
          short_response_p := current_response_p;
          IF short_response_p^.flags.one_word_response THEN
            IF short_response_p^.request^.response_processor_p = ^iop$process_idle_response THEN
              completed_request_p := short_response_p^.request^.device_request_p;
              IF (completed_request_p^.request.command [1].command_code = ioc$cc_idle) AND
                    (pp_interface_table_p^.pp_request_queue = NIL) THEN

                { Interlock the PP interface table and set the idle status.

                time := #FREE_RUNNING_CLOCK (0);
                timeout := time + 2000000;
                count := 0;
                REPEAT
                  i#test_set_bit (^pp_interface_table_p^, ioc$pp_interface_table_lock_bit, previously_set);
                  count := count + 1;
                  IF count >= 100 THEN
                    time := #FREE_RUNNING_CLOCK (0);
                    count := 0;
                  IFEND;
                UNTIL (NOT previously_set) OR (time > timeout);
                IF NOT previously_set THEN
                  pp_interface_table_p^.idle_status := TRUE;
                  pp_interface_table_p^.lock := FALSE;
                IFEND;
              IFEND;
            IFEND;

            out_pointer_offset := out_pointer_offset + 8;
            IF out_pointer_offset = pp_interface_table_p^.limit THEN
              out_pointer_offset := 0;
            IFEND;
            total_response_length := total_response_length - 8;
            CYCLE /retrieve_response/; {----->
          IFEND;
        IFEND;

        { Look for special responses.

        special_response := FALSE;
        IF cmv$logical_pp_table_p^ [pp].handlers.one_word_response_allowed THEN
          one_word_response_p := current_response_p;
          IF one_word_response_p^.response_flags.special_response THEN
            special_response := TRUE;
            response_length := one_word_response_p^.response_length;
            IF response_length > dfc$max_fs_pp_response_length THEN
              mtp$error_stop ('IO01 - invalid pp response');
            IFEND;
          IFEND;
        IFEND;

        IF NOT special_response THEN
          IF pp_interface_table_p^.limit - out_pointer_offset >= ioc$min_response_length THEN
            pp_response_p := current_response_p;
          ELSE { Move is necessary.
            remain := (pp_interface_table_p^.limit - out_pointer_offset);
            i#move (current_response_p, ^moved_response_buffer.bytes [0], remain);
            rest := ioc$min_response_length - remain;
            i#move (pp_interface_table_p^.response_buffer, ^moved_response_buffer.bytes [remain], rest);
            pp_response_p := ^moved_response_buffer.response;
          IFEND;

          response_length := pp_response_p^.response_length;
          IF (response_length < ioc$min_response_length) OR (response_length > total_response_length) THEN
            mtp$error_stop ('IO01 - invalid pp response');
          IFEND;

          IF (pp_response_p^.response_code.primary_response = ioc$normal_response) AND
                (pp_response_p^.request^.response_processor_p = ^iop$process_idle_response) THEN
            completed_request_p := pp_response_p^.request^.device_request_p;
            IF (completed_request_p^.request.command [1].command_code = ioc$cc_idle) AND
                  (pp_interface_table_p^.pp_request_queue = NIL) THEN

              { Interlock the PP interface table and set the idle_status.

              time := #FREE_RUNNING_CLOCK (0);
              timeout := time + 2000000;
              count := 0;
              REPEAT
                i#test_set_bit (^pp_interface_table_p^, ioc$pp_interface_table_lock_bit, previously_set);
                count := count + 1;
                IF count >= 100 THEN
                  time := #FREE_RUNNING_CLOCK (0);
                  count := 0;
                IFEND;
              UNTIL (NOT previously_set) OR (time > timeout);
              IF NOT previously_set THEN
                pp_interface_table_p^.idle_status := TRUE;
                pp_interface_table_p^.lock := FALSE;
              IFEND;
            IFEND;
          IFEND;
        IFEND;

        { Update the out pointer offset for the response buffer.

        IF out_pointer_offset + response_length < pp_interface_table_p^.limit THEN
          out_pointer_offset := out_pointer_offset + response_length;
        ELSE
          out_pointer_offset := out_pointer_offset + response_length - pp_interface_table_p^.limit;
        IFEND;

        { Loop if there are more requests to process.

        total_response_length := total_response_length - response_length;
      WHILEND /retrieve_response/;
    FOREND /check_idle_pps/;

  PROCEND iop$check_idle_pps;
?? OLDTITLE ??
?? NEWTITLE := 'iop$idle_all_paths', EJECT ??

{ PURPOSE:
{   This procedure idles all PPs.

  PROCEDURE [XDCL] iop$idle_all_paths
    (    wait: boolean;
     VAR status: syt$monitor_status);

    VAR
      loop: boolean,
      new_time: integer,
      old_time: integer,
      pp: iot$pp_number;

    status.normal := TRUE;

    IF cmv$logical_pp_table_p = NIL THEN
      mtp$set_status_abnormal (ioc$subsystem_io_manager, ioc$pp_not_configured, status);
      RETURN; {----->
    IFEND;

    { Idle all PPs.

  /idle/
    FOR pp := 1 TO cmv$max_number_of_pp DO
      IF cmv$logical_pp_table_p^ [pp].flags.configured AND cmv$logical_pp_table_p^ [pp].flags.
            pp_loaded AND NOT cmv$logical_pp_table_p^ [pp].flags.disabled AND
            (cmv$logical_pp_table_p^ [pp].pp_info.pp_interface_table_p <> NIL) THEN

        { Do not idle the slave.  The Master will take care of the slave.

        IF (cmv$logical_pp_table_p^ [pp].pp_info.pp_type = cmc$lpt_disk_pp_type) OR
              (cmv$logical_pp_table_p^ [pp].pp_info.pp_type = cmc$lpt_tape_pp_type) THEN
          IF (cmv$logical_pp_table_p^ [pp].pp_info.logical_partner_pp_index > 0) AND
                cmv$logical_pp_table_p^ [pp].pp_info.pp_communication_buffer_p^.slave THEN
            CYCLE /idle/; {----->
          IFEND;
        IFEND;

        iop$idle_resume (pp, ioc$ira_idle, status);
        IF NOT status.normal AND (status.condition <> ioc$pp_not_configured) THEN
          RETURN; {----->
        IFEND;
      IFEND;
    FOREND /idle/;

    status.normal := TRUE;
    IF NOT wait THEN
      RETURN; {----->
    IFEND;

    { Get the time that the request was queued and wait for the pp to reply to the idle command.

    old_time := #FREE_RUNNING_CLOCK (0);
    loop := TRUE;

    WHILE loop DO
      iop$check_idle_pps;
      loop := FALSE;

    /check_idle_status/
      FOR pp := 1 TO cmv$max_number_of_pp DO
        IF cmv$logical_pp_table_p^ [pp].flags.configured AND cmv$logical_pp_table_p^ [pp].flags.
              pp_loaded AND NOT cmv$logical_pp_table_p^ [pp].flags.disabled AND
              (cmv$logical_pp_table_p^ [pp].pp_info.pp_interface_table_p <> NIL) THEN

          { Ignore the slave.

          IF (cmv$logical_pp_table_p^ [pp].pp_info.pp_type = cmc$lpt_disk_pp_type) OR
                (cmv$logical_pp_table_p^ [pp].pp_info.pp_type = cmc$lpt_tape_pp_type) THEN
            IF (cmv$logical_pp_table_p^ [pp].pp_info.logical_partner_pp_index > 0) AND
                  cmv$logical_pp_table_p^ [pp].pp_info.pp_communication_buffer_p^.slave THEN
              CYCLE /check_idle_status/; {----->
            IFEND;
          IFEND;

          IF NOT cmv$logical_pp_table_p^ [pp].pp_info.pp_interface_table_p^.idle_status THEN
            loop := TRUE;
          IFEND;
        IFEND;
      FOREND /check_idle_status/;

      new_time := #FREE_RUNNING_CLOCK (0);
      IF new_time >= old_time + 2000000 THEN
        loop := FALSE;
      IFEND;
    WHILEND;

  PROCEND iop$idle_all_paths;
?? OLDTITLE ??
?? NEWTITLE := 'iop$idle_path', EJECT ??

{ PURPOSE:
{   This procedure idles one PP.

  PROCEDURE [XDCL] iop$idle_path
    (    pp: iot$pp_number;
     VAR status: syt$monitor_status);

    VAR
      count: 0 .. 0ff(16),
      time: integer,
      timeout: integer;

    status.normal := TRUE;

    iop$idle_resume (pp, ioc$ira_idle, status);
    IF NOT status.normal THEN
      RETURN; {----->
    IFEND;

    { Get the time that the request was queued and wait for the pp to reply to the idle command.

    time := #FREE_RUNNING_CLOCK (0);
    timeout := time + 2000;
    count := 0;
    REPEAT
      count := count + 1;
      IF count >= 100 THEN
        time := #FREE_RUNNING_CLOCK (0);
        count := 0;
      IFEND;
    UNTIL (time > timeout);

  PROCEND iop$idle_path;
?? OLDTITLE ??
?? NEWTITLE := 'iop$idle_resume', EJECT ??

{ PURPOSE:
{   This procedure idles or resumes a PP.

  PROCEDURE [XDCL] iop$idle_resume
    (    pp: iot$pp_number;
         action: iot$idle_resume_action;
     VAR status: syt$monitor_status);

    VAR
      v$initial_idle_command: [READ, oss$mainframe_wired_literal] iot$command := [
{ COMMAND_CODE     } ioc$cc_idle,
{ FLAGS            } [TRUE, FALSE, 0],
{ LENGTH           } 0,
{ ADDRESS          } 0],

      v$initial_resume_command: [READ, oss$mainframe_wired_literal] iot$command := [
{ COMMAND_CODE     } ioc$cc_resume,
{ FLAGS            } [TRUE, FALSE, 0],
{ LENGTH           } 0,
{ ADDRESS          } 0];

    VAR
      count: 0 .. 0ff(16),
      disk_request_p: ^iot$disk_request,
      io_request_p: ^iot$io_request,
      pp_interface_table_p: ^iot$pp_interface_table,
      previously_set: boolean,
      request_allocated: boolean,
      request_index: 0 .. ioc$request_heap_count,
      retry: 0 .. 0ff(16),
      time: integer,
      timeout: integer;

    status.normal := TRUE;

  /idle_resume/
    BEGIN
      request_allocated := FALSE;

      IF cmv$logical_pp_table_p = NIL THEN
        mtp$set_status_abnormal (ioc$subsystem_io_manager, ioc$pp_not_configured, status);
        EXIT /idle_resume/; {----->
      IFEND;

      IF NOT cmv$logical_pp_table_p^ [pp].flags.configured OR NOT cmv$logical_pp_table_p^ [pp].flags.
            pp_loaded OR cmv$logical_pp_table_p^ [pp].flags.disabled THEN
        mtp$set_status_abnormal (ioc$subsystem_io_manager, ioc$pp_not_configured, status);
        EXIT /idle_resume/; {----->
      IFEND;

      pp_interface_table_p := cmv$logical_pp_table_p^ [pp].pp_info.pp_interface_table_p;
      IF pp_interface_table_p = NIL THEN
        mtp$set_status_abnormal (ioc$subsystem_io_manager, ioc$pp_not_configured, status);
        EXIT /idle_resume/; {----->
      IFEND;

      { Interlock the PP interface table and set the idle or resume information.

      time := #FREE_RUNNING_CLOCK (0);
      timeout := time + 2000000;
      count := 0;
      REPEAT
        i#test_set_bit (^pp_interface_table_p^, ioc$pp_interface_table_lock_bit, previously_set);
        count := count + 1;
        IF count >= 100 THEN
          time := #FREE_RUNNING_CLOCK (0);
          count := 0;
        IFEND;
      UNTIL (NOT previously_set) OR (time > timeout);
      IF previously_set THEN
        mtp$set_status_abnormal (ioc$subsystem_io_manager, ioc$pp_not_configured, status);
        EXIT /idle_resume/; {----->
      IFEND;

      IF cmv$logical_pp_table_p^ [pp].flags.pp_idle_resume_supported THEN
        IF action = ioc$ira_idle THEN
          pp_interface_table_p^.idle_request := TRUE;
          pp_interface_table_p^.resume_request := FALSE;
          pp_interface_table_p^.idle_status := FALSE;
        ELSE {action = ioc$ira_resume}
          pp_interface_table_p^.idle_request := FALSE;
          pp_interface_table_p^.resume_request := TRUE;
        IFEND;
        pp_interface_table_p^.lock := FALSE;

      ELSE
        pp_interface_table_p^.idle_status := (action = ioc$ira_resume);
        pp_interface_table_p^.lock := FALSE;

        { Find an empty slot for request.

        iop$find_empty_request (io_request_p, status);
        IF NOT status.normal THEN
          EXIT /idle_resume/; {----->
        IFEND;

        disk_request_p := io_request_p^.device_request_p;
        request_index := disk_request_p^.request_index;
        request_allocated := TRUE;

        { Prepare the request.

        io_request_p^.response_processor_p := ^iop$process_idle_response;
        IF action = ioc$ira_idle THEN
          disk_request_p^.request.command [1] := v$initial_idle_command;
        ELSE {action = ioc$ira_resume}
          disk_request_p^.request.command [1] := v$initial_resume_command;
        IFEND;

        retry := 1;
        REPEAT
          iop$queue_pp_request (pp_interface_table_p, io_request_p, status);
          IF NOT status.normal THEN
            IF status.condition = ioc$pp_interlock_set THEN
              retry := retry + 1;
              IF retry > 10 THEN
                EXIT /idle_resume/; {----->
              IFEND;
            ELSE
              EXIT /idle_resume/; {----->
            IFEND;
          IFEND;
        UNTIL status.normal;

        iov$request_heap_map [request_index] := TRUE;
        iov$empty_request_count := iov$empty_request_count - 1;
      IFEND;
    END /idle_resume/;

    IF request_allocated AND (NOT status.normal) THEN
      disk_request_p^.link := iov$empty_requests;
      iov$empty_requests := io_request_p;
      IF iov$empty_requests_end = ^iov$empty_requests THEN
        iov$empty_requests_end := ^disk_request_p^.link;
      IFEND;
      iov$request_heap_map [request_index] := FALSE;
    IFEND;

  PROCEND iop$idle_resume;
?? OLDTITLE ??
?? NEWTITLE := 'iop$resume_all_paths', EJECT ??

{ PURPOSE:
{   This procedure resumes all PPs.

  PROCEDURE [XDCL] iop$resume_all_paths
    (VAR status: syt$monitor_status);

    VAR
      pp: iot$pp_number;

    status.normal := TRUE;

    IF cmv$logical_pp_table_p = NIL THEN
      mtp$set_status_abnormal (ioc$subsystem_io_manager, ioc$pp_not_configured, status);
      RETURN; {----->
    IFEND;

  /resume/
    FOR pp := 1 TO cmv$max_number_of_pp DO
      IF cmv$logical_pp_table_p^ [pp].flags.configured AND cmv$logical_pp_table_p^ [pp].flags.
            pp_loaded AND NOT cmv$logical_pp_table_p^ [pp].flags.disabled AND
            (cmv$logical_pp_table_p^ [pp].pp_info.pp_interface_table_p <> NIL) THEN

        { Do not resume the slave.  The Master will take care of the slave.

        IF (cmv$logical_pp_table_p^ [pp].pp_info.pp_type = cmc$lpt_disk_pp_type) OR
              (cmv$logical_pp_table_p^ [pp].pp_info.pp_type = cmc$lpt_tape_pp_type) THEN
          IF (cmv$logical_pp_table_p^ [pp].pp_info.logical_partner_pp_index > 0) AND
                cmv$logical_pp_table_p^ [pp].pp_info.pp_communication_buffer_p^.slave THEN
            CYCLE /resume/; {----->
          IFEND;
        IFEND;

        iop$idle_resume (pp, ioc$ira_resume, status);
        IF NOT status.normal AND (status.condition <> ioc$pp_not_configured) THEN
          RETURN; {----->
        IFEND;
      IFEND;
    FOREND /resume/;
    status.normal := TRUE;

  PROCEND iop$resume_all_paths;
?? OLDTITLE ??
MODEND iom$mtr_manage_pp_process;
