[qca-nss-ecm] Add OVS classifier
- Only unicast acceleration is supported
in this patch.
- Bridging, routing and multi bridge routing
are supported.
Change-Id: I8048b2d4739162072752e40ff10bfa2894849464
Signed-off-by: Murat Sezgin <msezgin@codeaurora.org>
diff --git a/Makefile b/Makefile
index fa35dbb..45c86bd 100644
--- a/Makefile
+++ b/Makefile
@@ -213,6 +213,12 @@
ccflags-$(ECM_IPV6_ENABLE) += -DECM_IPV6_ENABLE
# #############################################################################
+# Define ECM_CLASSIFIER_OVS_ENABLE=y in order to enable ovs classifier.
+# #############################################################################
+ecm-$(ECM_CLASSIFIER_OVS_ENABLE) += ecm_classifier_ovs.o
+ccflags-$(ECM_CLASSIFIER_OVS_ENABLE) += -DECM_CLASSIFIER_OVS_ENABLE
+
+# #############################################################################
# Define ECM_CLASSIFIER_MARK_ENABLE=y in order to enable mark classifier.
# #############################################################################
ECM_CLASSIFIER_MARK_ENABLE=y
@@ -329,6 +335,7 @@
# By turning off debugs you gain maximum ECM performance.
# #############################################################################
ccflags-y += -DECM_CLASSIFIER_DEBUG_LEVEL=1
+ccflags-y += -DECM_CLASSIFIER_OVS_DEBUG_LEVEL=1
ccflags-y += -DECM_CLASSIFIER_MARK_DEBUG_LEVEL=1
ccflags-y += -DECM_CLASSIFIER_DSCP_DEBUG_LEVEL=1
ccflags-y += -DECM_CLASSIFIER_HYFI_DEBUG_LEVEL=1
diff --git a/ecm_classifier.c b/ecm_classifier.c
index 6b5c354..1d2b31f 100644
--- a/ecm_classifier.c
+++ b/ecm_classifier.c
@@ -1,6 +1,6 @@
/*
**************************************************************************
- * Copyright (c) 2016, 2018-2019 The Linux Foundation. All rights reserved.
+ * Copyright (c) 2016, 2018-2020 The Linux Foundation. All rights reserved.
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all copies.
@@ -53,6 +53,9 @@
#ifdef ECM_CLASSIFIER_MARK_ENABLE
#include "ecm_classifier_mark.h"
#endif
+#ifdef ECM_CLASSIFIER_OVS_ENABLE
+#include "ecm_classifier_ovs.h"
+#endif
/*
* ecm_classifier_assign_classifier()
@@ -67,6 +70,7 @@
#ifdef ECM_CLASSIFIER_PCC_ENABLE
case ECM_CLASSIFIER_TYPE_PCC: {
struct ecm_classifier_pcc_instance *pcci;
+
pcci = ecm_classifier_pcc_instance_alloc(ci);
if (!pcci) {
DEBUG_TRACE("%p: Failed to create Parental Controls classifier\n", ci);
@@ -77,9 +81,24 @@
return (struct ecm_classifier_instance *)pcci;
}
#endif
+#ifdef ECM_CLASSIFIER_OVS_ENABLE
+ case ECM_CLASSIFIER_TYPE_OVS: {
+ struct ecm_classifier_ovs_instance *ecvi;
+
+ ecvi = ecm_classifier_ovs_instance_alloc(ci);
+ if (!ecvi) {
+ DEBUG_TRACE("%p: Failed to create ovs classifier\n", ci);
+ return NULL;
+ }
+ DEBUG_TRACE("%p: Created ovs classifier: %p\n", ci, ecvi);
+ ecm_db_connection_classifier_assign(ci, (struct ecm_classifier_instance *)ecvi);
+ return (struct ecm_classifier_instance *)ecvi;
+ }
+#endif
#ifdef ECM_CLASSIFIER_NL_ENABLE
case ECM_CLASSIFIER_TYPE_NL: {
struct ecm_classifier_nl_instance *cnli;
+
cnli = ecm_classifier_nl_instance_alloc(ci);
if (!cnli) {
DEBUG_TRACE("%p: Failed to create Netlink classifier\n", ci);
@@ -93,6 +112,7 @@
#ifdef ECM_CLASSIFIER_DSCP_ENABLE
case ECM_CLASSIFIER_TYPE_DSCP: {
struct ecm_classifier_dscp_instance *cdscpi;
+
cdscpi = ecm_classifier_dscp_instance_alloc(ci);
if (!cdscpi) {
DEBUG_TRACE("%p: Failed to create DSCP classifier\n", ci);
@@ -106,6 +126,7 @@
#ifdef ECM_CLASSIFIER_HYFI_ENABLE
case ECM_CLASSIFIER_TYPE_HYFI: {
struct ecm_classifier_hyfi_instance *chfi;
+
chfi = ecm_classifier_hyfi_instance_alloc(ci);
if (!chfi) {
DEBUG_TRACE("%p: Failed to create HyFi classifier\n", ci);
@@ -119,6 +140,7 @@
#ifdef ECM_CLASSIFIER_MARK_ENABLE
case ECM_CLASSIFIER_TYPE_MARK: {
struct ecm_classifier_mark_instance *ecmi;
+
ecmi = ecm_classifier_mark_instance_alloc(ci);
if (!ecmi) {
DEBUG_TRACE("%p: Failed to create mark classifier\n", ci);
diff --git a/ecm_classifier.h b/ecm_classifier.h
index 6575251..f24fd70 100644
--- a/ecm_classifier.h
+++ b/ecm_classifier.h
@@ -1,6 +1,6 @@
/*
**************************************************************************
- * Copyright (c) 2014-2015, 2018-2019, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2014-2015, 2018-2020 The Linux Foundation. All rights reserved.
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all copies.
@@ -34,6 +34,9 @@
#ifdef ECM_CLASSIFIER_NL_ENABLE
ECM_CLASSIFIER_TYPE_NL, /* Provides netlink interface */
#endif
+#ifdef ECM_CLASSIFIER_OVS_ENABLE
+ ECM_CLASSIFIER_TYPE_OVS, /* OVS classifier */
+#endif
#ifdef ECM_CLASSIFIER_PCC_ENABLE
ECM_CLASSIFIER_TYPE_PCC, /* Parental control subsystem support classifier */
#endif
@@ -115,8 +118,17 @@
* in this data structure to update the classifiers.
*/
struct ecm_classifier_rule_sync {
+ /*
+ * TODO: Use directional arrays for flow/return.
+ */
uint32_t flow_tx_packet_count;
+ uint32_t flow_tx_byte_count;
+ uint32_t flow_rx_packet_count;
+ uint32_t flow_rx_byte_count;
uint32_t return_tx_packet_count;
+ uint32_t return_tx_byte_count;
+ uint32_t return_rx_packet_count;
+ uint32_t return_rx_byte_count;
uint32_t reason;
};
diff --git a/ecm_classifier_ovs.c b/ecm_classifier_ovs.c
new file mode 100644
index 0000000..f0e7208
--- /dev/null
+++ b/ecm_classifier_ovs.c
@@ -0,0 +1,863 @@
+/*
+ **************************************************************************
+ * Copyright (c) 2019-2020, The Linux Foundation. All rights reserved.
+ * Permission to use, copy, modify, and/or distribute this software for
+ * any purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all copies.
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ **************************************************************************
+ */
+
+/*
+ * OVS Classifier.
+ * This classifier is called for the connections which are created
+ * on OVS interfaces. The connections can be routed or bridged connections.
+ * The classifier processes these connection flows and decides if the connection
+ * is ready to be accelerated. Moreover, it has an interface to an external classifier
+ * to make smart decisions on the flows, such as extracting the VLAN information from the
+ * OVS flow tables and gives it to the ECM front end to be passed to the acceleration engine.
+ */
+#include <linux/version.h>
+#include <linux/types.h>
+#include <linux/ip.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/debugfs.h>
+#include <linux/ctype.h>
+#include <net/tcp.h>
+#include <net/ipv6.h>
+#include <linux/inet.h>
+#include <linux/in.h>
+#include <linux/etherdevice.h>
+
+/*
+ * Debug output levels
+ * 0 = OFF
+ * 1 = ASSERTS / ERRORS
+ * 2 = 1 + WARN
+ * 3 = 2 + INFO
+ * 4 = 3 + TRACE
+ */
+#define DEBUG_LEVEL ECM_CLASSIFIER_OVS_DEBUG_LEVEL
+
+#include <ovsmgr.h>
+#include "ecm_types.h"
+#include "ecm_db_types.h"
+#include "ecm_state.h"
+#include "ecm_tracker.h"
+#include "ecm_classifier.h"
+#include "ecm_front_end_types.h"
+#include "ecm_tracker_datagram.h"
+#include "ecm_tracker_udp.h"
+#include "ecm_tracker_tcp.h"
+#include "ecm_db.h"
+#include "ecm_classifier_ovs.h"
+#include "ecm_front_end_common.h"
+#include "ecm_front_end_ipv4.h"
+#ifdef ECM_IPV6_ENABLE
+#include "ecm_front_end_ipv6.h"
+#endif
+#include "ecm_interface.h"
+
+/*
+ * Magic numbers
+ */
+#define ECM_CLASSIFIER_OVS_INSTANCE_MAGIC 0x2568
+
+/*
+ * struct ecm_classifier_ovs_instance
+ * State per connection for OVS classifier
+ */
+struct ecm_classifier_ovs_instance {
+ struct ecm_classifier_instance base; /* Base type */
+
+ uint32_t ci_serial; /* RO: Serial of the connection */
+
+ struct ecm_classifier_ovs_instance *next; /* Next classifier state instance (for accouting and reporting purposes) */
+ struct ecm_classifier_ovs_instance *prev; /* Next classifier state instance (for accouting and reporting purposes) */
+
+ struct ecm_classifier_process_response process_response;
+ /* Last process response computed */
+ int refs; /* Integer to trap we never go negative */
+#if (DEBUG_LEVEL > 0)
+ uint16_t magic;
+#endif
+};
+
+/*
+ * Operational control.
+ */
+static int ecm_classifier_ovs_enabled = 1; /* Operational behaviour */
+
+/*
+ * Management thread control
+ */
+static bool ecm_classifier_ovs_terminate_pending; /* True when the user wants us to terminate */
+
+/*
+ * Debugfs dentry object.
+ */
+static struct dentry *ecm_classifier_ovs_dentry;
+
+/*
+ * Locking of the classifier structures
+ */
+static DEFINE_SPINLOCK(ecm_classifier_ovs_lock); /* Protect SMP access. */
+
+/*
+ * List of our classifier instances
+ */
+static struct ecm_classifier_ovs_instance *ecm_classifier_ovs_instances = NULL;
+ /* list of all active instances */
+static int ecm_classifier_ovs_count = 0; /* Tracks number of instances allocated */
+
+/*
+ * ecm_classifier_ovs_ref()
+ * Ref
+ */
+static void ecm_classifier_ovs_ref(struct ecm_classifier_instance *ci)
+{
+ struct ecm_classifier_ovs_instance *ecvi;
+ ecvi = (struct ecm_classifier_ovs_instance *)ci;
+
+ DEBUG_CHECK_MAGIC(ecvi, ECM_CLASSIFIER_OVS_INSTANCE_MAGIC, "%p: magic failed", ecvi);
+ spin_lock_bh(&ecm_classifier_ovs_lock);
+ ecvi->refs++;
+ DEBUG_ASSERT(ecvi->refs > 0, "%p: ref wrap\n", ecvi);
+ DEBUG_TRACE("%p: ecvi ref %d\n", ecvi, ecvi->refs);
+ spin_unlock_bh(&ecm_classifier_ovs_lock);
+}
+
+/*
+ * ecm_classifier_ovs_deref()
+ * Deref
+ */
+static int ecm_classifier_ovs_deref(struct ecm_classifier_instance *ci)
+{
+ struct ecm_classifier_ovs_instance *ecvi;
+ ecvi = (struct ecm_classifier_ovs_instance *)ci;
+
+ DEBUG_CHECK_MAGIC(ecvi, ECM_CLASSIFIER_OVS_INSTANCE_MAGIC, "%p: magic failed", ecvi);
+ spin_lock_bh(&ecm_classifier_ovs_lock);
+ ecvi->refs--;
+ DEBUG_ASSERT(ecvi->refs >= 0, "%p: refs wrapped\n", ecvi);
+ DEBUG_TRACE("%p: ecvi deref %d\n", ecvi, ecvi->refs);
+ if (ecvi->refs) {
+ int refs = ecvi->refs;
+ spin_unlock_bh(&ecm_classifier_ovs_lock);
+ return refs;
+ }
+
+ /*
+ * Object to be destroyed
+ */
+ ecm_classifier_ovs_count--;
+ DEBUG_ASSERT(ecm_classifier_ovs_count >= 0, "%p: ecm_classifier_ovs_count wrap\n", ecvi);
+
+ /*
+ * UnLink the instance from our list
+ */
+ if (ecvi->next) {
+ ecvi->next->prev = ecvi->prev;
+ }
+ if (ecvi->prev) {
+ ecvi->prev->next = ecvi->next;
+ } else {
+ DEBUG_ASSERT(ecm_classifier_ovs_instances == ecvi, "%p: list bad %p\n", ecvi, ecm_classifier_ovs_instances);
+ ecm_classifier_ovs_instances = ecvi->next;
+ }
+ spin_unlock_bh(&ecm_classifier_ovs_lock);
+
+ /*
+ * Final
+ */
+ DEBUG_INFO("%p: Final ovs classifier instance\n", ecvi);
+ kfree(ecvi);
+
+ return 0;
+}
+
+/*
+ * ecm_classifier_ovs_interface_get_and_ref()
+ * Gets the OVS bridge port in the specified direction hierarchy.
+ */
+static inline struct net_device *ecm_classifier_ovs_interface_get_and_ref(struct ecm_db_connection_instance *ci,
+ ecm_db_obj_dir_t dir, bool ovs_port)
+{
+ int32_t if_first;
+ struct ecm_db_iface_instance *interfaces[ECM_DB_IFACE_HEIRARCHY_MAX];
+ int32_t i;
+
+ /*
+ * Get the interface hierarchy.
+ */
+ if_first = ecm_db_connection_interfaces_get_and_ref(ci, interfaces, dir);
+ if (if_first == ECM_DB_IFACE_HEIRARCHY_MAX) {
+ DEBUG_WARN("%p: Failed to get %s interfaces list\n", ci, ecm_db_obj_dir_strings[dir]);
+ return NULL;
+ }
+
+ /*
+ * Go through the hierarchy and get the OVS port.
+ */
+ for (i = ECM_DB_IFACE_HEIRARCHY_MAX - 1; i >= if_first; i--) {
+ struct net_device *dev = dev_get_by_index(&init_net,
+ ecm_db_iface_interface_identifier_get(interfaces[i]));
+ if (!dev) {
+ DEBUG_WARN("%p: Failed to get net device with %d index\n", ci, i);
+ continue;
+ }
+
+ if (ovs_port) {
+ if (ecm_interface_is_ovs_bridge_port(dev)) {
+ ecm_db_connection_interfaces_deref(interfaces, if_first);
+ DEBUG_TRACE("%p: %s_dev: %s at %d index is an OVS bridge port\n", ci, ecm_db_obj_dir_strings[dir], dev->name, i);
+ return dev;
+ }
+
+ } else {
+ if (ovsmgr_is_ovs_master(dev)) {
+ ecm_db_connection_interfaces_deref(interfaces, if_first);
+ DEBUG_TRACE("%p: %s_dev: %s at %d index is an OVS bridge dev\n", ci, ecm_db_obj_dir_strings[dir], dev->name, i);
+ return dev;
+ }
+ }
+
+ DEBUG_TRACE("%p: dev: %s index: %d\n", ci, dev->name, i);
+ dev_put(dev);
+ }
+
+ DEBUG_TRACE("%p: No OVS bridge port on the %s direction\n", ci, ecm_db_obj_dir_strings[dir]);
+ ecm_db_connection_interfaces_deref(interfaces, if_first);
+ return NULL;
+}
+
+/*
+ * ecm_classifier_ovs_process()
+ * Process new packet
+ *
+ * NOTE: This function would only ever be called if all other classifiers have failed.
+ */
+static void ecm_classifier_ovs_process(struct ecm_classifier_instance *aci, ecm_tracker_sender_type_t sender,
+ struct ecm_tracker_ip_header *ip_hdr, struct sk_buff *skb,
+ struct ecm_classifier_process_response *process_response)
+{
+ struct ecm_classifier_ovs_instance *ecvi = (struct ecm_classifier_ovs_instance *)aci;
+ struct ecm_db_connection_instance *ci;
+ struct net_device *from_dev = NULL;
+ struct net_device *to_dev = NULL;
+
+ DEBUG_CHECK_MAGIC(ecvi, ECM_CLASSIFIER_OVS_INSTANCE_MAGIC, "%p: invalid state magic\n", ecvi);
+
+ /*
+ * Not relevant to the connection if not enabled.
+ */
+ if (unlikely(!ecm_classifier_ovs_enabled)) {
+ /*
+ * Not relevant.
+ */
+ DEBUG_WARN("%p: ovs classifier is not enabled\n", aci);
+ goto not_relevant;
+ }
+
+ /*
+ * Get connection
+ */
+ ci = ecm_db_connection_serial_find_and_ref(ecvi->ci_serial);
+ if (!ci) {
+ /*
+ * Connection has gone from under us
+ */
+ DEBUG_WARN("%p: connection instance gone while processing classifier\n", aci);
+ goto not_relevant;
+ }
+
+ /*
+ * Get the possible OVS bridge ports. If both are NULL, the classifier is not
+ * relevant to this connection.
+ */
+ from_dev = ecm_classifier_ovs_interface_get_and_ref(ci, ECM_DB_OBJ_DIR_FROM, true);
+ to_dev = ecm_classifier_ovs_interface_get_and_ref(ci, ECM_DB_OBJ_DIR_TO, true);
+ if (!from_dev && !to_dev) {
+ /*
+ * So, the classifier is not relevant to this connection.
+ */
+ DEBUG_WARN("%p: None of the from/to interfaces are OVS bridge port\n", aci);
+ ecm_db_connection_deref(ci);
+ goto not_relevant;
+ }
+
+ /*
+ * If the connection is a bridge flow, both devices should be present.
+ */
+ if (!ecm_db_connection_is_routed_get(ci)) {
+ if (!from_dev || !to_dev) {
+ DEBUG_ERROR("%p: One of the ports is NULL from_dev: %p to_dev: %p\n", aci, from_dev, to_dev);
+ if (from_dev)
+ dev_put(from_dev);
+
+ if (to_dev)
+ dev_put(to_dev);
+
+ ecm_db_connection_deref(ci);
+ goto not_relevant;
+ }
+ }
+
+ if (from_dev)
+ dev_put(from_dev);
+
+ if (to_dev)
+ dev_put(to_dev);
+
+ /*
+ * Acceleration is permitted
+ */
+ spin_lock_bh(&ecm_classifier_ovs_lock);
+ ecvi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_YES;
+ ecvi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_ACCEL_MODE;
+ ecvi->process_response.accel_mode = ECM_CLASSIFIER_ACCELERATION_MODE_ACCEL;
+ *process_response = ecvi->process_response;
+ spin_unlock_bh(&ecm_classifier_ovs_lock);
+ ecm_db_connection_deref(ci);
+ return;
+
+not_relevant:
+ /*
+ * ecm_classifier_ovs_lock MUST be held
+ */
+ spin_lock_bh(&ecm_classifier_ovs_lock);
+ ecvi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_NO;
+ *process_response = ecvi->process_response;
+ spin_unlock_bh(&ecm_classifier_ovs_lock);
+ return;
+}
+
+/*
+ * ecm_classifier_ovs_type_get()
+ * Get type of classifier this is
+ */
+static ecm_classifier_type_t ecm_classifier_ovs_type_get(struct ecm_classifier_instance *aci)
+{
+ struct ecm_classifier_ovs_instance *ecvi;
+ ecvi = (struct ecm_classifier_ovs_instance *)aci;
+
+ DEBUG_CHECK_MAGIC(ecvi, ECM_CLASSIFIER_OVS_INSTANCE_MAGIC, "%p: magic failed", ecvi);
+ return ECM_CLASSIFIER_TYPE_OVS;
+}
+
+/*
+ * ecm_classifier_ovs_reclassify_allowed()
+ * Get whether reclassification is allowed
+ */
+static bool ecm_classifier_ovs_reclassify_allowed(struct ecm_classifier_instance *aci)
+{
+ struct ecm_classifier_ovs_instance *ecvi;
+ ecvi = (struct ecm_classifier_ovs_instance *)aci;
+
+ DEBUG_CHECK_MAGIC(ecvi, ECM_CLASSIFIER_OVS_INSTANCE_MAGIC, "%p: magic failed", ecvi);
+ return true;
+}
+
+/*
+ * ecm_classifier_ovs_reclassify()
+ * Reclassify
+ */
+static void ecm_classifier_ovs_reclassify(struct ecm_classifier_instance *aci)
+{
+ struct ecm_classifier_ovs_instance *ecvi;
+ ecvi = (struct ecm_classifier_ovs_instance *)aci;
+ DEBUG_CHECK_MAGIC(ecvi, ECM_CLASSIFIER_OVS_INSTANCE_MAGIC, "%p: magic failed", ecvi);
+
+ /*
+ * Revert back to MAYBE relevant - we will evaluate when we get the next process() call.
+ */
+ spin_lock_bh(&ecm_classifier_ovs_lock);
+ ecvi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_MAYBE;
+ spin_unlock_bh(&ecm_classifier_ovs_lock);
+}
+
+/*
+ * ecm_classifier_ovs_last_process_response_get()
+ * Get result code returned by the last process call
+ */
+static void ecm_classifier_ovs_last_process_response_get(struct ecm_classifier_instance *aci,
+ struct ecm_classifier_process_response *process_response)
+{
+ struct ecm_classifier_ovs_instance *ecvi;
+ ecvi = (struct ecm_classifier_ovs_instance *)aci;
+ DEBUG_CHECK_MAGIC(ecvi, ECM_CLASSIFIER_OVS_INSTANCE_MAGIC, "%p: magic failed", ecvi);
+
+ spin_lock_bh(&ecm_classifier_ovs_lock);
+ *process_response = ecvi->process_response;
+ spin_unlock_bh(&ecm_classifier_ovs_lock);
+}
+
+/*
+ * ecm_classifier_ovs_stats_sync()
+ * Sync the stats of the OVS flow.
+ */
+static inline void ecm_classifier_ovs_stats_sync(struct ovsmgr_dp_flow *flow,
+ uint32_t tx_pkts, uint32_t tx_bytes,uint32_t rx_pkts,uint32_t rx_bytes,
+ struct net_device *indev, struct net_device *outdev,
+ uint8_t *smac, uint8_t *dmac,
+ ip_addr_t sip, ip_addr_t dip,
+ uint16_t sport, uint16_t dport)
+{
+ struct ovsmgr_dp_flow_stats stats;
+
+ flow->indev = indev;
+ flow->outdev = outdev;
+
+ ether_addr_copy(flow->smac, smac);
+ ether_addr_copy(flow->dmac, dmac);
+
+ flow->tuple.src_port = sport;
+ flow->tuple.dst_port = dport;
+
+ if (flow->tuple.ip_version == 4) {
+ ECM_IP_ADDR_TO_NIN4_ADDR(flow->tuple.ipv4.src, sip);
+ ECM_IP_ADDR_TO_NIN4_ADDR(flow->tuple.ipv4.dst, dip);
+ DEBUG_TRACE("%p: STATS: src MAC: %pM src_dev: %s src: %pI4:%d proto: %d dest: %pI4:%d dest_dev: %s dest MAC: %pM\n",
+ flow, flow->smac, flow->indev->name, &flow->tuple.ipv4.src, flow->tuple.src_port, flow->tuple.protocol,
+ &flow->tuple.ipv4.dst, flow->tuple.dst_port, flow->outdev->name, flow->dmac);
+ } else {
+ ECM_IP_ADDR_TO_NIN6_ADDR(flow->tuple.ipv6.src, sip);
+ ECM_IP_ADDR_TO_NIN6_ADDR(flow->tuple.ipv6.dst, dip);
+ DEBUG_TRACE("%p: STATS: src MAC: %pM src_dev: %s src: %pI6:%d proto: %d dest: %pI6:%d dest_dev: %s dest MAC: %pM\n",
+ flow, flow->smac, flow->indev->name, &flow->tuple.ipv6.src, flow->tuple.src_port, flow->tuple.protocol,
+ &flow->tuple.ipv6.dst, flow->tuple.dst_port, flow->outdev->name, flow->dmac);
+ }
+
+ /*
+ * Set the flow stats and update the flow database.
+ */
+ stats.tx_pkts = tx_pkts;
+ stats.tx_bytes = tx_bytes;
+ stats.rx_pkts = rx_pkts;
+ stats.rx_bytes = rx_bytes;
+
+ ovsmgr_flow_stats_update(flow, &stats);
+}
+
+/*
+ * ecm_classifier_ovs_sync_to_stats()
+ * Common sync_to function for IPv4 and IPv6.
+ */
+static void ecm_classifier_ovs_sync_to_stats(struct ecm_classifier_instance *aci, struct ecm_classifier_rule_sync *sync)
+{
+ ip_addr_t src_ip;
+ ip_addr_t dst_ip;
+ struct ecm_db_connection_instance *ci;
+ struct ovsmgr_dp_flow flow;
+ struct net_device *from_dev;
+ struct net_device *to_dev;
+ struct net_device *br_dev;
+ uint8_t smac[ETH_ALEN];
+ uint8_t dmac[ETH_ALEN];
+ uint16_t sport;
+ uint16_t dport;
+
+ struct ecm_classifier_ovs_instance *ecvi = (struct ecm_classifier_ovs_instance *)aci;
+
+ DEBUG_CHECK_MAGIC(ecvi, ECM_CLASSIFIER_OVS_INSTANCE_MAGIC, "%p: magic failed", ecvi);
+
+ ci = ecm_db_connection_serial_find_and_ref(ecvi->ci_serial);
+ if (!ci) {
+ DEBUG_TRACE("%p: No ci found for %u\n", ecvi, ecvi->ci_serial);
+ return;
+ }
+
+ /*
+ * Get the possible OVS bridge ports.
+ */
+ from_dev = ecm_classifier_ovs_interface_get_and_ref(ci, ECM_DB_OBJ_DIR_FROM, true);
+ to_dev = ecm_classifier_ovs_interface_get_and_ref(ci, ECM_DB_OBJ_DIR_TO, true);
+ DEBUG_ASSERT(from_dev || to_dev, "%p: None of the from/to interfaces is OVS bridge port\n", aci);
+
+ /*
+ * IP version and protocol are common for routed and bridge flows.
+ */
+ flow.tuple.ip_version = ecm_db_connection_ip_version_get(ci);
+ flow.tuple.protocol = ecm_db_connection_protocol_get(ci);
+ flow.is_routed = ecm_db_connection_is_routed_get(ci);
+
+ /*
+ * Bridge flow
+ */
+ if (!flow.is_routed) {
+ /*
+ * Sync the flow direction (eth1 to eth2)
+ */
+ ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_FROM, smac);
+ ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_TO, dmac);
+
+ sport = ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_FROM);
+ dport = ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_TO);
+
+ ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_FROM, src_ip);
+ ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_TO, dst_ip);
+
+ ecm_classifier_ovs_stats_sync(&flow,
+ sync->flow_rx_packet_count, sync->flow_rx_byte_count,
+ sync->return_tx_packet_count, sync->return_tx_byte_count,
+ from_dev, to_dev,
+ smac, dmac,
+ src_ip, dst_ip,
+ sport, dport);
+
+ /*
+ * Sync the return direction (eth2 to eth1)
+ * All the flow parameters are reversed.
+ */
+ ecm_classifier_ovs_stats_sync(&flow,
+ sync->flow_tx_packet_count, sync->flow_tx_byte_count,
+ sync->return_rx_packet_count, sync->return_rx_byte_count,
+ to_dev, from_dev,
+ dmac, smac,
+ dst_ip, src_ip,
+ dport, sport);
+ goto done;
+ }
+
+ /*
+ * For routed flows both from and to side can be OVS bridge port, if there
+ * is a routed flow between two OVS bridges. (e.g: ovs-br1 and ovs-br2)
+ *
+ * PC1 -----> eth1-ovs-br1--->ovs-br2-eth2----->PC2
+ *
+ */
+ if (from_dev) {
+ /*
+ * from_dev = eth1
+ * br_dev = ovs-br1
+ */
+ br_dev = ecm_classifier_ovs_interface_get_and_ref(ci, ECM_DB_OBJ_DIR_FROM, false);
+ if (!br_dev) {
+ DEBUG_WARN("%p: from_dev = %s is a OVS bridge port, bridge interface is not found\n",
+ aci, from_dev->name);
+ goto done;
+ }
+ /*
+ * Sync the flow direction (eth1 to ovs-br1)
+ */
+ ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_FROM, smac);
+ ether_addr_copy(dmac, br_dev->dev_addr);
+
+ sport = ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_FROM);
+ dport = ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_TO);
+
+ ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_FROM, src_ip);
+ ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_TO, dst_ip);
+
+ ecm_classifier_ovs_stats_sync(&flow,
+ sync->return_tx_packet_count, sync->return_tx_byte_count,
+ sync->flow_rx_packet_count, sync->flow_rx_byte_count,
+ from_dev, br_dev,
+ smac, dmac,
+ src_ip, dst_ip,
+ sport, dport);
+
+ /*
+ * Sync the return direction (ovs-br1 to eth1)
+ * All the flow parameters are reversed.
+ */
+ ecm_classifier_ovs_stats_sync(&flow,
+ sync->flow_tx_packet_count, sync->flow_tx_byte_count,
+ sync->return_rx_packet_count, sync->return_rx_byte_count,
+ br_dev, from_dev,
+ dmac, smac,
+ dst_ip, src_ip,
+ dport, sport);
+ dev_put(br_dev);
+ }
+
+ if (to_dev) {
+ /*
+ * to_dev = eth2
+ * br_dev = ovs-br2
+ */
+ br_dev = ecm_classifier_ovs_interface_get_and_ref(ci, ECM_DB_OBJ_DIR_TO, false);
+ if (!br_dev) {
+ DEBUG_WARN("%p: to_dev = %s is a OVS bridge port, bridge interface is not found\n",
+ aci, to_dev->name);
+ goto done;
+ }
+
+ /*
+ * Sync the flow direction (ovs-br2 to eth2)
+ */
+ ether_addr_copy(smac, br_dev->dev_addr);
+ ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_TO, dmac);
+
+ sport = ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_FROM);
+ dport = ecm_db_connection_port_get(ci, ECM_DB_OBJ_DIR_TO);
+
+ ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_FROM, src_ip);
+ ecm_db_connection_address_get(ci, ECM_DB_OBJ_DIR_TO, dst_ip);
+
+ ecm_classifier_ovs_stats_sync(&flow,
+ sync->return_tx_packet_count, sync->return_tx_byte_count,
+ sync->flow_rx_packet_count, sync->flow_rx_byte_count,
+ br_dev, to_dev,
+ smac, dmac,
+ src_ip, dst_ip,
+ sport, dport);
+ /*
+ * Sync the return direction (eth2 to ovs-br2)
+ * All the flow parameters are reversed.
+ */
+ ecm_classifier_ovs_stats_sync(&flow,
+ sync->flow_tx_packet_count, sync->flow_tx_byte_count,
+ sync->return_rx_packet_count, sync->return_rx_byte_count,
+ to_dev, br_dev,
+ dmac, smac,
+ dst_ip, src_ip,
+ dport, sport);
+ dev_put(br_dev);
+ }
+
+done:
+ ecm_db_connection_deref(ci);
+
+ if (from_dev)
+ dev_put(from_dev);
+ if (to_dev)
+ dev_put(to_dev);
+}
+
+/*
+ * ecm_classifier_ovs_sync_to_v4()
+ * Front end is pushing accel engine state to us
+ */
+static void ecm_classifier_ovs_sync_to_v4(struct ecm_classifier_instance *aci, struct ecm_classifier_rule_sync *sync)
+{
+ /*
+ * Nothing to update.
+ * We only care about flows that are actively being accelerated.
+ */
+ if (!(sync->flow_tx_packet_count || sync->return_tx_packet_count)) {
+ return;
+ }
+
+ /*
+ * Handle only the stats sync. We don't care about the evict or flush syncs.
+ */
+ if (sync->reason != ECM_FRONT_END_IPV4_RULE_SYNC_REASON_STATS) {
+ return;
+ }
+
+ /*
+ * Common sync_to function call.
+ */
+ ecm_classifier_ovs_sync_to_stats(aci, sync);
+}
+
+/*
+ * ecm_classifier_ovs_sync_from_v4()
+ * Front end is retrieving accel engine state from us
+ */
+static void ecm_classifier_ovs_sync_from_v4(struct ecm_classifier_instance *aci, struct ecm_classifier_rule_create *ecrc)
+{
+ struct ecm_classifier_ovs_instance *ecvi __attribute__((unused));
+
+ ecvi = (struct ecm_classifier_ovs_instance *)aci;
+ DEBUG_CHECK_MAGIC(ecvi, ECM_CLASSIFIER_OVS_INSTANCE_MAGIC, "%p: magic failed", ecvi);
+}
+
+/*
+ * ecm_classifier_ovs_sync_to_v6()
+ * Front end is pushing accel engine state to us
+ */
+static void ecm_classifier_ovs_sync_to_v6(struct ecm_classifier_instance *aci, struct ecm_classifier_rule_sync *sync)
+{
+ /*
+ * Nothing to update.
+ * We only care about flows that are actively being accelerated.
+ */
+ if (!(sync->flow_tx_packet_count || sync->return_tx_packet_count)) {
+ return;
+ }
+
+ /*
+ * Handle only the stats sync. We don't care about the evict or flush syncs.
+ */
+ if (sync->reason != ECM_FRONT_END_IPV6_RULE_SYNC_REASON_STATS) {
+ return;
+ }
+
+ /*
+ * Common sync_to function call.
+ */
+ ecm_classifier_ovs_sync_to_stats(aci, sync);
+}
+
+/*
+ * ecm_classifier_ovs_sync_from_v6()
+ * Front end is retrieving accel engine state from us
+ */
+static void ecm_classifier_ovs_sync_from_v6(struct ecm_classifier_instance *aci, struct ecm_classifier_rule_create *ecrc)
+{
+ struct ecm_classifier_ovs_instance *ecvi __attribute__((unused));
+
+ ecvi = (struct ecm_classifier_ovs_instance *)aci;
+ DEBUG_CHECK_MAGIC(ecvi, ECM_CLASSIFIER_OVS_INSTANCE_MAGIC, "%p: magic failed", ecvi);
+}
+
+#ifdef ECM_STATE_OUTPUT_ENABLE
+/*
+ * ecm_classifier_ovs_state_get()
+ * Gets the state of this classfier and outputs it to debugfs.
+ */
+static int ecm_classifier_ovs_state_get(struct ecm_classifier_instance *ci, struct ecm_state_file_instance *sfi)
+{
+ int result;
+ struct ecm_classifier_ovs_instance *ecvi;
+ struct ecm_classifier_process_response process_response;
+
+ ecvi = (struct ecm_classifier_ovs_instance *)ci;
+ DEBUG_CHECK_MAGIC(ecvi, ECM_CLASSIFIER_OVS_INSTANCE_MAGIC, "%p: magic failed", ecvi);
+
+ if ((result = ecm_state_prefix_add(sfi, "ovs"))) {
+ return result;
+ }
+
+ spin_lock_bh(&ecm_classifier_ovs_lock);
+ process_response = ecvi->process_response;
+ spin_unlock_bh(&ecm_classifier_ovs_lock);
+
+ /*
+ * Output our last process response
+ */
+ if ((result = ecm_classifier_process_response_state_get(sfi, &process_response))) {
+ return result;
+ }
+
+ return ecm_state_prefix_remove(sfi);
+}
+#endif
+
+/*
+ * ecm_classifier_ovs_instance_alloc()
+ * Allocate an instance of the ovs classifier
+ */
+struct ecm_classifier_ovs_instance *ecm_classifier_ovs_instance_alloc(struct ecm_db_connection_instance *ci)
+{
+ struct ecm_classifier_ovs_instance *ecvi;
+
+ /*
+ * Allocate the instance
+ */
+ ecvi = (struct ecm_classifier_ovs_instance *)kzalloc(sizeof(struct ecm_classifier_ovs_instance), GFP_ATOMIC | __GFP_NOWARN);
+ if (!ecvi) {
+ DEBUG_WARN("i%p: Failed to allocate ovs Classifier instance\n", ci);
+ return NULL;
+ }
+
+ DEBUG_SET_MAGIC(ecvi, ECM_CLASSIFIER_OVS_INSTANCE_MAGIC);
+ ecvi->refs = 1;
+
+ /*
+ * Methods generic to all classifiers.
+ */
+ ecvi->base.process = ecm_classifier_ovs_process;
+ ecvi->base.sync_from_v4 = ecm_classifier_ovs_sync_from_v4;
+ ecvi->base.sync_to_v4 = ecm_classifier_ovs_sync_to_v4;
+ ecvi->base.sync_from_v6 = ecm_classifier_ovs_sync_from_v6;
+ ecvi->base.sync_to_v6 = ecm_classifier_ovs_sync_to_v6;
+ ecvi->base.type_get = ecm_classifier_ovs_type_get;
+ ecvi->base.reclassify_allowed = ecm_classifier_ovs_reclassify_allowed;
+ ecvi->base.reclassify = ecm_classifier_ovs_reclassify;
+ ecvi->base.last_process_response_get = ecm_classifier_ovs_last_process_response_get;
+#ifdef ECM_STATE_OUTPUT_ENABLE
+ ecvi->base.state_get = ecm_classifier_ovs_state_get;
+#endif
+ ecvi->base.ref = ecm_classifier_ovs_ref;
+ ecvi->base.deref = ecm_classifier_ovs_deref;
+ ecvi->ci_serial = ecm_db_connection_serial_get(ci);
+
+ ecvi->process_response.process_actions = 0;
+ ecvi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_MAYBE;
+
+ /*
+ * Final check if we are pending termination
+ */
+ spin_lock_bh(&ecm_classifier_ovs_lock);
+ if (ecm_classifier_ovs_terminate_pending) {
+ spin_unlock_bh(&ecm_classifier_ovs_lock);
+ DEBUG_WARN("%p: Terminating\n", ci);
+ kfree(ecvi);
+ return NULL;
+ }
+
+ /*
+ * Link the new instance into our list at the head
+ */
+ ecvi->next = ecm_classifier_ovs_instances;
+ if (ecm_classifier_ovs_instances) {
+ ecm_classifier_ovs_instances->prev = ecvi;
+ }
+ ecm_classifier_ovs_instances = ecvi;
+
+ /*
+ * Increment stats
+ */
+ ecm_classifier_ovs_count++;
+ DEBUG_ASSERT(ecm_classifier_ovs_count > 0, "%p: ecm_classifier_ovs_count wrap for instance: %p\n", ci, ecvi);
+ spin_unlock_bh(&ecm_classifier_ovs_lock);
+
+ DEBUG_INFO("%p: ovs classifier instance alloc: %p\n", ci, ecvi);
+ return ecvi;
+}
+EXPORT_SYMBOL(ecm_classifier_ovs_instance_alloc);
+
+/*
+ * ecm_classifier_ovs_init()
+ */
+int ecm_classifier_ovs_init(struct dentry *dentry)
+{
+ DEBUG_INFO("ovs classifier Module init\n");
+
+ ecm_classifier_ovs_dentry = debugfs_create_dir("ecm_classifier_ovs", dentry);
+ if (!ecm_classifier_ovs_dentry) {
+ DEBUG_ERROR("Failed to create ecm ovs directory in debugfs\n");
+ return -1;
+ }
+
+ if (!debugfs_create_u32("enabled", S_IRUGO | S_IWUSR, ecm_classifier_ovs_dentry,
+ (u32 *)&ecm_classifier_ovs_enabled)) {
+ DEBUG_ERROR("Failed to create ovs enabled file in debugfs\n");
+ debugfs_remove_recursive(ecm_classifier_ovs_dentry);
+ return -1;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(ecm_classifier_ovs_init);
+
+/*
+ * ecm_classifier_ovs_exit()
+ */
+void ecm_classifier_ovs_exit(void)
+{
+ DEBUG_INFO("ovs classifier Module exit\n");
+
+ spin_lock_bh(&ecm_classifier_ovs_lock);
+ ecm_classifier_ovs_terminate_pending = true;
+ spin_unlock_bh(&ecm_classifier_ovs_lock);
+
+ /*
+ * Remove the debugfs files recursively.
+ */
+ if (ecm_classifier_ovs_dentry) {
+ debugfs_remove_recursive(ecm_classifier_ovs_dentry);
+ }
+
+}
+EXPORT_SYMBOL(ecm_classifier_ovs_exit);
diff --git a/ecm_classifier_ovs.h b/ecm_classifier_ovs.h
new file mode 100644
index 0000000..1aaa7b7
--- /dev/null
+++ b/ecm_classifier_ovs.h
@@ -0,0 +1,18 @@
+/*
+ **************************************************************************
+ * Copyright (c) 2019-2020, The Linux Foundation. All rights reserved.
+ * Permission to use, copy, modify, and/or distribute this software for
+ * any purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all copies.
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ **************************************************************************
+ */
+
+struct ecm_classifier_ovs_instance;
+struct ecm_classifier_ovs_instance *ecm_classifier_ovs_instance_alloc(struct ecm_db_connection_instance *ci);
diff --git a/ecm_init.c b/ecm_init.c
index c8ef5bd..3f3205e 100644
--- a/ecm_init.c
+++ b/ecm_init.c
@@ -1,6 +1,6 @@
/*
**************************************************************************
- * Copyright (c) 2014-2016, 2018, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2014-2016, 2018, 2020, The Linux Foundation. All rights reserved.
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all copies.
@@ -59,6 +59,11 @@
extern int ecm_classifier_default_init(struct dentry *dentry);
extern void ecm_classifier_default_exit(void);
+#ifdef ECM_CLASSIFIER_OVS_ENABLE
+extern int ecm_classifier_ovs_init(struct dentry *dentry);
+extern void ecm_classifier_ovs_exit(void);
+#endif
+
#ifdef ECM_CLASSIFIER_MARK_ENABLE
extern int ecm_classifier_mark_init(struct dentry *dentry);
extern void ecm_classifier_mark_exit(void);
@@ -151,6 +156,13 @@
}
#endif
+#ifdef ECM_CLASSIFIER_OVS_ENABLE
+ ret = ecm_classifier_ovs_init(ecm_dentry);
+ if (0 != ret) {
+ goto err_cls_ovs;
+ }
+#endif
+
ret = ecm_interface_init();
if (0 != ret) {
goto err_iface;
@@ -207,6 +219,10 @@
#endif
ecm_interface_exit();
err_iface:
+#ifdef ECM_CLASSIFIER_OVS_ENABLE
+ ecm_classifier_ovs_exit();
+err_cls_ovs:
+#endif
#ifdef ECM_CLASSIFIER_MARK_ENABLE
ecm_classifier_mark_exit();
err_cls_mark:
@@ -300,6 +316,10 @@
DEBUG_INFO("exit mark classifier\n");
ecm_classifier_mark_exit();
#endif
+#ifdef ECM_CLASSIFIER_OVS_ENABLE
+ DEBUG_INFO("exit ovs classifier\n");
+ ecm_classifier_ovs_exit();
+#endif
DEBUG_INFO("exit default classifier\n");
ecm_classifier_default_exit();
DEBUG_INFO("exit db\n");
diff --git a/frontends/nss/ecm_nss_ipv4.c b/frontends/nss/ecm_nss_ipv4.c
index eda2a43..ae1a62e 100644
--- a/frontends/nss/ecm_nss_ipv4.c
+++ b/frontends/nss/ecm_nss_ipv4.c
@@ -2125,7 +2125,13 @@
* update the classifiers' stats.
*/
class_sync.flow_tx_packet_count = sync->flow_tx_packet_count;
+ class_sync.flow_tx_byte_count = sync->flow_tx_byte_count;
+ class_sync.flow_rx_packet_count = sync->flow_rx_packet_count;
+ class_sync.flow_rx_byte_count = sync->flow_rx_byte_count;
class_sync.return_tx_packet_count = sync->return_tx_packet_count;
+ class_sync.return_tx_byte_count = sync->return_tx_byte_count;
+ class_sync.return_rx_packet_count = sync->return_rx_packet_count;
+ class_sync.return_rx_byte_count = sync->return_rx_byte_count;
class_sync.reason = sync->reason;
/*
diff --git a/frontends/nss/ecm_nss_ipv6.c b/frontends/nss/ecm_nss_ipv6.c
index ee90845..1686f79 100644
--- a/frontends/nss/ecm_nss_ipv6.c
+++ b/frontends/nss/ecm_nss_ipv6.c
@@ -1835,7 +1835,13 @@
* update the classifiers' stats.
*/
class_sync.flow_tx_packet_count = sync->flow_tx_packet_count;
+ class_sync.flow_tx_byte_count = sync->flow_tx_byte_count;
+ class_sync.flow_rx_packet_count = sync->flow_rx_packet_count;
+ class_sync.flow_rx_byte_count = sync->flow_rx_byte_count;
class_sync.return_tx_packet_count = sync->return_tx_packet_count;
+ class_sync.return_tx_byte_count = sync->return_tx_byte_count;
+ class_sync.return_rx_packet_count = sync->return_rx_packet_count;
+ class_sync.return_rx_byte_count = sync->return_rx_byte_count;
class_sync.reason = sync->reason;
/*