소스 검색

Added Ethernet Link object's counter attributes and GetAndClear service

This commit adds the Interface Counters attribute (#4) and the Media
Counters attribute (#5) to the implementation of the Ethernet Link object.
This feature is enabled with OPENER_ETHLINK_CNTRS_ENABLE that is
automatically enabled if OpENer is configured with OPENER_IS_DLR_DEVICE
from the CMake command line.

At the moment only a dummy implementation to read the counters is provided
because the way to retrieve the real counters is hardware and platform
dependent. The dummy implementation reports bogus counter values that make
the validation of the data transport easy.

Signed-off-by: Stefan Mätje <stefan.maetje@esd.eu>
Stefan Mätje 6 년 전
부모
커밋
bad4d472c7

+ 7 - 2
source/src/cip/ciperror.h

@@ -48,7 +48,12 @@ typedef enum {
   kCipErrorUnexpectedAttributeInList = 0x27, /**< An attempt was made to set an attribute that is not able to be set at this time. */
   kCipErrorInvalidMemberId = 0x28, /**< The Member ID specified in the request does not exist in the specified Class/Instance/Attribute */
   kCipErrorMemberNotSetable = 0x29, /**< A request to modify a non-modifiable member was received */
-  kCipErrorGroup2OnlyServerGeneralFailure = 0x2A /**< This error code may only be reported by DeviceNet group 2 only servers with 4K or less code space and only in place of Service not supported, Attribute not supported and Attribute not setable. */
-/*2B - CF Reserved by CIP for future extensions D0 - FF Reserved for Object Class and service errors*/
+  kCipErrorGroup2OnlyServerGeneralFailure = 0x2A, /**< This error code may only be reported by DeviceNet group 2 only servers with 4K or less code space and only in place of Service not supported, Attribute not supported and Attribute not setable. */
+  kCipErrorUnknownModbusError = 0x2B, /**< A CIP to Modbus translator received an unknown Modbus Exception Code. */
+  kCipErrorAttributeNotGettable = 0x2C, /**< A request to read a non-readable attribute was received. */
+  kCipErrorInstanceNotDeletable = 0x2D, /**< The requested object instance cannot be deleted. */
+  kCipErrorServiceNotSupportedForSpecifiedPath = 0x2E,  /**< The object supports the service, but not for the designated application path (e.g. attribute). NOTE: Not to be used when a more specific General Status Code applies,
+                                                              e.g. 0x0E (Attribute not settable) or 0x29 (Member not settable).*/
+/* 2F - CF Reserved by CIP for future extensions D0 - FF Reserved for Object Class and service errors*/
 } CipError;
 #endif /* OPENER_CIPERROR_H_ */

+ 185 - 48
source/src/cip/cipethernetlink.c

@@ -3,22 +3,41 @@
  * All rights reserved.
  *
  ******************************************************************************/
-/*
- * CIP Ethernet Link Object
- * ========================
+/** @file
+ *  @brief Implement the CIP Ethernet Link Object
  *
- * Implemented Attributes
- * ----------------------
- * - Attribute  1: Interface Speed
- * - Attribute  2: Interface Flags
- * - Attribute  3: Physical Address (Ethernet MAC)
- * - Attribute  7: Interface Type
- *    See Vol. 2 Section 6-3.4 regarding an example for a device with internal
- *    switch port where the implementation of this attribute is recommended.
- * - Attribute 10: Interface Label
- *    If the define OPENER_ETHLINK_LABEL_ENABLE is set then this attribute
- *    has a string content ("PORT 1" by default on instance 1).
- * - Attribute 11: Interface Capabilities
+ *  CIP Ethernet Link Object
+ *  ========================
+ *
+ *  Implemented Attributes
+ *  ----------------------
+ *  Conditional attributes are indented and marked with the condition it
+ *  depends on like "(0 != OPENER_ETHLINK_CNTRS_ENABLE)"
+ *
+ *  - Attribute  1: Interface Speed
+ *  - Attribute  2: Interface Flags
+ *  - Attribute  3: Physical Address (Ethernet MAC)
+ *      - Attribute  4: Interface Counters (32-bit) (0 != OPENER_ETHLINK_CNTRS_ENABLE)
+ *      - Attribute  5: Media Counters (32-bit)     (0 != OPENER_ETHLINK_CNTRS_ENABLE)
+ *  - Attribute  7: Interface Type
+ *      See Vol. 2 Section 6-3.4 regarding an example for a device with internal
+ *      switch port where the implementation of this attribute is recommended.
+ *  - Attribute 10: Interface Label (0 != OPENER_ETHLINK_LABEL_ENABLE)
+ *      If the define OPENER_ETHLINK_LABEL_ENABLE is set then this attribute
+ *      has a string content ("PORT 1" by default on instance 1) otherwise it
+ *      is empty.
+ *  - Attribute 11: Interface Capabilities
+ *
+ *  Implemented Services
+ *  --------------------
+ *  Conditional services are indented and marked with the condition it
+ *  depends on like "(0 != OPENER_ETHLINK_CNTRS_ENABLE)"
+ *
+ *  - GetAttributeAll
+ *  - GetAttributeSingle
+ *    - GetAndClearAttribute  (0 != OPENER_ETHLINK_CNTRS_ENABLE)
+ *        This service should only implemented for the attributes 4, 5, 12,
+ *        13 and 15.
  *
  */
 
@@ -43,7 +62,7 @@
   #define OPENER_ETHLINK_LABEL_ENABLE  0
 #endif
 
-#if defined(OPENER_ETHLINK_LABEL_ENABLE) && OPENER_ETHLINK_LABEL_ENABLE != 0
+#if defined(OPENER_ETHLINK_LABEL_ENABLE) && 0 != OPENER_ETHLINK_LABEL_ENABLE
   #define IFACE_LABEL_ACCESS_MODE kGetableSingleAndAll
   #define IFACE_LABEL             "PORT 1"
   #define IFACE_LABEL_1           "PORT 2"
@@ -87,6 +106,16 @@ EipStatus GetAttributeSingleEthernetLink(
   const struct sockaddr *originator_address,
   const int encapsulation_session);
 
+#if defined(OPENER_ETHLINK_CNTRS_ENABLE) && 0 != OPENER_ETHLINK_CNTRS_ENABLE
+/* forward declaration for the GetAndClear service handler function */
+EipStatus GetAndClearEthernetLink(
+  CipInstance *RESTRICT const instance,
+  CipMessageRouterRequest *const message_router_request,
+  CipMessageRouterResponse *const message_router_response,
+  const struct sockaddr *originator_address,
+  const int encapsulation_session);
+#endif  /* ... && 0 != OPENER_ETHLINK_CNTRS_ENABLE */
+
 
 /** @brief This is the internal table of possible speed / duplex combinations
  *
@@ -125,7 +154,7 @@ static const CipEthernetLinkSpeedDuplexArrayEntry speed_duplex_table[] =
   },
 };
 
-#if defined(OPENER_ETHLINK_LABEL_ENABLE) && OPENER_ETHLINK_LABEL_ENABLE != 0
+#if defined(OPENER_ETHLINK_LABEL_ENABLE) && 0 != OPENER_ETHLINK_LABEL_ENABLE
 static const CipShortString iface_label_table[OPENER_ETHLINK_INSTANCE_CNT] =
 {
   {
@@ -145,11 +174,14 @@ static const CipShortString iface_label_table[OPENER_ETHLINK_INSTANCE_CNT] =
   },
 #endif
 };
-#endif /* defined(OPENER_ETHLINK_LABEL_ENABLE) && OPENER_ETHLINK_LABEL_ENABLE != 0 */
+#endif /* defined(OPENER_ETHLINK_LABEL_ENABLE) && 0 != OPENER_ETHLINK_LABEL_ENABLE */
 
 /* Two dummy variables to provide fill data for the GetAttributeAll service. */
 static CipUsint dummy_attribute_usint = 0;
+#if defined(OPENER_ETHLINK_CNTRS_ENABLE) && 0 != OPENER_ETHLINK_CNTRS_ENABLE
+#else
 static CipUdint dummy_attribute_udint = 0;
+#endif
 
 /* Constant dummy data for attribute #6 */
 static CipEthernetLinkInterfaceControl interface_control =
@@ -168,7 +200,7 @@ EipStatus CipEthernetLinkInit(void) {
                                                  2, /* # class services*/
                                                  11, /* # instance attributes*/
                                                  11, /* # highest instance attribute number*/
-                                                 2, /* # instance services*/
+                                                 2+OPENER_ETHLINK_CNTRS_ENABLE, /* # instance services*/
                                                  OPENER_ETHLINK_INSTANCE_CNT, /* # instances*/
                                                  "Ethernet Link", /* # class name */
                                                  4, /* # class revision*/
@@ -185,7 +217,7 @@ EipStatus CipEthernetLinkInit(void) {
     if (2 == idx) {
       g_ethernet_link[idx].interface_type = kEthLinkIfTypeInternal;
     }
-#if defined(OPENER_ETHLINK_LABEL_ENABLE) && OPENER_ETHLINK_LABEL_ENABLE != 0
+#if defined(OPENER_ETHLINK_LABEL_ENABLE) && 0 != OPENER_ETHLINK_LABEL_ENABLE
     g_ethernet_link[idx].interface_label = iface_label_table[idx];
 #endif
     g_ethernet_link[idx].interface_caps.capability_bits = kEthLinkCapAutoNeg;
@@ -199,6 +231,10 @@ EipStatus CipEthernetLinkInit(void) {
                   &GetAttributeSingleEthernetLink, "GetAttributeSingle");
     InsertService(ethernet_link_class, kGetAttributeAll, &GetAttributeAll,
                   "GetAttributeAll");
+#if defined(OPENER_ETHLINK_CNTRS_ENABLE) && 0 != OPENER_ETHLINK_CNTRS_ENABLE
+    InsertService(ethernet_link_class, kEthLinkGetAndClear,
+                  &GetAndClearEthernetLink, "GetAndClear");
+#endif
 
     /* bind attributes to the instance */
     for (size_t idx = 0; idx < OPENER_ETHLINK_INSTANCE_CNT; ++idx) {
@@ -211,10 +247,17 @@ EipStatus CipEthernetLinkInit(void) {
                       &g_ethernet_link[idx].interface_flags, kGetableSingleAndAll);
       InsertAttribute(ethernet_link_instance, 3, kCip6Usint,
                       &g_ethernet_link[idx].physical_address, kGetableSingleAndAll);
+#if defined(OPENER_ETHLINK_CNTRS_ENABLE) && 0 != OPENER_ETHLINK_CNTRS_ENABLE
+      InsertAttribute(ethernet_link_instance, 4, kCipUsint,
+                      &g_ethernet_link[idx].interface_cntrs, kGetableSingleAndAll);
+      InsertAttribute(ethernet_link_instance, 5, kCipUsint,
+                      &g_ethernet_link[idx].media_cntrs, kGetableSingleAndAll);
+#else
       InsertAttribute(ethernet_link_instance, 4, kCipUsint,
                       &dummy_attribute_udint, kGetableAll);
       InsertAttribute(ethernet_link_instance, 5, kCipUsint,
                       &dummy_attribute_udint, kGetableAll);
+#endif  /* ... && 0 != OPENER_ETHLINK_CNTRS_ENABLE */
       InsertAttribute(ethernet_link_instance, 6, kCipUdint, &interface_control,
                       kGetableAll);
       InsertAttribute(ethernet_link_instance, 7, kCipUsint,
@@ -247,63 +290,80 @@ void CipEthernetLinkSetMac(EipUint8 *p_physical_address) {
 }
 
 static int EncodeInterfaceCounters(CipUdint instance_id, CipOctet **message) {
-// Returns default value 0
-  int return_value = 0;
+  int encoded_len = 0;
   for (size_t i = 0; i < 11; i++) {
-    return_value += EncodeData(kCipUdint, &dummy_attribute_udint, message);
+#if defined(OPENER_ETHLINK_CNTRS_ENABLE) && 0 != OPENER_ETHLINK_CNTRS_ENABLE
+    /* Encode real values using the access through the array of the
+     *  interface_cntrs union. */
+    encoded_len += EncodeData(
+                    kCipUdint,
+                    g_ethernet_link[instance_id-1].interface_cntrs.cntr32 +i,
+                    message);
+#else
+    /* Encode the default counter value of 0 */
+    encoded_len += EncodeData(kCipUdint, &dummy_attribute_udint, message);
+#endif
   }
-  return return_value;
+  return encoded_len;
 }
 
 static int EncodeMediaCounters(CipUdint instance_id, CipOctet **message) {
-// Returns default value 0
-  int return_value = 0;
+  int encoded_len = 0;
   for (size_t i = 0; i < 12; i++) {
-    return_value += EncodeData(kCipUdint, &dummy_attribute_udint, message);
+#if defined(OPENER_ETHLINK_CNTRS_ENABLE) && 0 != OPENER_ETHLINK_CNTRS_ENABLE
+    /* Encode real values using the access through the array of the
+     *  media_cntrs union. */
+    encoded_len += EncodeData(
+                    kCipUdint,
+                    g_ethernet_link[instance_id-1].media_cntrs.cntr32 +i,
+                    message);
+#else
+    /* Encode the default counter value of 0 */
+    encoded_len += EncodeData(kCipUdint, &dummy_attribute_udint, message);
+#endif
   }
-  return return_value;
+  return encoded_len;
 }
 
 static int EncodeInterfaceControl(CipOctet **message) {
-// Returns default value 0
-  int return_value = 0;
-  return_value += EncodeData(kCipWord, &interface_control.control_bits,
-                             message);
-  return_value += EncodeData(kCipUint,
-                             &interface_control.forced_interface_speed,
-                             message);
-  return return_value;
+  int encoded_len = 0;
+  encoded_len += EncodeData(kCipWord, &interface_control.control_bits,
+                            message);
+  encoded_len += EncodeData(kCipUint,
+                            &interface_control.forced_interface_speed,
+                            message);
+  return encoded_len;
 }
 
 #define NELEMENTS(x)  ((sizeof(x)/sizeof(x[0])))
 static int EncodeInterfaceCapability(CipUdint instance_id, CipOctet **message)
 {
-  int return_value = 0;
-  return_value += EncodeData(
+  int encoded_len = 0;
+  encoded_len += EncodeData(
                     kCipDword,
                     &g_ethernet_link[instance_id - 1].interface_caps.capability_bits,
                     message);
   uint16_t selected = g_ethernet_link[instance_id - 1].interface_caps.speed_duplex_selector;
   CipUsint count;
-  for(count = 0; selected; count++) { /* count # of bits set */
+  for (count = 0; selected; count++) { /* count # of bits set */
 	  selected &= selected - 1u;  /* clear the least significant bit set */
   }
-  return_value += EncodeData(kCipUsint, &count, message);
+  encoded_len += EncodeData(kCipUsint, &count, message);
 
   for (size_t i = 0; i < NELEMENTS(speed_duplex_table); i++) {
     if (g_ethernet_link[instance_id - 1].interface_caps.speed_duplex_selector &
         (1u << i)) {
-      return_value += EncodeData(
+      encoded_len += EncodeData(
                         kCipUint,
                         &speed_duplex_table[i].interface_speed,
                         message);
-      return_value += EncodeData(
+      encoded_len += EncodeData(
                         kCipUsint,
                         &speed_duplex_table[i].interface_duplex_mode,
                         message);
     }
   }
-  return return_value;
+  return encoded_len;
 }
 
 EipStatus GetAttributeSingleEthernetLink(
@@ -312,7 +372,6 @@ EipStatus GetAttributeSingleEthernetLink(
   CipMessageRouterResponse *const message_router_response,
   const struct sockaddr *originator_address,
   const int encapsulation_session) {
-  /* Mask for filtering get-ability */
 
   CipAttributeStruct *attribute = GetCipAttribute(
     instance, message_router_request->request_path.attribute_number);
@@ -327,8 +386,8 @@ EipStatus GetAttributeSingleEthernetLink(
   EipUint16 attribute_number = message_router_request->request_path
                                .attribute_number;
 
-OPENER_TRACE_INFO("ENTER I %u A %hu\n", instance->instance_number, attribute_number);
   if ( (NULL != attribute) && (NULL != attribute->data) ) {
+    /* Mask for filtering get-ability */
     uint8_t get_bit_mask = 0;
     if (kGetAttributeAll == message_router_request->service) {
       get_bit_mask = (instance->cip_class->get_all_bit_mask[CalculateIndex(
@@ -339,11 +398,31 @@ OPENER_TRACE_INFO("ENTER I %u A %hu\n", instance->instance_number, attribute_num
                                                                  attribute_number)
                       ]);
     }
-    OPENER_TRACE_INFO ("Service %" PRIu8 ", get_bit_mask=%02" PRIx8 "\n",
+    OPENER_TRACE_INFO ("Service %" PRIu8 ", get_bit_mask=%02" PRIX8 "\n",
                        message_router_request->service, get_bit_mask);
     if ( 0 != ( get_bit_mask & ( 1 << (attribute_number % 8) ) ) ) {
-      OPENER_TRACE_INFO("getAttribute %d\n",
-                        message_router_request->request_path.attribute_number); /* create a reply message containing the data*/
+      OPENER_TRACE_INFO("getAttribute %d\n", attribute_number);
+
+      /* create a reply message containing the data*/
+      bool use_common_handler;
+      switch (attribute_number) {
+      case 4: /* fall through */
+      case 5: /* fall through */
+      case 6: /* fall through */
+      case 11: /* fall through */
+        use_common_handler = false;
+        break;
+      default:
+        use_common_handler = true;
+        break;
+      }
+
+      /* Call the PreGetCallback if enabled for this attribute and the common handler is not used. */
+      if (!use_common_handler) {
+        if (attribute->attribute_flags & kPreGetFunc && NULL != instance->cip_class->PreGetCallback) {
+          instance->cip_class->PreGetCallback(instance, attribute, message_router_request->service);
+        }
+      }
 
       switch (attribute_number) {
         case 4:
@@ -377,9 +456,67 @@ OPENER_TRACE_INFO("ENTER I %u A %hu\n", instance->instance_number, attribute_num
                              encapsulation_session);
       }
 
+      /* Call the PostGetCallback if enabled for this attribute and the common handler is not used. */
+      if (!use_common_handler) {
+        if (attribute->attribute_flags & kPostGetFunc && NULL != instance->cip_class->PostGetCallback) {
+          instance->cip_class->PostGetCallback(instance, attribute, message_router_request->service);
+        }
+      }
+
     }
   }
 
   return kEipStatusOkSend;
 }
 
+#if defined(OPENER_ETHLINK_CNTRS_ENABLE) && 0 != OPENER_ETHLINK_CNTRS_ENABLE
+EipStatus GetAndClearEthernetLink(
+  CipInstance *RESTRICT const instance,
+  CipMessageRouterRequest *const message_router_request,
+  CipMessageRouterResponse *const message_router_response,
+  const struct sockaddr *originator_address,
+  const int encapsulation_session) {
+
+  CipAttributeStruct *attribute = GetCipAttribute(
+    instance, message_router_request->request_path.attribute_number);
+
+  message_router_response->data_length = 0;
+  message_router_response->reply_service = (0x80
+                                            | message_router_request->service);
+  message_router_response->general_status = kCipErrorAttributeNotSupported;
+  message_router_response->size_of_additional_status = 0;
+
+  EipUint16 attribute_number = message_router_request->request_path
+                               .attribute_number;
+
+  if ( (NULL != attribute) && (NULL != attribute->data) ) {
+    OPENER_TRACE_INFO("GetAndClear attribute %" PRIu16 "\n", attribute_number);
+
+    /* The PreGetCallback is not executed here.
+     * The GetAttributeSingle{EthernetLink}() will do it on our behalf. */
+    switch (attribute_number) {
+      case 4: /* fall through */
+      case 5:
+        GetAttributeSingleEthernetLink(
+                           instance, message_router_request,
+                           message_router_response,
+                           originator_address,
+                           encapsulation_session);
+        break;
+      default:
+        message_router_response->general_status =
+          kCipErrorServiceNotSupportedForSpecifiedPath;
+        break;
+    }
+
+    /* Clearing the counters must be done in the PostGetCallback because it
+     *  is often hardware specific and can't be handled in generic code. */
+    /* The PostGetCallback is not executed here. The
+     *  GetAttributeSingle{EthernetLink}() should have done it on our
+     *  behalf already.
+     */
+  }
+
+  return kEipStatusOkSend;
+}
+#endif  /* ... && 0 != OPENER_ETHLINK_CNTRS_ENABLE */

+ 60 - 0
source/src/cip/cipethernetlink.h

@@ -5,6 +5,9 @@
  ******************************************************************************/
 #ifndef OPENER_CIPETHERNETLINK_H_
 #define OPENER_CIPETHERNETLINK_H_
+/** @file
+ *  @brief Declare public interface of the CIP Ethernet Link Object
+ */
 
 #include "typedefs.h"
 #include "ciptypes.h"
@@ -71,11 +74,68 @@ typedef struct {
   uint16_t speed_duplex_selector;
 } CipEthernetLinkMetaInterfaceCapability;
 
+
+
+#if defined(OPENER_ETHLINK_CNTRS_ENABLE) && 0 != OPENER_ETHLINK_CNTRS_ENABLE
+/** @brief Type definition of the Interface Counters attribute #4
+ *
+ * This union holds the 32-bit Interface Counters of the Ethernet Link object.
+ *  This is attribute is becomes required if the HC Interface Counters attribute or
+ *  Media Counters attribute is implemented, otherwise highly recommended.
+ * That means for DLR capable devices this attribute is required because the
+ *  Media Counters attribute is required for DLR capable devices.
+ */
+typedef union {
+  CipUdint  cntr32[11];
+  struct {
+    CipUdint  in_octets;
+    CipUdint  in_ucast;
+    CipUdint  in_nucast;
+    CipUdint  in_discards;
+    CipUdint  in_errors;
+    CipUdint  in_unknown_protos;
+    CipUdint  out_octets;
+    CipUdint  out_ucast;
+    CipUdint  out_nucast;
+    CipUdint  out_discards;
+    CipUdint  out_errors;
+  } ul;
+} CipEthernetLinkInterfaceCounters;
+
+/** @brief Type definition of the Media Counters attribute #5
+ *
+ * This union holds the 32-bit Media Counters of the Ethernet Link object.
+ *  This attribute becomes required if the devices supports DLR or if the
+ *  HC Media Counters attribute is implemented, otherwise highly recommended.
+ */
+typedef union {
+  CipUdint  cntr32[12];
+  struct {
+    CipUdint  align_errs;
+    CipUdint  fcs_errs;
+    CipUdint  single_coll;
+    CipUdint  multi_coll;
+    CipUdint  sqe_test_errs;
+    CipUdint  def_trans;
+    CipUdint  late_coll;
+    CipUdint  exc_coll;
+    CipUdint  mac_tx_errs;
+    CipUdint  crs_errs;
+    CipUdint  frame_too_long;
+    CipUdint  mac_rx_errs;
+  } ul;
+} CipEthernetLinkMediaCounters;
+#endif  /* ... && OPENER_ETHLINK_CNTRS_ENABLE != 0 */
+
 /** @brief Data of an CIP Ethernet Link object */
 typedef struct {
   EipUint32 interface_speed; /**< Attribute #1: 10/100/1000 Mbit/sec */
   EipUint32 interface_flags; /**< Attribute #2: Interface flags as defined in the CIP specification */
   EipUint8 physical_address[6]; /**< Attribute #3: MAC address of the Ethernet link */
+#if defined(OPENER_ETHLINK_CNTRS_ENABLE) && 0 != OPENER_ETHLINK_CNTRS_ENABLE
+  CipEthernetLinkInterfaceCounters interface_cntrs; /**< Attribute #4: Interface counters 32-bit wide */
+  CipEthernetLinkMediaCounters media_cntrs; /**< Attribute #5: Media counters 32-bit wide */
+#endif
   CipUsint interface_type;  /**< Attribute #7: Type of interface; */
   CipShortString interface_label; /**< Attribute #10: Interface label */
   CipEthernetLinkMetaInterfaceCapability interface_caps; /**< Attribute #11: Interface capabilities */

+ 2 - 0
source/src/cip/ciptypes.h

@@ -89,9 +89,11 @@ typedef enum {
   kInsertMember = 0x1A,
   kRemoveMember = 0x1B,
   kGroupSync = 0x1C,
+  kGetConnectionPointMemberList = 0x1D,
   /* End CIP common services */
 
   /* Start CIP object-specific services */
+  kEthLinkGetAndClear = 0x4C, /**< Ethernet Link object's Get_And_Clear service */
   kForwardOpen = 0x54,
   kForwardClose = 0x4E,
   kUnconnectedSend = 0x52,

+ 5 - 1
source/src/ports/MINGW/sample_application/CMakeLists.txt

@@ -11,4 +11,8 @@ opener_platform_support("INCLUDES")
 
 opener_platform_support("INCLUDES")
 
-add_library(SAMPLE_APP sampleapplication.c)
+if (OPENER_IS_DLR_DEVICE)
+  set( SAMPLE_APP_DLR_ONLY_SRC ethlinkcbs.c )
+endif()
+
+add_library(SAMPLE_APP sampleapplication.c ${SAMPLE_APP_DLR_ONLY_SRC})

+ 169 - 0
source/src/ports/MINGW/sample_application/ethlinkcbs.c

@@ -0,0 +1,169 @@
+/******************************************************************************
+ * Copyright (c) 2019, Rockwell Automation, Inc.
+ * All rights reserved.
+ *
+ *****************************************************************************/
+
+/** @file
+ * @brief Ethernet Link object callbacks
+ *
+ * This module implements the Ethernet Link object callbacks. These callbacks
+ *  handle the update and clear operation for the interface and media counters
+ *  of every Ethernet Link object of our device.
+ *
+ * The current implementation is only a dummy implementation that doesn't
+ *  return real counters of the interface(s). It is only intended to check
+ *  whether the EIP stack transmits the counters at the right position in
+ *  the response while we're are filling the counters in the Ethernet Link
+ *  counter attributes by their union member names.
+ */
+
+/*---------------------------------------------------------------------------*/
+/*                               INCLUDES                                    */
+/*---------------------------------------------------------------------------*/
+#include "ethlinkcbs.h"
+
+#include "cipethernetlink.h"
+#include "trace.h"
+
+/*---------------------------------------------------------------------------*/
+/*                                LOCALS                                     */
+/*---------------------------------------------------------------------------*/
+
+/* These are "dummy" counters that are only used by the check code to be able
+ *  to transmit interface and media counters that change with each
+ *  GetAttribute explicit request.
+ */
+static CipUdint iface_calls[OPENER_ETHLINK_INSTANCE_CNT];
+static CipUdint media_calls[OPENER_ETHLINK_INSTANCE_CNT];
+
+/*---------------------------------------------------------------------------*/
+/*                           IMPLEMENTATION                                  */
+/*---------------------------------------------------------------------------*/
+
+#if defined(OPENER_ETHLINK_CNTRS_ENABLE) && 0 != OPENER_ETHLINK_CNTRS_ENABLE
+/*---------------------------------------------------------------------------*/
+/* Here we implement the functions delivering some dummy data created from   */
+/*  the instance number, the attribute number, the position of the data      */
+/*  field in the counters union and the count of GetAttributeSingle calls    */
+/*  for that attribute since startup or last GetAndClear service.            */
+/* The returned data is calculated using MAKE_CNTR() in a way that the input */
+/*  values can be easily decoded from the decimal view in the Wireshark log. */
+/* This is meant as debugging aid and to check if the individual counter     */
+/*  value is sent at the right position in the Get* service response.        */
+
+#define MAKE_CNTR(inst, attr, idx, cnt) ((10000000u * inst) + (100000u * attr) + (1000u * idx) + cnt)
+
+EipStatus EthLnkPreGetCallback
+(
+  CipInstance *const instance,
+  CipAttributeStruct *const attribute,
+  CipByte service
+)
+{
+  bool hadAction = true;
+  EipStatus status = kEipStatusOk;
+
+  CipUint attr_no = attribute->attribute_number;
+  /* ATTENTION: Array indices run from 0..(N-1), instance numbers from 1..N */
+  CipUdint inst_no  = instance->instance_number;
+  unsigned idx = inst_no-1;
+  switch (attr_no) {
+  case 4: {
+    CipEthernetLinkInterfaceCounters *p_iface_cntrs = &g_ethernet_link[idx].interface_cntrs;
+
+    ++iface_calls[idx];  /* Count successful calls */
+    p_iface_cntrs->ul.in_octets   = MAKE_CNTR(inst_no, attr_no,  0, iface_calls[idx]);
+    p_iface_cntrs->ul.in_ucast    = MAKE_CNTR(inst_no, attr_no,  1, iface_calls[idx]);
+    p_iface_cntrs->ul.in_nucast   = MAKE_CNTR(inst_no, attr_no,  2, iface_calls[idx]);
+    p_iface_cntrs->ul.in_discards = MAKE_CNTR(inst_no, attr_no,  3, iface_calls[idx]);
+    p_iface_cntrs->ul.in_errors   = MAKE_CNTR(inst_no, attr_no,  4, iface_calls[idx]);
+    p_iface_cntrs->ul.in_unknown_protos = MAKE_CNTR(inst_no, attr_no, 5, iface_calls[idx]);
+    p_iface_cntrs->ul.out_octets  = MAKE_CNTR(inst_no, attr_no,  6, iface_calls[idx]);
+    p_iface_cntrs->ul.out_ucast   = MAKE_CNTR(inst_no, attr_no,  7, iface_calls[idx]);
+    p_iface_cntrs->ul.out_nucast  = MAKE_CNTR(inst_no, attr_no,  8, iface_calls[idx]);
+    p_iface_cntrs->ul.out_discards = MAKE_CNTR(inst_no, attr_no, 9, iface_calls[idx]);
+    p_iface_cntrs->ul.out_errors  = MAKE_CNTR(inst_no, attr_no, 10, iface_calls[idx]);
+    break;
+  }
+  case 5: {
+    CipEthernetLinkMediaCounters *p_media_cntrs = &g_ethernet_link[idx].media_cntrs;
+
+    ++media_calls[idx];  /* Count successful calls */
+    /* The 1 != mediaCalls[idx] is a concession to the conformance test tool that
+     *  expects the media counters to be zero after a GetAndClear service.
+     * This way we always transmit zeros after reset or a GetAndClear service. */
+    if (1 != media_calls[idx]) {
+      p_media_cntrs->ul.align_errs    = MAKE_CNTR(inst_no, attr_no,  0, media_calls[idx]);
+      p_media_cntrs->ul.fcs_errs      = MAKE_CNTR(inst_no, attr_no,  1, media_calls[idx]);
+      p_media_cntrs->ul.single_coll   = MAKE_CNTR(inst_no, attr_no,  2, media_calls[idx]);
+      p_media_cntrs->ul.multi_coll    = MAKE_CNTR(inst_no, attr_no,  3, media_calls[idx]);
+      p_media_cntrs->ul.sqe_test_errs = MAKE_CNTR(inst_no, attr_no,  4, media_calls[idx]);
+      p_media_cntrs->ul.def_trans     = MAKE_CNTR(inst_no, attr_no,  5, media_calls[idx]);
+      p_media_cntrs->ul.late_coll     = MAKE_CNTR(inst_no, attr_no,  6, media_calls[idx]);
+      p_media_cntrs->ul.exc_coll      = MAKE_CNTR(inst_no, attr_no,  7, media_calls[idx]);
+      p_media_cntrs->ul.mac_tx_errs   = MAKE_CNTR(inst_no, attr_no,  8, media_calls[idx]);
+      p_media_cntrs->ul.crs_errs      = MAKE_CNTR(inst_no, attr_no,  9, media_calls[idx]);
+      p_media_cntrs->ul.frame_too_long= MAKE_CNTR(inst_no, attr_no, 10, media_calls[idx]);
+      p_media_cntrs->ul.mac_rx_errs   = MAKE_CNTR(inst_no, attr_no, 11, media_calls[idx]);
+    }
+    break;
+  }
+  default:
+    hadAction = false;
+    break;
+  }
+
+  if (hadAction) {
+    OPENER_TRACE_INFO(
+      "Eth Link PreCallback: %s, i %" PRIu32 ", a %" PRIu16 ", s %" PRIu8 "\n",
+      instance->cip_class->class_name,
+      instance->instance_number,
+      attribute->attribute_number,
+      service);
+  }
+  return status;
+}
+
+
+EipStatus EthLnkPostGetCallback
+(
+  CipInstance *const instance,
+  CipAttributeStruct *const attribute,
+  CipByte service
+)
+{
+  CipUdint  inst_no = instance->instance_number;
+  EipStatus status = kEipStatusOk;
+
+  if (kEthLinkGetAndClear == (service & 0x7f)) {
+    OPENER_TRACE_INFO(
+      "Eth Link PostCallback: %s, i %" PRIu32 ", a %" PRIu16 ", s %" PRIu8 "\n",
+      instance->cip_class->class_name,
+      inst_no,
+      attribute->attribute_number,
+      service);
+    /* Clear the instance specific object counters. In this dummy function we only
+     *  clear our GetAttributeSingle PreCallback execution counters. */
+    switch (attribute->attribute_number) {
+    case 4:
+      iface_calls[inst_no-1] = 0u;
+      break;
+    case 5:
+      media_calls[inst_no-1] = 0u;
+      /* This is a concession to the conformance test tool that expects
+       *  the media counters to be zero after a GetAndClear service. */
+      for (int idx = 0; idx < 12; ++idx) {
+        g_ethernet_link[inst_no-1].media_cntrs.cntr32[idx] = 0u;
+      }
+      break;
+    default:
+      OPENER_TRACE_INFO(
+        "Wrong attribute number %" PRIu16 " in GetAndClear callback\n",
+        attribute->attribute_number);
+      break;
+    }
+  }
+  return status;
+}
+#endif /* ... && 0 != OPENER_ETHLINK_CNTRS_ENABLE */

+ 42 - 0
source/src/ports/MINGW/sample_application/ethlinkcbs.h

@@ -0,0 +1,42 @@
+/******************************************************************************
+ * Copyright (c) 2019, Rockwell Automation, Inc.
+ * All rights reserved.
+ *
+ *****************************************************************************/
+#ifndef OPENER_ETHLINKCBS_H_
+#define OPENER_ETHLINKCBS_H_
+/** @file
+ * @brief Declaration of Ethernet Link object callbacks
+ *
+ * This header declares the Ethernet Link object callbacks. These callbacks
+ *  handle the update and clear operation for the interface and media counters
+ *  of every Ethernet Link object of our device.
+ */
+
+
+/*---------------------------------------------------------------------------*/
+/*                               INCLUDES                                    */
+/*---------------------------------------------------------------------------*/
+#include "typedefs.h"
+#include "ciptypes.h"
+
+/*---------------------------------------------------------------------------*/
+/*                             PROTOTYPES                                    */
+/*---------------------------------------------------------------------------*/
+
+EipStatus EthLnkPreGetCallback
+(
+    CipInstance *const instance,
+    CipAttributeStruct *const attribute,
+    CipByte service
+);
+
+EipStatus EthLnkPostGetCallback
+(
+    CipInstance *const instance,
+    CipAttributeStruct *const attribute,
+    CipByte service
+);
+
+
+#endif  /* #ifndef OPENER_ETHLINKCBS_H_ */

+ 14 - 1
source/src/ports/MINGW/sample_application/opener_user_conf.h

@@ -39,9 +39,10 @@ typedef unsigned short in_port_t;
   #define OPENER_IS_DLR_DEVICE  0
 #endif
 
-#if defined(OPENER_IS_DLR_DEVICE) && OPENER_IS_DLR_DEVICE != 0
+#if defined(OPENER_IS_DLR_DEVICE) && 0 != OPENER_IS_DLR_DEVICE
   /* Enable all the stuff the DLR device depends on */
   #define OPENER_TCPIP_IFACE_CFG_SETTABLE 1
+  #define OPENER_ETHLINK_CNTRS_ENABLE     1
   #define OPENER_ETHLINK_LABEL_ENABLE     1
   #define OPENER_ETHLINK_INSTANCE_CNT     3
 #endif
@@ -85,6 +86,18 @@ typedef unsigned short in_port_t;
   #define OPENER_ETHLINK_LABEL_ENABLE  0
 #endif
 
+/** @brief Set this define if you need Counters for Ethernet Link object
+ *
+ * This define enables the Media Counters (attribute #5) which are required
+ *  for a DLR device. Also the Interface Counters (attribute #4) are enabled
+ *  which become required because the Media Counters are implemented.
+ */
+#ifndef OPENER_ETHLINK_CNTRS_ENABLE
+  #define OPENER_ETHLINK_CNTRS_ENABLE 0
+#endif
+
+
+
 /** @brief Define the number of objects that may be used in connections
  *
  *  This number needs only to consider additional objects. Connections to

+ 37 - 0
source/src/ports/MINGW/sample_application/sampleapplication.c

@@ -13,6 +13,10 @@
 #include "ciptcpipinterface.h"
 #include "cipqos.h"
 #include "nvdata.h"
+#if defined(OPENER_ETHLINK_CNTRS_ENABLE) && 0 != OPENER_ETHLINK_CNTRS_ENABLE
+  #include "cipethernetlink.h"
+  #include "ethlinkcbs.h"
+#endif
 
 #define DEMO_APP_INPUT_ASSEMBLY_NUM                100 //0x064
 #define DEMO_APP_OUTPUT_ASSEMBLY_NUM               150 //0x096
@@ -75,6 +79,39 @@ EipStatus ApplicationInitialization(void) {
                        NvTcpipSetCallback,
                        kNvDataFunc);
 
+#if defined(OPENER_ETHLINK_CNTRS_ENABLE) && 0 != OPENER_ETHLINK_CNTRS_ENABLE
+  /* For the Ethernet Interface & Media Counters connect a PreGetCallback and
+   *  a PostGetCallback.
+   * The PreGetCallback is used to fetch the counters from the hardware.
+   * The PostGetCallback is utilized by the GetAndClear service to clear
+   *  the hardware counters after the current data have been transmitted.
+   */
+  {
+    CipClass *p_eth_link_class = GetCipClass(kCipEthernetLinkClassCode);
+    InsertGetSetCallback(p_eth_link_class,
+                         EthLnkPreGetCallback,
+                         kPreGetFunc);
+    InsertGetSetCallback(p_eth_link_class,
+                         EthLnkPostGetCallback,
+                         kPostGetFunc);
+    /* Specify the attributes for which the callback should be executed. */
+    for (int idx = 0; idx < OPENER_ETHLINK_INSTANCE_CNT; ++idx)
+    {
+      CipAttributeStruct *p_eth_link_attr;
+      CipInstance *p_eth_link_inst =
+        GetCipInstance(p_eth_link_class, idx+1);
+      OPENER_ASSERT(p_eth_link_inst);
+
+      /* Interface counters attribute */
+      p_eth_link_attr = GetCipAttribute(p_eth_link_inst, 4);
+      p_eth_link_attr->attribute_flags |= (kPreGetFunc | kPostGetFunc);
+      /* Media counters attribute */
+      p_eth_link_attr = GetCipAttribute(p_eth_link_inst, 5);
+      p_eth_link_attr->attribute_flags |= (kPreGetFunc | kPostGetFunc);
+    }
+  }
+#endif
+
   return kEipStatusOk;
 }
 

+ 5 - 1
source/src/ports/POSIX/sample_application/CMakeLists.txt

@@ -9,5 +9,9 @@ opener_common_includes()
 #######################################
 opener_platform_support("INCLUDES")
 
-add_library(SAMPLE_APP sampleapplication.c)
+if (OPENER_IS_DLR_DEVICE)
+  set( SAMPLE_APP_DLR_ONLY_SRC ethlinkcbs.c )
+endif()
+
+add_library(SAMPLE_APP sampleapplication.c ${SAMPLE_APP_DLR_ONLY_SRC})
 target_link_libraries( SAMPLE_APP NVDATA )

+ 169 - 0
source/src/ports/POSIX/sample_application/ethlinkcbs.c

@@ -0,0 +1,169 @@
+/******************************************************************************
+ * Copyright (c) 2019, Rockwell Automation, Inc.
+ * All rights reserved.
+ *
+ *****************************************************************************/
+
+/** @file
+ * @brief Ethernet Link object callbacks
+ *
+ * This module implements the Ethernet Link object callbacks. These callbacks
+ *  handle the update and clear operation for the interface and media counters
+ *  of every Ethernet Link object of our device.
+ *
+ * The current implementation is only a dummy implementation that doesn't
+ *  return real counters of the interface(s). It is only intended to check
+ *  whether the EIP stack transmits the counters at the right position in
+ *  the response while we're are filling the counters in the Ethernet Link
+ *  counter attributes by their union member names.
+ */
+
+/*---------------------------------------------------------------------------*/
+/*                               INCLUDES                                    */
+/*---------------------------------------------------------------------------*/
+#include "ethlinkcbs.h"
+
+#include "cipethernetlink.h"
+#include "trace.h"
+
+/*---------------------------------------------------------------------------*/
+/*                                LOCALS                                     */
+/*---------------------------------------------------------------------------*/
+
+/* These are "dummy" counters that are only used by the check code to be able
+ *  to transmit interface and media counters that change with each
+ *  GetAttribute explicit request.
+ */
+static CipUdint iface_calls[OPENER_ETHLINK_INSTANCE_CNT];
+static CipUdint media_calls[OPENER_ETHLINK_INSTANCE_CNT];
+
+/*---------------------------------------------------------------------------*/
+/*                           IMPLEMENTATION                                  */
+/*---------------------------------------------------------------------------*/
+
+#if defined(OPENER_ETHLINK_CNTRS_ENABLE) && 0 != OPENER_ETHLINK_CNTRS_ENABLE
+/*---------------------------------------------------------------------------*/
+/* Here we implement the functions delivering some dummy data created from   */
+/*  the instance number, the attribute number, the position of the data      */
+/*  field in the counters union and the count of GetAttributeSingle calls    */
+/*  for that attribute since startup or last GetAndClear service.            */
+/* The returned data is calculated using MAKE_CNTR() in a way that the input */
+/*  values can be easily decoded from the decimal view in the Wireshark log. */
+/* This is meant as debugging aid and to check if the individual counter     */
+/*  value is sent at the right position in the Get* service response.        */
+
+#define MAKE_CNTR(inst, attr, idx, cnt) ((10000000u * inst) + (100000u * attr) + (1000u * idx) + cnt)
+
+EipStatus EthLnkPreGetCallback
+(
+  CipInstance *const instance,
+  CipAttributeStruct *const attribute,
+  CipByte service
+)
+{
+  bool hadAction = true;
+  EipStatus status = kEipStatusOk;
+
+  CipUint attr_no = attribute->attribute_number;
+  /* ATTENTION: Array indices run from 0..(N-1), instance numbers from 1..N */
+  CipUdint inst_no  = instance->instance_number;
+  unsigned idx = inst_no-1;
+  switch (attr_no) {
+  case 4: {
+    CipEthernetLinkInterfaceCounters *p_iface_cntrs = &g_ethernet_link[idx].interface_cntrs;
+
+    ++iface_calls[idx];  /* Count successful calls */
+    p_iface_cntrs->ul.in_octets   = MAKE_CNTR(inst_no, attr_no,  0, iface_calls[idx]);
+    p_iface_cntrs->ul.in_ucast    = MAKE_CNTR(inst_no, attr_no,  1, iface_calls[idx]);
+    p_iface_cntrs->ul.in_nucast   = MAKE_CNTR(inst_no, attr_no,  2, iface_calls[idx]);
+    p_iface_cntrs->ul.in_discards = MAKE_CNTR(inst_no, attr_no,  3, iface_calls[idx]);
+    p_iface_cntrs->ul.in_errors   = MAKE_CNTR(inst_no, attr_no,  4, iface_calls[idx]);
+    p_iface_cntrs->ul.in_unknown_protos = MAKE_CNTR(inst_no, attr_no, 5, iface_calls[idx]);
+    p_iface_cntrs->ul.out_octets  = MAKE_CNTR(inst_no, attr_no,  6, iface_calls[idx]);
+    p_iface_cntrs->ul.out_ucast   = MAKE_CNTR(inst_no, attr_no,  7, iface_calls[idx]);
+    p_iface_cntrs->ul.out_nucast  = MAKE_CNTR(inst_no, attr_no,  8, iface_calls[idx]);
+    p_iface_cntrs->ul.out_discards = MAKE_CNTR(inst_no, attr_no, 9, iface_calls[idx]);
+    p_iface_cntrs->ul.out_errors  = MAKE_CNTR(inst_no, attr_no, 10, iface_calls[idx]);
+    break;
+  }
+  case 5: {
+    CipEthernetLinkMediaCounters *p_media_cntrs = &g_ethernet_link[idx].media_cntrs;
+
+    ++media_calls[idx];  /* Count successful calls */
+    /* The 1 != mediaCalls[idx] is a concession to the conformance test tool that
+     *  expects the media counters to be zero after a GetAndClear service.
+     * This way we always transmit zeros after reset or a GetAndClear service. */
+    if (1 != media_calls[idx]) {
+      p_media_cntrs->ul.align_errs    = MAKE_CNTR(inst_no, attr_no,  0, media_calls[idx]);
+      p_media_cntrs->ul.fcs_errs      = MAKE_CNTR(inst_no, attr_no,  1, media_calls[idx]);
+      p_media_cntrs->ul.single_coll   = MAKE_CNTR(inst_no, attr_no,  2, media_calls[idx]);
+      p_media_cntrs->ul.multi_coll    = MAKE_CNTR(inst_no, attr_no,  3, media_calls[idx]);
+      p_media_cntrs->ul.sqe_test_errs = MAKE_CNTR(inst_no, attr_no,  4, media_calls[idx]);
+      p_media_cntrs->ul.def_trans     = MAKE_CNTR(inst_no, attr_no,  5, media_calls[idx]);
+      p_media_cntrs->ul.late_coll     = MAKE_CNTR(inst_no, attr_no,  6, media_calls[idx]);
+      p_media_cntrs->ul.exc_coll      = MAKE_CNTR(inst_no, attr_no,  7, media_calls[idx]);
+      p_media_cntrs->ul.mac_tx_errs   = MAKE_CNTR(inst_no, attr_no,  8, media_calls[idx]);
+      p_media_cntrs->ul.crs_errs      = MAKE_CNTR(inst_no, attr_no,  9, media_calls[idx]);
+      p_media_cntrs->ul.frame_too_long= MAKE_CNTR(inst_no, attr_no, 10, media_calls[idx]);
+      p_media_cntrs->ul.mac_rx_errs   = MAKE_CNTR(inst_no, attr_no, 11, media_calls[idx]);
+    }
+    break;
+  }
+  default:
+    hadAction = false;
+    break;
+  }
+
+  if (hadAction) {
+    OPENER_TRACE_INFO(
+      "Eth Link PreCallback: %s, i %" PRIu32 ", a %" PRIu16 ", s %" PRIu8 "\n",
+      instance->cip_class->class_name,
+      instance->instance_number,
+      attribute->attribute_number,
+      service);
+  }
+  return status;
+}
+
+
+EipStatus EthLnkPostGetCallback
+(
+  CipInstance *const instance,
+  CipAttributeStruct *const attribute,
+  CipByte service
+)
+{
+  CipUdint  inst_no = instance->instance_number;
+  EipStatus status = kEipStatusOk;
+
+  if (kEthLinkGetAndClear == (service & 0x7f)) {
+    OPENER_TRACE_INFO(
+      "Eth Link PostCallback: %s, i %" PRIu32 ", a %" PRIu16 ", s %" PRIu8 "\n",
+      instance->cip_class->class_name,
+      inst_no,
+      attribute->attribute_number,
+      service);
+    /* Clear the instance specific object counters. In this dummy function we only
+     *  clear our GetAttributeSingle PreCallback execution counters. */
+    switch (attribute->attribute_number) {
+    case 4:
+      iface_calls[inst_no-1] = 0u;
+      break;
+    case 5:
+      media_calls[inst_no-1] = 0u;
+      /* This is a concession to the conformance test tool that expects
+       *  the media counters to be zero after a GetAndClear service. */
+      for (int idx = 0; idx < 12; ++idx) {
+        g_ethernet_link[inst_no-1].media_cntrs.cntr32[idx] = 0u;
+      }
+      break;
+    default:
+      OPENER_TRACE_INFO(
+        "Wrong attribute number %" PRIu16 " in GetAndClear callback\n",
+        attribute->attribute_number);
+      break;
+    }
+  }
+  return status;
+}
+#endif /* ... && 0 != OPENER_ETHLINK_CNTRS_ENABLE */

+ 42 - 0
source/src/ports/POSIX/sample_application/ethlinkcbs.h

@@ -0,0 +1,42 @@
+/******************************************************************************
+ * Copyright (c) 2019, Rockwell Automation, Inc.
+ * All rights reserved.
+ *
+ *****************************************************************************/
+#ifndef OPENER_ETHLINKCBS_H_
+#define OPENER_ETHLINKCBS_H_
+/** @file
+ * @brief Declaration of Ethernet Link object callbacks
+ *
+ * This header declares the Ethernet Link object callbacks. These callbacks
+ *  handle the update and clear operation for the interface and media counters
+ *  of every Ethernet Link object of our device.
+ */
+
+
+/*---------------------------------------------------------------------------*/
+/*                               INCLUDES                                    */
+/*---------------------------------------------------------------------------*/
+#include "typedefs.h"
+#include "ciptypes.h"
+
+/*---------------------------------------------------------------------------*/
+/*                             PROTOTYPES                                    */
+/*---------------------------------------------------------------------------*/
+
+EipStatus EthLnkPreGetCallback
+(
+    CipInstance *const instance,
+    CipAttributeStruct *const attribute,
+    CipByte service
+);
+
+EipStatus EthLnkPostGetCallback
+(
+    CipInstance *const instance,
+    CipAttributeStruct *const attribute,
+    CipByte service
+);
+
+
+#endif  /* #ifndef OPENER_ETHLINKCBS_H_ */

+ 12 - 1
source/src/ports/POSIX/sample_application/opener_user_conf.h

@@ -43,9 +43,10 @@
   #define OPENER_IS_DLR_DEVICE  0
 #endif
 
-#if defined(OPENER_IS_DLR_DEVICE) && OPENER_IS_DLR_DEVICE != 0
+#if defined(OPENER_IS_DLR_DEVICE) && 0 != OPENER_IS_DLR_DEVICE
   /* Enable all the stuff the DLR device depends on */
   #define OPENER_TCPIP_IFACE_CFG_SETTABLE 1
+  #define OPENER_ETHLINK_CNTRS_ENABLE     1
   #define OPENER_ETHLINK_LABEL_ENABLE     1
   #define OPENER_ETHLINK_INSTANCE_CNT     3
 #endif
@@ -89,6 +90,16 @@
   #define OPENER_ETHLINK_LABEL_ENABLE  0
 #endif
 
+/** @brief Set this define if you need Counters for Ethernet Link object
+ *
+ * This define enables the Media Counters (attribute #5) which are required
+ *  for a DLR device. Also the Interface Counters (attribute #4) are enabled
+ *  which become required because the Media Counters are implemented.
+ */
+#ifndef OPENER_ETHLINK_CNTRS_ENABLE
+  #define OPENER_ETHLINK_CNTRS_ENABLE 0
+#endif
+
 
 
 /** @brief Define the number of objects that may be used in connections

+ 37 - 0
source/src/ports/POSIX/sample_application/sampleapplication.c

@@ -15,6 +15,10 @@
 #include "ciptcpipinterface.h"
 #include "cipqos.h"
 #include "nvdata.h"
+#if defined(OPENER_ETHLINK_CNTRS_ENABLE) && 0 != OPENER_ETHLINK_CNTRS_ENABLE
+  #include "cipethernetlink.h"
+  #include "ethlinkcbs.h"
+#endif
 
 #define DEMO_APP_INPUT_ASSEMBLY_NUM                100 //0x064
 #define DEMO_APP_OUTPUT_ASSEMBLY_NUM               150 //0x096
@@ -78,6 +82,39 @@ EipStatus ApplicationInitialization(void) {
                        NvTcpipSetCallback,
                        kNvDataFunc);
 
+#if defined(OPENER_ETHLINK_CNTRS_ENABLE) && 0 != OPENER_ETHLINK_CNTRS_ENABLE
+  /* For the Ethernet Interface & Media Counters connect a PreGetCallback and
+   *  a PostGetCallback.
+   * The PreGetCallback is used to fetch the counters from the hardware.
+   * The PostGetCallback is utilized by the GetAndClear service to clear
+   *  the hardware counters after the current data have been transmitted.
+   */
+  {
+    CipClass *p_eth_link_class = GetCipClass(kCipEthernetLinkClassCode);
+    InsertGetSetCallback(p_eth_link_class,
+                         EthLnkPreGetCallback,
+                         kPreGetFunc);
+    InsertGetSetCallback(p_eth_link_class,
+                         EthLnkPostGetCallback,
+                         kPostGetFunc);
+    /* Specify the attributes for which the callback should be executed. */
+    for (int idx = 0; idx < OPENER_ETHLINK_INSTANCE_CNT; ++idx)
+    {
+      CipAttributeStruct *p_eth_link_attr;
+      CipInstance *p_eth_link_inst =
+        GetCipInstance(p_eth_link_class, idx+1);
+      OPENER_ASSERT(p_eth_link_inst);
+
+      /* Interface counters attribute */
+      p_eth_link_attr = GetCipAttribute(p_eth_link_inst, 4);
+      p_eth_link_attr->attribute_flags |= (kPreGetFunc | kPostGetFunc);
+      /* Media counters attribute */
+      p_eth_link_attr = GetCipAttribute(p_eth_link_inst, 5);
+      p_eth_link_attr->attribute_flags |= (kPreGetFunc | kPostGetFunc);
+    }
+  }
+#endif
+
   return kEipStatusOk;
 }
 

+ 5 - 1
source/src/ports/WIN32/sample_application/CMakeLists.txt

@@ -11,4 +11,8 @@ opener_platform_support("INCLUDES")
 
 opener_platform_support("INCLUDES")
 
-add_library(SAMPLE_APP sampleapplication.c)
+if (OPENER_IS_DLR_DEVICE)
+  set( SAMPLE_APP_DLR_ONLY_SRC ethlinkcbs.c )
+endif()
+
+add_library(SAMPLE_APP sampleapplication.c ${SAMPLE_APP_DLR_ONLY_SRC})

+ 169 - 0
source/src/ports/WIN32/sample_application/ethlinkcbs.c

@@ -0,0 +1,169 @@
+/******************************************************************************
+ * Copyright (c) 2019, Rockwell Automation, Inc.
+ * All rights reserved.
+ *
+ *****************************************************************************/
+
+/** @file
+ * @brief Ethernet Link object callbacks
+ *
+ * This module implements the Ethernet Link object callbacks. These callbacks
+ *  handle the update and clear operation for the interface and media counters
+ *  of every Ethernet Link object of our device.
+ *
+ * The current implementation is only a dummy implementation that doesn't
+ *  return real counters of the interface(s). It is only intended to check
+ *  whether the EIP stack transmits the counters at the right position in
+ *  the response while we're are filling the counters in the Ethernet Link
+ *  counter attributes by their union member names.
+ */
+
+/*---------------------------------------------------------------------------*/
+/*                               INCLUDES                                    */
+/*---------------------------------------------------------------------------*/
+#include "ethlinkcbs.h"
+
+#include "cipethernetlink.h"
+#include "trace.h"
+
+/*---------------------------------------------------------------------------*/
+/*                                LOCALS                                     */
+/*---------------------------------------------------------------------------*/
+
+/* These are "dummy" counters that are only used by the check code to be able
+ *  to transmit interface and media counters that change with each
+ *  GetAttribute explicit request.
+ */
+static CipUdint iface_calls[OPENER_ETHLINK_INSTANCE_CNT];
+static CipUdint media_calls[OPENER_ETHLINK_INSTANCE_CNT];
+
+/*---------------------------------------------------------------------------*/
+/*                           IMPLEMENTATION                                  */
+/*---------------------------------------------------------------------------*/
+
+#if defined(OPENER_ETHLINK_CNTRS_ENABLE) && 0 != OPENER_ETHLINK_CNTRS_ENABLE
+/*---------------------------------------------------------------------------*/
+/* Here we implement the functions delivering some dummy data created from   */
+/*  the instance number, the attribute number, the position of the data      */
+/*  field in the counters union and the count of GetAttributeSingle calls    */
+/*  for that attribute since startup or last GetAndClear service.            */
+/* The returned data is calculated using MAKE_CNTR() in a way that the input */
+/*  values can be easily decoded from the decimal view in the Wireshark log. */
+/* This is meant as debugging aid and to check if the individual counter     */
+/*  value is sent at the right position in the Get* service response.        */
+
+#define MAKE_CNTR(inst, attr, idx, cnt) ((10000000u * inst) + (100000u * attr) + (1000u * idx) + cnt)
+
+EipStatus EthLnkPreGetCallback
+(
+  CipInstance *const instance,
+  CipAttributeStruct *const attribute,
+  CipByte service
+)
+{
+  bool hadAction = true;
+  EipStatus status = kEipStatusOk;
+
+  CipUint attr_no = attribute->attribute_number;
+  /* ATTENTION: Array indices run from 0..(N-1), instance numbers from 1..N */
+  CipUdint inst_no  = instance->instance_number;
+  unsigned idx = inst_no-1;
+  switch (attr_no) {
+  case 4: {
+    CipEthernetLinkInterfaceCounters *p_iface_cntrs = &g_ethernet_link[idx].interface_cntrs;
+
+    ++iface_calls[idx];  /* Count successful calls */
+    p_iface_cntrs->ul.in_octets   = MAKE_CNTR(inst_no, attr_no,  0, iface_calls[idx]);
+    p_iface_cntrs->ul.in_ucast    = MAKE_CNTR(inst_no, attr_no,  1, iface_calls[idx]);
+    p_iface_cntrs->ul.in_nucast   = MAKE_CNTR(inst_no, attr_no,  2, iface_calls[idx]);
+    p_iface_cntrs->ul.in_discards = MAKE_CNTR(inst_no, attr_no,  3, iface_calls[idx]);
+    p_iface_cntrs->ul.in_errors   = MAKE_CNTR(inst_no, attr_no,  4, iface_calls[idx]);
+    p_iface_cntrs->ul.in_unknown_protos = MAKE_CNTR(inst_no, attr_no, 5, iface_calls[idx]);
+    p_iface_cntrs->ul.out_octets  = MAKE_CNTR(inst_no, attr_no,  6, iface_calls[idx]);
+    p_iface_cntrs->ul.out_ucast   = MAKE_CNTR(inst_no, attr_no,  7, iface_calls[idx]);
+    p_iface_cntrs->ul.out_nucast  = MAKE_CNTR(inst_no, attr_no,  8, iface_calls[idx]);
+    p_iface_cntrs->ul.out_discards = MAKE_CNTR(inst_no, attr_no, 9, iface_calls[idx]);
+    p_iface_cntrs->ul.out_errors  = MAKE_CNTR(inst_no, attr_no, 10, iface_calls[idx]);
+    break;
+  }
+  case 5: {
+    CipEthernetLinkMediaCounters *p_media_cntrs = &g_ethernet_link[idx].media_cntrs;
+
+    ++media_calls[idx];  /* Count successful calls */
+    /* The 1 != mediaCalls[idx] is a concession to the conformance test tool that
+     *  expects the media counters to be zero after a GetAndClear service.
+     * This way we always transmit zeros after reset or a GetAndClear service. */
+    if (1 != media_calls[idx]) {
+      p_media_cntrs->ul.align_errs    = MAKE_CNTR(inst_no, attr_no,  0, media_calls[idx]);
+      p_media_cntrs->ul.fcs_errs      = MAKE_CNTR(inst_no, attr_no,  1, media_calls[idx]);
+      p_media_cntrs->ul.single_coll   = MAKE_CNTR(inst_no, attr_no,  2, media_calls[idx]);
+      p_media_cntrs->ul.multi_coll    = MAKE_CNTR(inst_no, attr_no,  3, media_calls[idx]);
+      p_media_cntrs->ul.sqe_test_errs = MAKE_CNTR(inst_no, attr_no,  4, media_calls[idx]);
+      p_media_cntrs->ul.def_trans     = MAKE_CNTR(inst_no, attr_no,  5, media_calls[idx]);
+      p_media_cntrs->ul.late_coll     = MAKE_CNTR(inst_no, attr_no,  6, media_calls[idx]);
+      p_media_cntrs->ul.exc_coll      = MAKE_CNTR(inst_no, attr_no,  7, media_calls[idx]);
+      p_media_cntrs->ul.mac_tx_errs   = MAKE_CNTR(inst_no, attr_no,  8, media_calls[idx]);
+      p_media_cntrs->ul.crs_errs      = MAKE_CNTR(inst_no, attr_no,  9, media_calls[idx]);
+      p_media_cntrs->ul.frame_too_long= MAKE_CNTR(inst_no, attr_no, 10, media_calls[idx]);
+      p_media_cntrs->ul.mac_rx_errs   = MAKE_CNTR(inst_no, attr_no, 11, media_calls[idx]);
+    }
+    break;
+  }
+  default:
+    hadAction = false;
+    break;
+  }
+
+  if (hadAction) {
+    OPENER_TRACE_INFO(
+      "Eth Link PreCallback: %s, i %" PRIu32 ", a %" PRIu16 ", s %" PRIu8 "\n",
+      instance->cip_class->class_name,
+      instance->instance_number,
+      attribute->attribute_number,
+      service);
+  }
+  return status;
+}
+
+
+EipStatus EthLnkPostGetCallback
+(
+  CipInstance *const instance,
+  CipAttributeStruct *const attribute,
+  CipByte service
+)
+{
+  CipUdint  inst_no = instance->instance_number;
+  EipStatus status = kEipStatusOk;
+
+  if (kEthLinkGetAndClear == (service & 0x7f)) {
+    OPENER_TRACE_INFO(
+      "Eth Link PostCallback: %s, i %" PRIu32 ", a %" PRIu16 ", s %" PRIu8 "\n",
+      instance->cip_class->class_name,
+      inst_no,
+      attribute->attribute_number,
+      service);
+    /* Clear the instance specific object counters. In this dummy function we only
+     *  clear our GetAttributeSingle PreCallback execution counters. */
+    switch (attribute->attribute_number) {
+    case 4:
+      iface_calls[inst_no-1] = 0u;
+      break;
+    case 5:
+      media_calls[inst_no-1] = 0u;
+      /* This is a concession to the conformance test tool that expects
+       *  the media counters to be zero after a GetAndClear service. */
+      for (int idx = 0; idx < 12; ++idx) {
+        g_ethernet_link[inst_no-1].media_cntrs.cntr32[idx] = 0u;
+      }
+      break;
+    default:
+      OPENER_TRACE_INFO(
+        "Wrong attribute number %" PRIu16 " in GetAndClear callback\n",
+        attribute->attribute_number);
+      break;
+    }
+  }
+  return status;
+}
+#endif /* ... && 0 != OPENER_ETHLINK_CNTRS_ENABLE */

+ 42 - 0
source/src/ports/WIN32/sample_application/ethlinkcbs.h

@@ -0,0 +1,42 @@
+/******************************************************************************
+ * Copyright (c) 2019, Rockwell Automation, Inc.
+ * All rights reserved.
+ *
+ *****************************************************************************/
+#ifndef OPENER_ETHLINKCBS_H_
+#define OPENER_ETHLINKCBS_H_
+/** @file
+ * @brief Declaration of Ethernet Link object callbacks
+ *
+ * This header declares the Ethernet Link object callbacks. These callbacks
+ *  handle the update and clear operation for the interface and media counters
+ *  of every Ethernet Link object of our device.
+ */
+
+
+/*---------------------------------------------------------------------------*/
+/*                               INCLUDES                                    */
+/*---------------------------------------------------------------------------*/
+#include "typedefs.h"
+#include "ciptypes.h"
+
+/*---------------------------------------------------------------------------*/
+/*                             PROTOTYPES                                    */
+/*---------------------------------------------------------------------------*/
+
+EipStatus EthLnkPreGetCallback
+(
+    CipInstance *const instance,
+    CipAttributeStruct *const attribute,
+    CipByte service
+);
+
+EipStatus EthLnkPostGetCallback
+(
+    CipInstance *const instance,
+    CipAttributeStruct *const attribute,
+    CipByte service
+);
+
+
+#endif  /* #ifndef OPENER_ETHLINKCBS_H_ */

+ 13 - 1
source/src/ports/WIN32/sample_application/opener_user_conf.h

@@ -42,9 +42,10 @@ typedef unsigned short in_port_t;
   #define OPENER_IS_DLR_DEVICE  0
 #endif
 
-#if defined(OPENER_IS_DLR_DEVICE) && OPENER_IS_DLR_DEVICE != 0
+#if defined(OPENER_IS_DLR_DEVICE) && 0 != OPENER_IS_DLR_DEVICE
   /* Enable all the stuff the DLR device depends on */
   #define OPENER_TCPIP_IFACE_CFG_SETTABLE 1
+  #define OPENER_ETHLINK_CNTRS_ENABLE     1
   #define OPENER_ETHLINK_LABEL_ENABLE     1
   #define OPENER_ETHLINK_INSTANCE_CNT     3
 #endif
@@ -88,6 +89,17 @@ typedef unsigned short in_port_t;
   #define OPENER_ETHLINK_LABEL_ENABLE  0
 #endif
 
+/** @brief Set this define if you need Counters for Ethernet Link object
+ *
+ * This define enables the Media Counters (attribute #5) which are required
+ *  for a DLR device. Also the Interface Counters (attribute #4) are enabled
+ *  which become required because the Media Counters are implemented.
+ */
+#ifndef OPENER_ETHLINK_CNTRS_ENABLE
+  #define OPENER_ETHLINK_CNTRS_ENABLE 0
+#endif
+
+
 /** @brief Define the number of objects that may be used in connections
  *
  *  This number needs only to consider additional objects. Connections to

+ 37 - 0
source/src/ports/WIN32/sample_application/sampleapplication.c

@@ -12,6 +12,10 @@
 #include "ciptcpipinterface.h"
 #include "cipqos.h"
 #include "nvdata.h"
+#if defined(OPENER_ETHLINK_CNTRS_ENABLE) && 0 != OPENER_ETHLINK_CNTRS_ENABLE
+  #include "cipethernetlink.h"
+  #include "ethlinkcbs.h"
+#endif
 
 #define DEMO_APP_INPUT_ASSEMBLY_NUM                100 //0x064
 #define DEMO_APP_OUTPUT_ASSEMBLY_NUM               150 //0x096
@@ -74,6 +78,39 @@ EipStatus ApplicationInitialization(void) {
                        NvTcpipSetCallback,
                        kNvDataFunc);
 
+#if defined(OPENER_ETHLINK_CNTRS_ENABLE) && 0 != OPENER_ETHLINK_CNTRS_ENABLE
+  /* For the Ethernet Interface & Media Counters connect a PreGetCallback and
+   *  a PostGetCallback.
+   * The PreGetCallback is used to fetch the counters from the hardware.
+   * The PostGetCallback is utilized by the GetAndClear service to clear
+   *  the hardware counters after the current data have been transmitted.
+   */
+  {
+    CipClass *p_eth_link_class = GetCipClass(kCipEthernetLinkClassCode);
+    InsertGetSetCallback(p_eth_link_class,
+                         EthLnkPreGetCallback,
+                         kPreGetFunc);
+    InsertGetSetCallback(p_eth_link_class,
+                         EthLnkPostGetCallback,
+                         kPostGetFunc);
+    /* Specify the attributes for which the callback should be executed. */
+    for (int idx = 0; idx < OPENER_ETHLINK_INSTANCE_CNT; ++idx)
+    {
+      CipAttributeStruct *p_eth_link_attr;
+      CipInstance *p_eth_link_inst =
+        GetCipInstance(p_eth_link_class, idx+1);
+      OPENER_ASSERT(p_eth_link_inst);
+
+      /* Interface counters attribute */
+      p_eth_link_attr = GetCipAttribute(p_eth_link_inst, 4);
+      p_eth_link_attr->attribute_flags |= (kPreGetFunc | kPostGetFunc);
+      /* Media counters attribute */
+      p_eth_link_attr = GetCipAttribute(p_eth_link_inst, 5);
+      p_eth_link_attr->attribute_flags |= (kPreGetFunc | kPostGetFunc);
+    }
+  }
+#endif
+
   return kEipStatusOk;
 }