MODULE iom$das_head_shift_test;
?? RIGHT := 110 ??

{ Purpose:
{ This module is used to invoke a head shift test on all configured
{ DAS units. The test is first run by the DAS driver during it's
{ initialization and thereafter this task will invoke the test once
{ every 28 days of contiguous system run time. The test will warn
{ the customer of a possible head shift problem while the errors are
{ still recoverable. If head shift is detected the driver will return
{ a status that will cause an operator intervention window to be
{ opened which will inform the operator of the condition.
{
{ Design:
{ This module is invoked during system deadstart as a system task by
{ osp$job_template_init_ph2. It will suspend for 672 hours and then
{ execute. At execution it searches the configuration for DAS units.
{ The head shift scan test is started on each configured DAS unit. The
{ task then suspends itself for another 672 hours. If head shift is
{ detected the driver will return a status which is processed by
{ iop$log_disk_error which will cause an operator action window to be
{ opened at the console to inform the operator of the condition.

?? NEWTITLE := 'Global Declarations Referenced By This Module', EJECT ??
?? PUSH (LISTEXT := ON) ??
*copyc osd$default_pragmats
*copyc cle$all_must_be_used_alone
*copyc cle$none_must_be_used_alone
*copyc cme$manage_interface_tables
*copyc ioe$st_errors
*copyc ofe$error_codes
*copyc dmt$active_volume_table_index
*copyc iot$cylinders_to_initialize
*copyc ost$caller_identifier
*copyc pmt$program_parameters
?? POP ??
*copyc avp$configuration_administrator
*copyc avp$system_operator
*copyc clp$build_standard_title
*copyc clp$close_display
*copyc clp$convert_integer_to_string
*copyc clp$count_list_elements
*copyc clp$evaluate_parameters
*copyc clp$new_display_line
*copyc clp$open_display_reference
*copyc clp$put_display
*copyc clp$reset_for_next_display_page
*copyc cmp$get_mass_storage_info
*copyc dmp$search_avt_by_rvsn
*copyc ifp$invoke_pause_utility
*copyc iop$initialize_sectors
*copyc iop$mass_storage_io
*copyc jmp$system_job
*copyc osp$await_activity_completion
*copyc osp$disestablish_cond_handler
*copyc osp$establish_condition_handler
*copyc osp$format_message
*copyc osp$generate_message
*copyc osp$set_status_abnormal
*copyc osp$set_status_from_condition
*copyc pmp$continue_to_cause
*copyc pmp$delay
*copyc pmp$log_ascii
*copyc pmp$wait
*copyc clv$nil_display_control
*copyc cmv$logical_unit_table
*copyc dmv$active_volume_table
*copyc syv$recovering_job_count

  VAR
    iov$log_head_shift_testing: [XREF] boolean;

?? OLDTITLE ??
?? NEWTITLE := 'Global Declarations Declared By This Module', EJECT ??

  TYPE
    t$message_kind = (c$mk_start_test_message, c$mk_stop_test_message, c$mk_start_unit_test_message,
          c$mk_stop_unit_test_message, c$mk_error_message, c$trace_message);

  TYPE
    t$output_proc = procedure (    message_kind: t$message_kind;
                                   str: string ( * ));

  TYPE
    t$skip_volumes_conditions = set of t$skip_volumes_condition,
    t$skip_volumes_condition = (c$svc_restoring_volume, c$svc_force_format, c$svc_parity_protect_disabled);

?? OLDTITLE ??
?? NEWTITLE := 'P$DAS_UNIT_HEAD_SHIFT_TEST ', EJECT ??

  PROCEDURE p$das_unit_head_shift_test
    (    active_volume_table_index: dmt$active_volume_table_index;
         skip_volumes_conditions: t$skip_volumes_conditions;
         output_proc_p: ^t$output_proc);

    VAR
      cylinders_to_initialize: array [1 .. 1] of iot$cylinders_to_initialize,
      local_status: ost$status,
      logical_unit_number: iot$logical_unit,
      msi: cmt$mass_storage_information,
      rvsn: rmt$recorded_vsn,
      uit_p: ^iot$unit_interface_table,
      s: string (65);

    logical_unit_number := dmv$active_volume_table.table_p^ [active_volume_table_index].logical_unit_number;
    cmp$get_mass_storage_info (logical_unit_number, msi, local_status);
    IF NOT local_status.normal THEN
      IF (local_status.condition = cme$it_not_cip_device)
{   } OR (local_status.condition = cme$it_no_cip_access)
{   } OR (local_status.condition = cme$it_unusable_cip_access) THEN
        local_status.normal := TRUE;
      ELSE
        p$output_error_message (local_status, output_proc_p);
        RETURN; {----->
      IFEND;
    IFEND;

    CASE msi.unit_type OF
    = cmc$ms5833_1, cmc$ms5833_1p, cmc$ms5833_2, cmc$ms5833_3p, cmc$ms5833_4 =

{ A Headshift Test can stop the restore process during certain circumstances and destroy the volume.
{ Something similar could happend during format.
{ But we have also had situations where we could "re-activate" a hanging restore by a Headshift Test!
{ So, use a parameter to decide what to do in this situation.

      rvsn := dmv$active_volume_table.table_p^ [active_volume_table_index].mass_storage.recorded_vsn;
      IF cmv$logical_unit_table <> NIL THEN
        uit_p := cmv$logical_unit_table^ [logical_unit_number].unit_interface_table;
      ELSE { I think, this can newer happen!
        uit_p := NIL;
      IFEND;

      IF (uit_p <> NIL)
{   } AND ((uit_p^.unit_type = ioc$dt_ms5833_1p) OR (uit_p^.unit_type = ioc$dt_ms5833_3p)) THEN

        IF uit_p^.unit_status.restoring_drive AND (c$svc_restoring_volume IN skip_volumes_conditions) THEN
          s := '* Restoring Drive, Headshift Test skipped for: ';
          s (48, 6) := rvsn;
          output_proc_p^ (c$mk_stop_unit_test_message, s (1, 54));
          RETURN; {----->
        IFEND;

        IF uit_p^.unit_status.force_format AND (c$svc_force_format IN skip_volumes_conditions) THEN
          s := '* Force Format set, Headshift Test skipped for: ';
          s (49, 6) := rvsn;
          output_proc_p^ (c$mk_stop_unit_test_message, s (1, 55));
          RETURN; {----->
        IFEND;

        IF (uit_p^.unit_status.parity_protection_enabled = FALSE)
{     } AND (c$svc_parity_protect_disabled IN skip_volumes_conditions) THEN

          s := '* Parity Protection disabled, Headshift Test skipped for: ';
          s (59, 6) := rvsn;
          output_proc_p^ (c$mk_stop_unit_test_message, s (1, 65));
          RETURN; {----->
        IFEND;
      IFEND;

{ Disk requires testing. Send function to the driver to initiate the head shift testing task.
{ A value of 4 in the start_cylinder field will cause the driver to invoke the testing task
{ when the initialize_sectors function is decoded by the driver.

      IF (iov$log_head_shift_testing) AND (output_proc_p <> NIL) THEN
        s := '* Start Headshift Test Disk: ';
        s (30, 6) := rvsn;
        output_proc_p^ (c$mk_start_unit_test_message, s (1, 35));
      IFEND;

      cylinders_to_initialize [1].start_cylinder := 4;
      iop$initialize_sectors (logical_unit_number, cylinders_to_initialize, local_status);
      IF NOT local_status.normal THEN
        IF local_status.condition <> ioe$unit_disabled THEN
          p$output_error_message (local_status, output_proc_p);
        IFEND;
        RETURN; {----->
      IFEND;

      IF (iov$log_head_shift_testing) AND (output_proc_p <> NIL) THEN
        s := '* Stop  Headshift Test Disk: ';
        s (30, 6) := rvsn;
        output_proc_p^ (c$mk_stop_unit_test_message, s (1, 35));
      IFEND;
    ELSE
      ;
    CASEND;

  PROCEND p$das_unit_head_shift_test;
?? OLDTITLE ??
?? NEWTITLE := 'P$OUTPUT_ERROR_MESSAGE', EJECT ??

  PROCEDURE p$output_error_message
    (    log_status: ost$status;
         output_proc_p: ^t$output_proc);

    VAR
      local_status: ost$status,
      message: ost$status_message,
      message_line_index: 1 .. osc$max_status_message_lines,
      message_p: ^ost$status_message,
      message_line_p: ^ost$status_message_line,
      message_line_count_p: ^ost$status_message_line_count,
      message_line_size_p: ^ost$status_message_line_size;

    osp$format_message (log_status, osc$full_message_level, osc$max_status_message_line, message,
          local_status);
    IF local_status.normal THEN
      message_p := ^message;
      RESET message_p;
      NEXT message_line_count_p IN message_p;

      FOR message_line_index := 1 TO message_line_count_p^ DO
        NEXT message_line_size_p IN message_p;
        NEXT message_line_p: [message_line_size_p^] IN message_p;
        output_proc_p^ (c$mk_error_message, message_line_p^);
      FOREND;
    IFEND;

  PROCEND p$output_error_message;
?? OLDTITLE ??
?? NEWTITLE := 'P$TEST_ALL_UNITS', EJECT ??

  PROCEDURE p$test_all_units
    (    delay_between_unit_tests: integer;
         skip_volumes_conditions: t$skip_volumes_conditions;
         output_proc_p: ^t$output_proc);

    VAR
      avt_index: integer;

  /screen_loop/
    FOR avt_index := LOWERBOUND (dmv$active_volume_table.table_p^) TO
          UPPERBOUND (dmv$active_volume_table.table_p^) DO
      IF NOT dmv$active_volume_table.table_p^ [avt_index].entry_available THEN
        p$das_unit_head_shift_test (avt_index, skip_volumes_conditions, output_proc_p);
      IFEND;

      {Wait delay time between issuing commands.
      pmp$wait (delay_between_unit_tests, delay_between_unit_tests);
    FOREND /screen_loop/;

  PROCEND p$test_all_units;
?? OLDTITLE ??
?? NEWTITLE := '[xdcl, #gate] IOP$DAS_HEAD_SHIFT_TEST_COMMAND', EJECT ??

{Purpose:
{  Support the Das Head Shift Test Disk command.

  PROCEDURE [XDCL, #GATE] iop$das_head_shift_test_command
    (    parameter_list: clt$parameter_list;
     VAR status: ost$status);

*copyc iod$das_head_shift_test_command
*copy clv$display_variables

    VAR
      caller_identifier: ost$caller_identifier,
      default_ring_attributes: amt$ring_attributes,
      delay_between_unit_tests: integer,
      display_control: clt$display_control,
      skip_volumes_conditions: t$skip_volumes_conditions,
      status_p: ^ost$status;

?? NEWTITLE := 'P$CONDITION_HANDLER', EJECT ??

{ PURPOSE:
{   This condition handler is used to handle interactive pause break and terminate break conditions.  It also
{   insures that the output file is closed in the event of an error.

    PROCEDURE p$condition_handler
      (    condition: pmt$condition;
           condition_information: ^pmt$condition_information;
           save_area: ^ost$stack_frame_save_area;
       VAR handler_status: ost$status);

      VAR
        local_status: ost$status;

      IF condition.selector = ifc$interactive_condition THEN
        IF condition.interactive_condition = ifc$pause_break THEN
          ifp$invoke_pause_utility (local_status);
        ELSEIF condition.interactive_condition = ifc$terminate_break THEN
          osp$set_status_from_condition ('LG', condition, save_area, status, local_status);
          EXIT iop$das_head_shift_test_command; {----->
        ELSE
          pmp$continue_to_cause (pmc$execute_standard_procedure, handler_status);
        IFEND
      ELSEIF condition.selector = pmc$block_exit_processing THEN
        clp$close_display (display_control, local_status);
      ELSE
        pmp$continue_to_cause (pmc$execute_standard_procedure, handler_status);
      IFEND;

    PROCEND p$condition_handler;
?? OLDTITLE ??
?? NEWTITLE := 'P$EVALUATE_SKIP_VOL_CONDITIONS', EJECT ??

    PROCEDURE p$evaluate_skip_vol_conditions
      (    value: ^clt$data_value;
       VAR skip_volumes_conditions: t$skip_volumes_conditions;
       VAR status: ost$status);

      CONST
        c$skip_units_conditions = 'SKIP_UNITS_CONDITIONS';

      VAR
        all_specified: boolean,
        none_specified: boolean,
        key_specified: boolean,
        element_value_p: ^clt$data_value,
        value_p: ^clt$data_value;

      status.normal := TRUE;
      skip_volumes_conditions := $t$skip_volumes_conditions [];
      all_specified := FALSE;
      none_specified := FALSE;
      key_specified := FALSE;

      value_p := value;
      WHILE (value_p <> NIL) AND (value_p^.kind = clc$list) DO
        element_value_p := value_p^.element_value;
        IF (element_value_p <> NIL) AND (element_value_p^.kind = clc$keyword) THEN
{ALL
          IF element_value_p^.keyword_value = 'ALL' THEN
            IF none_specified OR key_specified THEN
              osp$set_status_abnormal ('CL', cle$all_must_be_used_alone, c$skip_units_conditions, status);
              RETURN; {----->
            ELSE
              skip_volumes_conditions := -$t$skip_volumes_conditions [];
              all_specified := TRUE;
            IFEND;

{NONE
          ELSEIF element_value_p^.keyword_value = 'NONE' THEN
            IF all_specified OR key_specified THEN
              osp$set_status_abnormal ('CL', cle$none_must_be_used_alone, c$skip_units_conditions, status);
              RETURN; {----->
            ELSE
              skip_volumes_conditions := -$t$skip_volumes_conditions [];
              none_specified := TRUE;
            IFEND;

{RESTORING
          ELSEIF element_value_p^.keyword_value = 'RESTORING' THEN
            IF all_specified THEN
              osp$set_status_abnormal ('CL', cle$all_must_be_used_alone, c$skip_units_conditions, status);
              RETURN; {----->
            ELSEIF none_specified THEN
              osp$set_status_abnormal ('CL', cle$none_must_be_used_alone, c$skip_units_conditions, status);
              RETURN; {----->
            ELSE
              skip_volumes_conditions := skip_volumes_conditions +
                    $t$skip_volumes_conditions [c$svc_restoring_volume];
              key_specified := TRUE;
            IFEND;

{FORMATTING
          ELSEIF element_value_p^.keyword_value = 'FORCE_FORMAT' THEN
            IF all_specified THEN
              osp$set_status_abnormal ('CL', cle$all_must_be_used_alone, c$skip_units_conditions, status);
              RETURN; {----->
            ELSEIF none_specified THEN
              osp$set_status_abnormal ('CL', cle$none_must_be_used_alone, c$skip_units_conditions, status);
              RETURN; {----->
            ELSE
              skip_volumes_conditions := skip_volumes_conditions +
                    $t$skip_volumes_conditions [c$svc_force_format];
              key_specified := TRUE;
            IFEND;

{PARITY_PROTECTION_DISABLED
          ELSEIF element_value_p^.keyword_value = 'PARITY_PROTECTION_DISABLED' THEN
            IF all_specified THEN
              osp$set_status_abnormal ('CL', cle$all_must_be_used_alone, c$skip_units_conditions, status);
              RETURN; {----->
            ELSEIF none_specified THEN
              osp$set_status_abnormal ('CL', cle$none_must_be_used_alone, c$skip_units_conditions, status);
              RETURN; {----->
            ELSE
              skip_volumes_conditions := skip_volumes_conditions +
                    $t$skip_volumes_conditions [c$svc_parity_protect_disabled];
              key_specified := TRUE;
            IFEND;

          IFEND;
        IFEND;

        value_p := value_p^.link;
      WHILEND;

    PROCEND p$evaluate_skip_vol_conditions;
?? OLDTITLE ??
?? NEWTITLE := 'PUT_SUBTITLE', EJECT ??
*copy clp$new_page_procedure

    PROCEDURE put_subtitle
      (    display_control: clt$display_control;
       VAR status: ost$status);

{ The display_log output has no subtitles, this is merely a dummy routine used to keep the module consistent
{ with those that do produce subtitles.

    PROCEND put_subtitle;
?? OLDTITLE ??
?? NEWTITLE := 'P$OUTPUT_PROC', EJECT ??

    PROCEDURE p$output_proc
      (    message_kind: t$message_kind;
           str: string ( * ));

      clp$put_display (display_control, str, clc$trim, status);
      IF NOT status.normal THEN
        EXIT iop$das_head_shift_test_command; {----->
      IFEND;

    PROCEND p$output_proc;
?? OLDTITLE ??
?? NEWTITLE := 'P$TEST_UNIT_LIST', EJECT ??

    PROCEDURE p$test_unit_list
      (    unit_list_p: ^clt$data_value;
           delay_between_unit_tests: integer;
           skip_volumes_conditions: t$skip_volumes_conditions);

      VAR
        avt_index: dmt$active_volume_table_index,
        count: integer,
        found: boolean,
        i: integer,
        rvsn: rmt$recorded_vsn,
        value_p: ^clt$data_value;

      count := clp$count_list_elements (unit_list_p);
      value_p := unit_list_p;
      FOR i := 1 TO count DO
        IF value_p^.element_value <> NIL THEN
          rvsn := value_p^.element_value^.name_value;

          dmp$search_avt_by_rvsn (rvsn, avt_index, found);
          IF found THEN
            p$das_unit_head_shift_test (avt_index, skip_volumes_conditions, ^p$output_proc);
          IFEND;
        IFEND;

        value_p := unit_list_p^.link;
      FOREND;

    PROCEND p$test_unit_list;
?? OLDTITLE ??
?? EJECT ??

    #CALLER_ID (caller_identifier);
    clp$evaluate_parameters (parameter_list, #SEQ (pdt), NIL, ^pvt, status);
    IF NOT status.normal THEN
      RETURN; {----->
    IFEND;

    IF NOT (avp$configuration_administrator () OR avp$system_operator ()) THEN
      osp$set_status_abnormal ('OF', ofe$sou_not_active, 'configuration_administration or system_operation',
            status);
      RETURN; {----->
    IFEND;

    p$evaluate_skip_vol_conditions (pvt [p$skip_units_conditions].value, skip_volumes_conditions, status);
    IF NOT status.normal THEN
      RETURN; {----->
    IFEND;

    delay_between_unit_tests := pvt [p$delay_between_unit_tests].value^.integer_value.value * 1000;
    display_control := clv$nil_display_control;
    #SPOIL (display_control);

    osp$establish_condition_handler (^p$condition_handler, TRUE);

  /output_file_open/
    BEGIN
      default_ring_attributes.r1 := caller_identifier.ring;
      default_ring_attributes.r2 := caller_identifier.ring;
      default_ring_attributes.r3 := caller_identifier.ring;
      clp$open_display_reference (pvt [p$output].value^.file_value^, ^clp$new_page_procedure, fsc$list,
            default_ring_attributes, display_control, status);
      IF NOT status.normal THEN
        EXIT /output_file_open/; {----->
      IFEND;
      clv$titles_built := FALSE;
      clv$command_name := 'DAS Head Shift Test';

      IF pvt [p$recorded_vsn].value^.kind = clc$keyword THEN
        p$test_all_units (delay_between_unit_tests, skip_volumes_conditions, ^p$output_proc);
      ELSE
        p$test_unit_list (pvt [p$recorded_vsn].value, delay_between_unit_tests, skip_volumes_conditions);
      IFEND;
    END /output_file_open/;

    IF status.normal THEN
      status_p := ^status;
    ELSE
      PUSH status_p;
    IFEND;

    clp$close_display (display_control, status_p^);
    osp$disestablish_cond_handler;

  PROCEND iop$das_head_shift_test_command;
?? OLDTITLE ??
?? NEWTITLE := '[xdcl, #gate] IOP$DAS_HEAD_SHIFT_TEST ', EJECT ??

{Purpose:
{  Support the periodic DAS Head Shift test, running as a system task.

  PROCEDURE [XDCL, #GATE] iop$das_head_shift_test
    (    p: pmt$program_parameters;
     VAR status: ost$status);

    CONST
      c$delay_between_units = 60 * 1000, { 1 minute.
      c$periodic_run_delay = 60 * 60 * 1000, { 1 hour
      c$periodic_run_interval = 28 * 24 * 60 * 60 * 1000; { 28 days

    VAR
      ignore: ost$status,
      ready_index: integer,
      system_job: boolean,
      wait_list: array [1 .. 1] of ost$activity;

?? NEWTITLE := 'P$OUTPUT_PROC', EJECT ??

    PROCEDURE p$output_proc
      (    message_kind: t$message_kind;
           str: string ( * ));

      VAR
        ignore_status: ost$status;

      pmp$log_ascii (str, -$pmt$ascii_logset [], pmc$msg_origin_system, ignore_status);

    PROCEND p$output_proc;
?? OLDTITLE ??
?? EJECT ??

    system_job := jmp$system_job ();
{---
    IF NOT system_job THEN
      RETURN; {----->
    IFEND;
{---


{wait at least 1 hour after recovery => prevent from shoking operators during deadstart ;-)
    wait_list [1].activity := osc$await_time;
    wait_list [1].milliseconds := c$periodic_run_delay;
    REPEAT
      osp$await_activity_completion (wait_list, ready_index, ignore);
    UNTIL syv$recovering_job_count <= 0;

{Now, we wait an hour after recovery completed.
    osp$await_activity_completion (wait_list, ready_index, ignore);

{setup wait list for the periodic run
    wait_list [1].activity := osc$await_time;
    wait_list [1].milliseconds := c$periodic_run_interval;

  /forever/
    WHILE TRUE DO

      IF iov$log_head_shift_testing THEN
        pmp$log_ascii ('* Start DAS Headshift Test', -$pmt$ascii_logset [], pmc$msg_origin_system, ignore);
      IFEND;

{ Scan the active configuration
      p$test_all_units (c$delay_between_units, $t$skip_volumes_conditions [c$svc_restoring_volume],
            ^p$output_proc);

      IF iov$log_head_shift_testing THEN
        pmp$log_ascii ('* End DAS Headshift Test', -$pmt$ascii_logset [], pmc$msg_origin_system, ignore);
      IFEND;

{ Suspend task execution for 28 days.
      osp$await_activity_completion (wait_list, ready_index, ignore);
    WHILEND /forever/;

  PROCEND iop$das_head_shift_test;
?? OLDTITLE ??
MODEND iom$das_head_shift_test;
