?? NEWTITLE := 'NOS/VE Network Access : Channel Connection Manager' ??
MODULE nlm$channel_connection_manager;
?? RIGHT := 110 ??

{ PURPOSE:
{   The purpose of this module is to isolate the knowledge of the structures
{   required to gain access to a Channel Connection. This module also contains
{   the procedures used to: initiate a device reset, shut down a connection,
{   abort a connection and terminate all connections associated with a particular
{   device.
{
{ DESIGN:
{   This module was designed to reside on the OSF$JOB_TEMPLATE_23D library.
{
?? NEWTITLE := 'Global Declarations Referenced by this Module', EJECT ??
?? PUSH (LISTEXT := ON) ??
*copyc nac$null_connection_id
*copyc nlt$cc_connection
*copyc nlt$cc_connection_class
?? POP ??
*copyc nap$issue_pp_request
*copyc nap$namve_system_error
*copyc nlp$bm_release_message
*copyc nlp$cc_decr_connection_count
*copyc nlp$cc_get_device_specific_attr
*copyc nlp$cc_get_received_messages
*copyc nlp$cc_grant_credits
*copyc nlp$cl_deactivate_layer
*copyc nlp$cl_decr_priority_connection
*copyc nlp$cl_get_connection_access
*copyc nlp$cl_get_exclusive_via_cid
*copyc nlp$cl_get_layer_connection
*copyc nlp$cl_get_nonexclusive_to_root
*copyc nlp$cl_release_exclusive_access
*copyc nlp$cl_release_nonexclu_to_root
*copyc nlp$connection_queued
*copyc nlp$get_exclusive_access
*copyc nlp$get_nonexclusive_access
*copyc nlp$release_exclusive_access
*copyc osp$add_to_locked_variable
*copyc nlp$release_nonexclusive_access
*copyc osp$decrement_locked_variable
*copyc osp$increment_locked_variable
*copyc osp$sub_from_locked_variable
*copyc nav$global_osi_statistics
*copyc nav$network_paged_heap
*copyc nav$network_procedures
*copyc nav$statistics_enabled
*copyc nlv$bm_null_message_id
*copyc nlv$cc_grant_credit_trigger
*copyc nlv$cl_connections
*copyc nlv$configured_network_devices
*copyc nlv$sm_devices
?? TITLE := '[XDCL] nlp$cc_abort_connection', EJECT ??
*copy nlh$cc_abort_connection

  PROCEDURE [XDCL] nlp$cc_abort_connection
    (    reason: nlt$cc_disconnect_reason;
         connection { input, output } : ^nlt$cc_connection;
         cl_connection { input, output } : ^nlt$cl_connection);

    VAR
      event: nlt$cc_event,
      ignore_accumulated_messages: integer,
      initial_state: nlt$cc_connection_state;

    initial_state := connection^.device_specific_attributes.state;
    connection^.device_specific_attributes.state := nlc$cc_closed;
    nlp$cc_shut_down_connection (connection, cl_connection, connection^.device_specific_attributes.device_id);
    IF initial_state <> nlc$cc_closing THEN
      event.kind := nlc$cc_disconnect_event;
      event.disconnect.reason := reason;
      event.disconnect.data := nlv$bm_null_message_id;
      nav$network_procedures [connection^.event_processor].
            cc_event_processor^ (cl_connection, event, ignore_accumulated_messages);
    IFEND;

  PROCEND nlp$cc_abort_connection;
?? TITLE := '[XDCL] nlp$cc_report_undelivered_data', EJECT ??
*copy nlh$cc_report_undelivered_data

  PROCEDURE [XDCL] nlp$cc_report_undelivered_data
    (    cl_connection: ^nlt$cl_connection;
         accumulated_message_buffers: integer);

    VAR
      connection: ^nlt$cc_connection,
      layer_active: boolean;

    nlp$cl_get_layer_connection (nlc$channel_connection_layer, cl_connection, layer_active, connection);
    IF layer_active AND (connection^.device_specific_attributes.state = nlc$cc_open) THEN
      connection^.accumulated_message_buffers := accumulated_message_buffers;
      IF connection^.receive_credits <= nlv$cc_grant_credit_trigger THEN
        nlp$cc_grant_credits (connection);
      IFEND;
    IFEND;

  PROCEND nlp$cc_report_undelivered_data;
?? TITLE := '[XDCL] nlp$cc_reset_device', EJECT ??
*copy nlh$cc_reset_device

  PROCEDURE [XDCL] nlp$cc_reset_device
    (    device_id: nlt$device_identifier);

    VAR
      command: iot$command,
      network_device_list: ^nlt$network_device_list;

    nlp$get_exclusive_access (nlv$configured_network_devices.access_control);
    network_device_list := nlv$configured_network_devices.network_device_list;

    IF network_device_list^ [device_id].path_status = nlc$path_available THEN
      network_device_list^ [device_id].path_status := nlc$path_unavailable;
      command.flags.store_response := TRUE;
      command.flags.indirect_address := FALSE;
      command.length := 0;
      command.address := 0;
      command.command_code := ioc$cc_reset_device;
      nap$issue_pp_request (network_device_list^ [device_id].pp_number, command, NIL);
      nap$namve_system_error (TRUE, 'Device reset requested', NIL);
    IFEND;
    nlp$release_exclusive_access (nlv$configured_network_devices.access_control);
  PROCEND nlp$cc_reset_device;
?? TITLE := '[XDCL] nlp$cc_shut_down_connection', EJECT ??
*copy nlh$cc_shut_down_connection

  PROCEDURE [XDCL] nlp$cc_shut_down_connection
    (    connection: ^nlt$cc_connection;
         cl_connection: ^nlt$cl_connection;
         device_id: nlt$device_identifier);

    VAR
      message_id: nlt$bm_message_id,
      next_received_message: ^nlt$bm_message_descriptor,
      received_messages: ^nlt$bm_message_descriptor;

    IF (connection^.device_specific_attributes.state = nlc$cc_closed) THEN
      release_send_buffer (connection);
      IF ((cl_connection^.queue_on_connection) AND (NOT nlp$connection_queued (cl_connection))) THEN

{ Discard queued input.

        nlp$cc_get_received_messages (cl_connection, received_messages);
        IF received_messages <> NIL THEN
          WHILE received_messages <> NIL DO
            next_received_message := received_messages^.received_message.next_received_message;
            message_id.descriptor := received_messages;
            message_id.sequence_number := received_messages^.sequence_number;
            nlp$bm_release_message (message_id);
            received_messages := next_received_message;
          WHILEND;
        IFEND;
      IFEND;
      IF connection^.sub_connection_count = 0 THEN
        IF connection^.sub_connections <> NIL THEN
          FREE connection^.sub_connections IN nav$network_paged_heap^;
        IFEND;
        IF ((cl_connection^.queue_on_connection) AND (NOT nlp$connection_queued (cl_connection))) OR
              ((NOT cl_connection^.queue_on_connection) AND (connection^.next_deliverable_sequence# =
              cl_connection^.next_assignable_cc_sequence#)) THEN
          nlp$cl_deactivate_layer (nlc$channel_connection_layer, cl_connection);
        IFEND;
        IF connection^.class = nlc$cc_priority_class THEN
          nlp$cl_decr_priority_connection;
        IFEND;
      IFEND;
    IFEND;
    nlp$cc_decr_connection_count (device_id);

  PROCEND nlp$cc_shut_down_connection;
?? TITLE := '[XDCL] nlp$cc_terminate_connections', EJECT ??
*copy nlh$cc_terminate_connections

  PROCEDURE [XDCL] nlp$cc_terminate_connections
    (    device_id: nlt$device_identifier);

    VAR
      access_gained: boolean,
      actual: integer,
      cl_connection: ^nlt$cl_connection,
      connection: ^nlt$cc_connection,
      connection_exists: boolean,
      device_specific_attributes: ^nlt$cc_device_specific_attr,
      ignore_error: boolean,
      initial_state: nlt$cc_connection_state,
      layer_active: boolean,
      main_connection: boolean,
      next_connection: ^nlt$cl_connection,
      root: nlt$cl_reference_number,
      statistic1: ^integer,
      statistic2: ^integer,
      sm_connection_id: nat$connection_id;

{! statistics begin}

    IF nav$statistics_enabled THEN
      osp$increment_locked_variable (nav$global_osi_statistics.channel_connection_device^ [device_id].
            device_resets, 0, actual);
    IFEND;

{! statistics end}

{ Terminate the system mgmt connection first.

    nlp$get_nonexclusive_access (nlv$sm_devices.access_control);
    IF nlv$sm_devices.list^ [device_id].state <> nlc$sm_uninitialized THEN
      sm_connection_id := nlv$sm_devices.list^ [device_id].connection_id;
    ELSE
      sm_connection_id := nac$null_connection_id;
    IFEND;
    nlp$release_nonexclusive_access (nlv$sm_devices.access_control);
    IF sm_connection_id <> nac$null_connection_id THEN
      nlp$cl_get_exclusive_via_cid (sm_connection_id, connection_exists, cl_connection);
      IF cl_connection <> NIL THEN
        nlp$cl_get_layer_connection (nlc$channel_connection_layer, cl_connection, layer_active, connection);
        IF layer_active THEN
          IF connection^.device_specific_attributes.state <> nlc$cc_closed THEN
            nlp$cc_abort_connection (nlc$cc_dr_link_down, connection, cl_connection);

{! statistics begin}

            IF nav$statistics_enabled THEN
              IF connection^.device_specific_attributes.state = nlc$cc_open THEN
                IF connection^.class = nlc$cc_normal_class THEN
                  statistic1 := ^nav$global_osi_statistics.channel_connection_device^ [device_id].
                        current_normal_connections;
                  statistic2 := ^nav$global_osi_statistics.channel_connection.normal_connections;
                ELSE { IF connection^.class = nlc$cc_priority_class THEN
                  statistic1 := ^nav$global_osi_statistics.channel_connection_device^ [device_id].
                        current_priority_connections;
                  statistic2 := ^nav$global_osi_statistics.channel_connection.priority_connections;
                IFEND;
                osp$decrement_locked_variable (statistic1^, 1, actual, ignore_error);
                osp$decrement_locked_variable (statistic2^, 1, actual, ignore_error);
              IFEND;
            IFEND;

{! statistics end}

          IFEND;
        IFEND;
        nlp$cl_release_exclusive_access (cl_connection);
      IFEND;
    IFEND;

{ Terminate remaining connections.

    IF nlv$cl_connections.list <> NIL THEN

    /traverse_roots/
      FOR root := LOWERBOUND (nlv$cl_connections.list^) TO UPPERBOUND (nlv$cl_connections.list^) DO
        nlp$cl_get_nonexclusive_to_root (root);
        cl_connection := nlv$cl_connections.list^ [root].first;

      /traverse_stem/
        WHILE cl_connection <> NIL DO
          next_connection := cl_connection^.nextt;
          IF device_id IN cl_connection^.device_ids THEN
            REPEAT
              nlp$cl_get_connection_access (cl_connection, access_gained);
              IF NOT access_gained THEN
                syp$cycle;
              IFEND;
            UNTIL access_gained;
            nlp$cl_get_layer_connection (nlc$channel_connection_layer, cl_connection, layer_active,
                  connection);
            IF layer_active THEN
              nlp$cc_get_device_specific_attr (device_id, connection, main_connection,
                    device_specific_attributes);
              IF main_connection THEN
                IF device_specific_attributes^.state <> nlc$cc_closed THEN
                  nlp$cc_abort_connection (nlc$cc_dr_link_down, connection, cl_connection);

{! statistics begin}

                  IF nav$statistics_enabled THEN
                    IF device_specific_attributes^.state = nlc$cc_open THEN
                      IF connection^.class = nlc$cc_normal_class THEN
                        statistic1 := ^nav$global_osi_statistics.channel_connection_device^ [device_id].
                              current_normal_connections;
                        statistic2 := ^nav$global_osi_statistics.channel_connection.normal_connections;
                      ELSE { IF connection^.class = nlc$cc_priority_class THEN
                        statistic1 := ^nav$global_osi_statistics.channel_connection_device^ [device_id].
                              current_priority_connections;
                        statistic2 := ^nav$global_osi_statistics.channel_connection.priority_connections;
                      IFEND;
                      osp$decrement_locked_variable (statistic1^, 1, actual, ignore_error);
                      osp$decrement_locked_variable (statistic2^, 1, actual, ignore_error);
                    IFEND;
                  IFEND;

{! statistics end}

                IFEND;
              ELSE { subconnection
                IF device_specific_attributes^.state <> nlc$cc_closed THEN
                  initial_state := device_specific_attributes^.state;
                  device_specific_attributes^.state := nlc$cc_closed;
                  connection^.sub_connection_count := connection^.sub_connection_count - 1;
                  IF connection^.sub_connection_count = 0 THEN

{  The state of a "subconnection" that is not CLOSED, is either: nlc$cc_connect_confirm_wait
{  or nlc$cc_closing.

                    IF initial_state = nlc$cc_connect_confirm_wait THEN

{ Setup the device_id field in the "main" connection for call to nlp$cc_abort_connection.
{ This is needed because nlp$cc_abort_connection expects all fields in the "main" connection
{ device specific attributes field to be valid; and at this point the "main" connection has
{ never been established and therefore not initialized.

                      connection^.device_specific_attributes.device_id :=
                            device_specific_attributes^.device_id;
                      nlp$cc_abort_connection (nlc$cc_dr_link_down, connection, cl_connection);
                    ELSE { nlc$cc_closing
                      nlp$cc_shut_down_connection (connection, cl_connection,
                            device_specific_attributes^.device_id);
                    IFEND;
                  ELSE { sub_connection_count > 0
                    nlp$cc_decr_connection_count (device_specific_attributes^.device_id);
                  IFEND;
                IFEND;
              IFEND;
            IFEND;
            nlp$cl_release_exclusive_access (cl_connection);
          IFEND;
          cl_connection := next_connection;
        WHILEND /traverse_stem/;
        nlp$cl_release_nonexclu_to_root (root);
      FOREND /traverse_roots/;
    IFEND;

  PROCEND nlp$cc_terminate_connections;
?? TITLE := 'release_send_buffer', EJECT ??
{
{  PURPOSE:
{    The purpose of this request is to release all queued outbound
{  Channel Connection messages.
{

  PROCEDURE [INLINE] release_send_buffer
    (    connection { input, output } : ^nlt$cc_connection);

    VAR
      actual: integer,
      extension: ^nlt$cc_send_buffer_extension,
      ignore_error: boolean,
      message_count: integer,
      statistic1: ^integer,
      statistic2: ^integer;

    message_count := 0;
    WHILE (connection^.send_buffer.out <> connection^.send_buffer.inn) DO
      nlp$bm_release_message (connection^.send_buffer.cc_pdu [connection^.send_buffer.out].data);
      connection^.send_buffer.out := ((connection^.send_buffer.out + 1) MOD nlc$cc_send_buffer_limit);
      message_count := message_count + 1;
    WHILEND;
    WHILE connection^.send_buffer.extension <> NIL DO
      nlp$bm_release_message (connection^.send_buffer.extension^.cc_pdu.data);
      extension := connection^.send_buffer.extension;
      connection^.send_buffer.extension := extension^.nextt;
      FREE extension IN nav$network_paged_heap^;
      message_count := message_count + 1;
    WHILEND;

{! statistics begin}

    IF nav$statistics_enabled AND (message_count <> 0) THEN
      IF connection^.class = nlc$cc_normal_class THEN
        statistic1 := ^nav$global_osi_statistics.channel_connection_device^
              [connection^.device_specific_attributes.device_id].send_pdus_discarded;
        statistic2 := ^nav$global_osi_statistics.channel_connection_device^
              [connection^.device_specific_attributes.device_id].normal_send_pdus_queued;
      ELSE { IF connection^.class = nlc$cc_priority_class THEN
        statistic1 := ^nav$global_osi_statistics.channel_connection_device^
              [connection^.device_specific_attributes.device_id].priority_send_pdus_discarded;
        statistic2 := ^nav$global_osi_statistics.channel_connection_device^
              [connection^.device_specific_attributes.device_id].priority_send_pdus_queued;
      IFEND;
      osp$add_to_locked_variable (statistic1^, 0, message_count, actual);
      osp$sub_from_locked_variable (statistic2^, message_count, message_count, actual, ignore_error);
    IFEND;

{! statistics end}

  PROCEND release_send_buffer;

?? OLDTITLE ??
MODEND nlm$channel_connection_manager;
