blob: f8e8c80126764efbefbcfe63f8e6914e55add795 [file] [log] [blame]
/*
***************************************************************************
* Copyright (c) 2020-2021, 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.
***************************************************************************
*/
#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/string.h>
#include <linux/netfilter_bridge.h>
#include <net/ip.h>
#include <linux/inet.h>
#include <sp_api.h>
/*
* Debug output levels
* 0 = OFF
* 1 = ASSERTS / ERRORS
* 2 = 1 + WARN
* 3 = 2 + INFO
* 4 = 3 + TRACE
*/
#define DEBUG_LEVEL ECM_CLASSIFIER_EMESH_DEBUG_LEVEL
#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_db.h"
#include "ecm_interface.h"
#include "ecm_classifier_emesh_public.h"
#include "ecm_front_end_ipv4.h"
#include "ecm_front_end_ipv6.h"
#include "ecm_front_end_common.h"
/*
* Magic numbers
*/
#define ECM_CLASSIFIER_EMESH_INSTANCE_MAGIC 0xFECA
/*
* Latency parameter operation
*/
#define ECM_CLASSIFIER_EMESH_ADD_LATENCY_PARAMS 0x1
#define ECM_CLASSIFIER_EMESH_SUB_LATENCY_PARAMS 0x2
/*
* Flag to enable SPM rule lookup
*/
#define ECM_CLASSIFIER_EMESH_ENABLE_SPM_RULE_LOOKUP 0x1
#define ECM_CLASSIFIER_EMESH_ENABLE_LATENCY_UPDATE 0x2
/*
* struct ecm_classifier_emesh_instance
* State to allow tracking of dynamic qos for a connection
*/
struct ecm_classifier_emesh_instance {
struct ecm_classifier_instance base; /* Base type */
struct ecm_classifier_emesh_instance *next; /* Next classifier state instance (for accouting and reporting purposes) */
struct ecm_classifier_emesh_instance *prev; /* Next classifier state instance (for accouting and reporting purposes) */
uint32_t ci_serial; /* RO: Serial of the connection */
uint32_t pcp[ECM_CONN_DIR_MAX]; /* PCP values for the connections */
struct ecm_classifier_process_response process_response;/* Last process response computed */
int refs; /* Integer to trap we never go negative */
uint8_t packet_seen[ECM_CONN_DIR_MAX]; /* Per direction packet seen flag */
uint32_t service_interval_dl; /* wlan downlink latency parameter: Service interval associated with this connection */
uint32_t burst_size_dl; /* wlan downlink latency parameter: Burst Size associated with this connection */
uint32_t service_interval_ul; /* wlan uplink latency parameter: Service interval associated with this connection */
uint32_t burst_size_ul; /* wlan uplink latency parameter: Burst Size associated with this connection */
#if (DEBUG_LEVEL > 0)
uint16_t magic;
#endif
};
/*
* Operational control
*/
static uint32_t ecm_classifier_emesh_enabled; /* Operational behaviour */
static uint32_t ecm_classifier_emesh_latency_config_enabled; /* Mesh Latency profile enable flag */
/*
* Management thread control
*/
static bool ecm_classifier_emesh_terminate_pending = false; /* True when the user wants us to terminate */
/*
* Debugfs dentry object.
*/
static struct dentry *ecm_classifier_emesh_dentry;
/*
* Locking of the classifier structures
*/
static DEFINE_SPINLOCK(ecm_classifier_emesh_lock); /* Protect SMP access. */
/*
* List of our classifier instances
*/
static struct ecm_classifier_emesh_instance *ecm_classifier_emesh_instances = NULL;
/* list of all active instances */
static int ecm_classifier_emesh_count = 0; /* Tracks number of instances allocated */
/*
* Callback structure to support Mesh latency param config in WLAN driver
*/
static struct ecm_classifier_emesh_callbacks ecm_emesh;
/*
* ecm_classifier_emesh_ref()
* Ref
*/
static void ecm_classifier_emesh_ref(struct ecm_classifier_instance *ci)
{
struct ecm_classifier_emesh_instance *cemi;
cemi = (struct ecm_classifier_emesh_instance *)ci;
DEBUG_CHECK_MAGIC(cemi, ECM_CLASSIFIER_EMESH_INSTANCE_MAGIC, "%px: magic failed\n", cemi);
spin_lock_bh(&ecm_classifier_emesh_lock);
cemi->refs++;
DEBUG_TRACE("%px: cemi ref %d\n", cemi, cemi->refs);
DEBUG_ASSERT(cemi->refs > 0, "%px: ref wrap\n", cemi);
spin_unlock_bh(&ecm_classifier_emesh_lock);
}
/*
* ecm_classifier_emesh_deref()
* Deref
*/
static int ecm_classifier_emesh_deref(struct ecm_classifier_instance *ci)
{
struct ecm_classifier_emesh_instance *cemi;
cemi = (struct ecm_classifier_emesh_instance *)ci;
DEBUG_CHECK_MAGIC(cemi, ECM_CLASSIFIER_EMESH_INSTANCE_MAGIC, "%px: magic failed\n", cemi);
spin_lock_bh(&ecm_classifier_emesh_lock);
cemi->refs--;
DEBUG_ASSERT(cemi->refs >= 0, "%px: refs wrapped\n", cemi);
DEBUG_TRACE("%px: EMESH classifier deref %d\n", cemi, cemi->refs);
if (cemi->refs) {
int refs = cemi->refs;
spin_unlock_bh(&ecm_classifier_emesh_lock);
return refs;
}
/*
* Object to be destroyed
*/
ecm_classifier_emesh_count--;
DEBUG_ASSERT(ecm_classifier_emesh_count >= 0, "%px: ecm_classifier_emesh_count wrap\n", cemi);
/*
* UnLink the instance from our list
*/
if (cemi->next) {
cemi->next->prev = cemi->prev;
}
if (cemi->prev) {
cemi->prev->next = cemi->next;
} else {
DEBUG_ASSERT(ecm_classifier_emesh_instances == cemi, "%px: list bad %px\n", cemi, ecm_classifier_emesh_instances);
ecm_classifier_emesh_instances = cemi->next;
}
spin_unlock_bh(&ecm_classifier_emesh_lock);
/*
* Final
*/
DEBUG_INFO("%px: Final EMESH classifier instance\n", cemi);
kfree(cemi);
return 0;
}
/*
* ecm_classifier_emesh_is_bidi_packet_seen()
* Return true if both direction packets are seen.
*/
static inline bool ecm_classifier_emesh_is_bidi_packet_seen(struct ecm_classifier_emesh_instance *cemi)
{
return ((cemi->packet_seen[ECM_CONN_DIR_FLOW] == true) && (cemi->packet_seen[ECM_CONN_DIR_RETURN] == true));
}
/*
* ecm_classifier_emesh_fill_pcp()
* Save the PCP value in the classifier instance.
*/
static void ecm_classifier_emesh_fill_pcp(struct ecm_classifier_emesh_instance *cemi,
ecm_tracker_sender_type_t sender, enum ip_conntrack_info ctinfo,
struct sk_buff *skb)
{
if (sender == ECM_TRACKER_SENDER_TYPE_SRC) {
cemi->pcp[ECM_CONN_DIR_FLOW] = skb->priority;
cemi->packet_seen[ECM_CONN_DIR_FLOW] = true;
} else {
cemi->pcp[ECM_CONN_DIR_RETURN] = skb->priority;
cemi->packet_seen[ECM_CONN_DIR_RETURN] = true;
}
}
/*
* ecm_classifier_emesh_process()
* Process new data for connection
*/
static void ecm_classifier_emesh_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_emesh_instance *cemi;
ecm_classifier_relevence_t relevance;
struct ecm_db_connection_instance *ci = NULL;
struct ecm_front_end_connection_instance *feci;
ecm_front_end_acceleration_mode_t accel_mode;
uint32_t became_relevant = 0;
struct nf_conn *ct;
enum ip_conntrack_info ctinfo;
int protocol;
uint64_t slow_pkts;
cemi = (struct ecm_classifier_emesh_instance *)aci;
DEBUG_CHECK_MAGIC(cemi, ECM_CLASSIFIER_EMESH_INSTANCE_MAGIC, "%px: magic failed\n", cemi);
spin_lock_bh(&ecm_classifier_emesh_lock);
relevance = cemi->process_response.relevance;
/*
* Are we relevant?
* If the classifier is set as ir-relevant to the connection,
* the process response of the classifier instance was set from
* the earlier packets.
*/
if (relevance == ECM_CLASSIFIER_RELEVANCE_NO) {
/*
* Lock still held
*/
goto emesh_classifier_out;
}
/*
* Yes or maybe relevant.
*
* Need to decide our relevance to this connection.
* We are only relevent to a connection iff:
* 1. We are enabled.
* 2. Connection can be accelerated.
* Any other condition and we are not and will stop analysing this connection.
*/
if (!ecm_classifier_emesh_enabled) {
/*
* Lock still held
*/
cemi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_NO;
goto emesh_classifier_out;
}
spin_unlock_bh(&ecm_classifier_emesh_lock);
/*
* Can we accelerate?
*/
ci = ecm_db_connection_serial_find_and_ref(cemi->ci_serial);
if (!ci) {
DEBUG_TRACE("%px: No ci found for %u\n", cemi, cemi->ci_serial);
spin_lock_bh(&ecm_classifier_emesh_lock);
cemi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_NO;
goto emesh_classifier_out;
}
/*
* Check if SPM rule lookup flag is enabled
*/
if (ecm_classifier_emesh_latency_config_enabled & ECM_CLASSIFIER_EMESH_ENABLE_SPM_RULE_LOOKUP) {
uint8_t dmac[ETH_ALEN];
uint8_t smac[ETH_ALEN];
if (sender == ECM_TRACKER_SENDER_TYPE_SRC) {
DEBUG_TRACE("%px: sender is SRC\n", aci);
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);
} else {
DEBUG_TRACE("%px: sender is DEST\n", aci);
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_TO, smac);
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_FROM, dmac);
}
/*
* Invoke SPM rule lookup API for skb priority update
* For bridging traffic, it will be matched with the rule table on SPM prerouting hook
*/
if (skb->skb_iif != skb->dev->ifindex) {
sp_mapdb_apply(skb, smac, dmac);
}
}
feci = ecm_db_connection_front_end_get_and_ref(ci);
accel_mode = feci->accel_state_get(feci);
slow_pkts = ecm_front_end_get_slow_packet_count(feci);
feci->deref(feci);
protocol = ecm_db_connection_protocol_get(ci);
ecm_db_connection_deref(ci);
if (ECM_FRONT_END_ACCELERATION_NOT_POSSIBLE(accel_mode)) {
spin_lock_bh(&ecm_classifier_emesh_lock);
cemi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_NO;
goto emesh_classifier_out;
}
/*
* Is there a valid conntrack?
*/
ct = nf_ct_get(skb, &ctinfo);
if (!ct) {
spin_lock_bh(&ecm_classifier_emesh_lock);
cemi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_NO;
goto emesh_classifier_out;
}
/*
* We are relevant to the connection.
* Set the process response to its default value, that is, to
* allow the acceleration.
*/
became_relevant = ecm_db_time_get();
spin_lock_bh(&ecm_classifier_emesh_lock);
cemi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_YES;
cemi->process_response.became_relevant = became_relevant;
cemi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_ACCEL_MODE;
cemi->process_response.accel_mode = ECM_CLASSIFIER_ACCELERATION_MODE_ACCEL;
spin_unlock_bh(&ecm_classifier_emesh_lock);
if (protocol == IPPROTO_TCP) {
/*
* Stop the processing if both side packets are already seen.
* Above the process response is already set to allow the acceleration.
*/
if (ecm_classifier_emesh_is_bidi_packet_seen(cemi)) {
spin_lock_bh(&ecm_classifier_emesh_lock);
goto emesh_classifier_out;
}
/*
* Store the PCP value in the classifier instance and deny the
* acceleration if both side PCP value is not yet available.
*/
ecm_classifier_emesh_fill_pcp(cemi, sender, ctinfo, skb);
if (!ecm_classifier_emesh_is_bidi_packet_seen(cemi)) {
DEBUG_TRACE("%px: Both side PCP value is not yet picked\n", cemi);
spin_lock_bh(&ecm_classifier_emesh_lock);
cemi->process_response.accel_mode = ECM_CLASSIFIER_ACCELERATION_MODE_NO;
goto emesh_classifier_out;
}
} else {
/*
* If the acceleration delay option is enabled, we will wait
* until seeing both side traffic.
*
* There are 2 options:
* Option 1: Wait forever until to see the reply direction traffic
* Option 2: Wait for seeing N number of packets. If we still don't see reply,
* set the uni-directional values.
*/
if (ecm_classifier_accel_delay_pkts) {
/*
* Stop the processing if both side packets are already seen.
* Above the process response is already set to allow the
* acceleration.
*/
if (ecm_classifier_emesh_is_bidi_packet_seen(cemi)) {
spin_lock_bh(&ecm_classifier_emesh_lock);
goto emesh_classifier_out;
}
/*
* Store the PCP value in the classifier instance and allow the
* acceleration if both side PCP value is not yet available.
*/
ecm_classifier_emesh_fill_pcp(cemi, sender, ctinfo, skb);
if (ecm_classifier_emesh_is_bidi_packet_seen(cemi)) {
DEBUG_TRACE("%px: Both side PCP value is picked\n", cemi);
goto done;
}
/*
* Deny the acceleration if any of the below options holds true.
* For option 1, we wait forever
* For option 2, we wait until seeing ecm_classifier_accel_delay_pkts.
*/
if ((ecm_classifier_accel_delay_pkts == 1) || (slow_pkts < ecm_classifier_accel_delay_pkts)) {
DEBUG_TRACE("%px: accel_delay_pkts: %d slow_pkts: %llu accel is not allowed yet\n",
cemi, ecm_classifier_accel_delay_pkts, slow_pkts);
spin_lock_bh(&ecm_classifier_emesh_lock);
cemi->process_response.accel_mode = ECM_CLASSIFIER_ACCELERATION_MODE_NO;
goto emesh_classifier_out;
}
}
/*
* If we didn't see both direction traffic during the acceleration
* delay time, we can allow the acceleration by setting the uni-directional
* values to both flow and return PCP.
*/
cemi->pcp[ECM_CONN_DIR_FLOW] = skb->priority;
cemi->pcp[ECM_CONN_DIR_RETURN] = skb->priority;
}
done:
DEBUG_TRACE("Protocol: %d, Flow Priority: %d, Return priority: %d, sender: %d\n",
protocol, cemi->pcp[ECM_CONN_DIR_FLOW],
cemi->pcp[ECM_CONN_DIR_RETURN], sender);
spin_lock_bh(&ecm_classifier_emesh_lock);
cemi->process_response.process_actions |= ECM_CLASSIFIER_PROCESS_ACTION_QOS_TAG;
if (((sender == ECM_TRACKER_SENDER_TYPE_SRC) && (IP_CT_DIR_ORIGINAL == CTINFO2DIR(ctinfo))) ||
((sender == ECM_TRACKER_SENDER_TYPE_DEST) && (IP_CT_DIR_REPLY == CTINFO2DIR(ctinfo)))) {
cemi->process_response.flow_qos_tag = cemi->pcp[ECM_CONN_DIR_FLOW];
cemi->process_response.return_qos_tag = cemi->pcp[ECM_CONN_DIR_RETURN];
} else {
cemi->process_response.flow_qos_tag = cemi->pcp[ECM_CONN_DIR_RETURN];
cemi->process_response.return_qos_tag = cemi->pcp[ECM_CONN_DIR_FLOW];
}
emesh_classifier_out:
/*
* Return our process response
*/
*process_response = cemi->process_response;
spin_unlock_bh(&ecm_classifier_emesh_lock);
}
/*
* ecm_classifier_emesh_update_latency_param_on_conn_decel()
* Update mesh latency parameters to wlan host driver when a connection gets decelerated in ECM
*/
void ecm_classifier_emesh_update_latency_param_on_conn_decel(struct ecm_classifier_instance *aci, struct ecm_classifier_rule_sync *sync)
{
struct ecm_classifier_emesh_instance *cemi;
struct ecm_db_connection_instance *ci;
uint8_t peer_mac[ETH_ALEN];
cemi = (struct ecm_classifier_emesh_instance *)aci;
DEBUG_CHECK_MAGIC(cemi, ECM_CLASSIFIER_EMESH_INSTANCE_MAGIC, "%px: magic failed", cemi);
/*
* Return if E-Mesh functionality is not enabled.
*/
if (!ecm_classifier_emesh_enabled) {
return;
}
if (!(ecm_classifier_emesh_latency_config_enabled
& ECM_CLASSIFIER_EMESH_ENABLE_LATENCY_UPDATE)) {
return;
}
if (!ecm_emesh.update_peer_mesh_latency_params) {
return;
}
ci = ecm_db_connection_serial_find_and_ref(cemi->ci_serial);
if (!ci) {
DEBUG_WARN("%px: No ci found for %u\n", cemi, cemi->ci_serial);
return;
}
/*
* Get mac address for destination node
*/
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_TO, peer_mac);
ecm_emesh.update_peer_mesh_latency_params(peer_mac,
cemi->service_interval_dl, cemi->burst_size_dl, cemi->service_interval_ul, cemi->burst_size_ul,
cemi->pcp[ECM_CONN_DIR_FLOW], ECM_CLASSIFIER_EMESH_SUB_LATENCY_PARAMS);
/*
* Get mac address for source node
*/
ecm_db_connection_node_address_get(ci, ECM_DB_OBJ_DIR_FROM, peer_mac);
ecm_emesh.update_peer_mesh_latency_params(peer_mac,
cemi->service_interval_dl, cemi->burst_size_dl, cemi->service_interval_ul, cemi->burst_size_ul,
cemi->pcp[ECM_CONN_DIR_FLOW], ECM_CLASSIFIER_EMESH_SUB_LATENCY_PARAMS);
ecm_db_connection_deref(ci);
}
/*
* ecm_classifier_emesh_sync_to_v4()
* Front end is pushing accel engine state to us
*/
static void ecm_classifier_emesh_sync_to_v4(struct ecm_classifier_instance *aci, struct ecm_classifier_rule_sync *sync)
{
struct ecm_classifier_emesh_instance *cemi;
cemi = (struct ecm_classifier_emesh_instance *)aci;
DEBUG_CHECK_MAGIC(cemi, ECM_CLASSIFIER_EMESH_INSTANCE_MAGIC, "%px: magic failed", cemi);
switch(sync->reason) {
case ECM_FRONT_END_IPV4_RULE_SYNC_REASON_FLUSH:
case ECM_FRONT_END_IPV4_RULE_SYNC_REASON_EVICT:
case ECM_FRONT_END_IPV4_RULE_SYNC_REASON_DESTROY:
ecm_classifier_emesh_update_latency_param_on_conn_decel(aci, sync);
break;
default:
break;
}
}
/*
* ecm_classifier_emesh_update_wlan_latency_params_on_conn_accel()
* Update wifi latency parameters associated with SP rule to wlan host driver
* when a connection getting accelerated in ECM
*/
static void ecm_classifier_emesh_update_wlan_latency_params_on_conn_accel(struct ecm_classifier_instance *aci,
struct ecm_classifier_rule_create *ecrc)
{
struct ecm_classifier_emesh_instance *cemi;
struct ecm_db_connection_instance *ci;
uint8_t service_interval_dl;
uint32_t burst_size_dl;
uint8_t service_interval_ul;
uint32_t burst_size_ul;
struct sk_buff *skb;
uint8_t dmac[ETH_ALEN];
uint8_t smac[ETH_ALEN];
/*
* Return if E-Mesh functionality is not enabled.
*/
if (!ecm_classifier_emesh_enabled) {
return;
}
if (!(ecm_classifier_emesh_latency_config_enabled
& ECM_CLASSIFIER_EMESH_ENABLE_LATENCY_UPDATE)) {
/*
* Flow based latency parameter updation to WLAN host driver not enabled
*/
return;
}
/*
* When mesh low latency feature flags is enabled, ECM gets
* latency config parameters associated with a SPM rule and send
* to WLAN host driver invoking callback
*/
if (!ecm_emesh.update_peer_mesh_latency_params) {
return;
}
skb = ecrc->skb;
cemi = (struct ecm_classifier_emesh_instance *)aci;
DEBUG_CHECK_MAGIC(cemi, ECM_CLASSIFIER_EMESH_INSTANCE_MAGIC, "%px: magic failed", cemi);
ci = ecm_db_connection_serial_find_and_ref(cemi->ci_serial);
if (!ci) {
DEBUG_WARN("%px: No ci found for %u\n", cemi, cemi->ci_serial);
return;
}
/*
* Invoke SPM rule lookup API to update skb priority
* When latency config is enabled, fetch latency parameter
* associated with a SPM rule.Since we do not know direction of
* connection, we get src and destination mac address of both
* connection and let wlan driver find corresponding wlan peer
* connected
*/
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);
sp_mapdb_get_wlan_latency_params(skb, &service_interval_dl, &burst_size_dl,
&service_interval_ul, &burst_size_ul, smac, dmac);
spin_lock_bh(&ecm_classifier_emesh_lock);
/*
* Update latency parameters to accelerated connection
*/
cemi->service_interval_dl = service_interval_dl;
cemi->burst_size_dl = burst_size_dl;
cemi->service_interval_ul = service_interval_ul;
cemi->burst_size_ul = burst_size_ul;
spin_unlock_bh(&ecm_classifier_emesh_lock);
/*
* If one of the latency parameters are zero, there could be
* 2 possibilities - 1. no rule match 2. sp rule does not have
* latency parameter configured.
*/
if ((service_interval_ul && burst_size_ul) || (service_interval_dl && burst_size_dl)) {
/*
* Send destination mac address of this connection
*/
ecm_emesh.update_peer_mesh_latency_params(dmac,
service_interval_dl, burst_size_dl, service_interval_ul, burst_size_ul,
skb->priority, ECM_CLASSIFIER_EMESH_ADD_LATENCY_PARAMS);
}
/*
* Get latency parameter for other direction
*/
sp_mapdb_get_wlan_latency_params(skb, &service_interval_dl, &burst_size_dl,
&service_interval_ul, &burst_size_ul, dmac, smac);
if ((service_interval_ul && burst_size_ul) || (service_interval_dl && burst_size_dl)) {
/*
* Send source mac address of this connection
*/
ecm_emesh.update_peer_mesh_latency_params(smac,
service_interval_dl, burst_size_dl, service_interval_ul, burst_size_ul,
skb->priority, ECM_CLASSIFIER_EMESH_ADD_LATENCY_PARAMS);
}
ecm_db_connection_deref(ci);
}
/*
* ecm_classifier_emesh_sync_from_v4()
* Front end is retrieving accel engine state from us
*/
static void ecm_classifier_emesh_sync_from_v4(struct ecm_classifier_instance *aci, struct ecm_classifier_rule_create *ecrc)
{
ecm_classifier_emesh_update_wlan_latency_params_on_conn_accel(aci, ecrc);
}
/*
* ecm_classifier_emesh_sync_to_v6()
* Front end is pushing accel engine state to us
*/
static void ecm_classifier_emesh_sync_to_v6(struct ecm_classifier_instance *aci, struct ecm_classifier_rule_sync *sync)
{
struct ecm_classifier_emesh_instance *cemi;
cemi = (struct ecm_classifier_emesh_instance *)aci;
DEBUG_CHECK_MAGIC(cemi, ECM_CLASSIFIER_EMESH_INSTANCE_MAGIC, "%px: magic failed", cemi);
switch(sync->reason) {
case ECM_FRONT_END_IPV6_RULE_SYNC_REASON_FLUSH:
case ECM_FRONT_END_IPV6_RULE_SYNC_REASON_EVICT:
case ECM_FRONT_END_IPV6_RULE_SYNC_REASON_DESTROY:
ecm_classifier_emesh_update_latency_param_on_conn_decel(aci, sync);
break;
default:
break;
}
}
/*
* ecm_classifier_emesh_sync_from_v6()
* Front end is retrieving accel engine state from us
*/
static void ecm_classifier_emesh_sync_from_v6(struct ecm_classifier_instance *aci, struct ecm_classifier_rule_create *ecrc)
{
ecm_classifier_emesh_update_wlan_latency_params_on_conn_accel(aci, ecrc);
}
/*
* ecm_classifier_emesh_type_get()
* Get type of classifier this is
*/
static ecm_classifier_type_t ecm_classifier_emesh_type_get(struct ecm_classifier_instance *ci)
{
struct ecm_classifier_emesh_instance *cemi;
cemi = (struct ecm_classifier_emesh_instance *)ci;
DEBUG_CHECK_MAGIC(cemi, ECM_CLASSIFIER_EMESH_INSTANCE_MAGIC, "%px: magic failed\n", cemi);
return ECM_CLASSIFIER_TYPE_EMESH;
}
/*
* ecm_classifier_emesh_last_process_response_get()
* Get result code returned by the last process call
*/
static void ecm_classifier_emesh_last_process_response_get(struct ecm_classifier_instance *ci,
struct ecm_classifier_process_response *process_response)
{
struct ecm_classifier_emesh_instance *cemi;
cemi = (struct ecm_classifier_emesh_instance *)ci;
DEBUG_CHECK_MAGIC(cemi, ECM_CLASSIFIER_EMESH_INSTANCE_MAGIC, "%px: magic failed\n", cemi);
spin_lock_bh(&ecm_classifier_emesh_lock);
*process_response = cemi->process_response;
spin_unlock_bh(&ecm_classifier_emesh_lock);
}
/*
* ecm_classifier_emesh_reclassify_allowed()
* Indicate if reclassify is allowed
*/
static bool ecm_classifier_emesh_reclassify_allowed(struct ecm_classifier_instance *ci)
{
struct ecm_classifier_emesh_instance *cemi;
cemi = (struct ecm_classifier_emesh_instance *)ci;
DEBUG_CHECK_MAGIC(cemi, ECM_CLASSIFIER_EMESH_INSTANCE_MAGIC, "%px: magic failed\n", cemi);
return true;
}
/*
* ecm_classifier_emesh_reclassify()
* Reclassify
*/
static void ecm_classifier_emesh_reclassify(struct ecm_classifier_instance *ci)
{
struct ecm_classifier_emesh_instance *cemi;
cemi = (struct ecm_classifier_emesh_instance *)ci;
DEBUG_CHECK_MAGIC(cemi, ECM_CLASSIFIER_EMESH_INSTANCE_MAGIC, "%px: magic failed\n", cemi);
/*
* Revert back to MAYBE relevant - we will evaluate when we get the next process() call.
*/
spin_lock_bh(&ecm_classifier_emesh_lock);
cemi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_MAYBE;
spin_unlock_bh(&ecm_classifier_emesh_lock);
}
#ifdef ECM_STATE_OUTPUT_ENABLE
/*
* ecm_classifier_emesh_state_get()
* Return state
*/
static int ecm_classifier_emesh_state_get(struct ecm_classifier_instance *ci, struct ecm_state_file_instance *sfi)
{
int result;
struct ecm_classifier_emesh_instance *cemi;
struct ecm_classifier_process_response process_response;
cemi = (struct ecm_classifier_emesh_instance *)ci;
DEBUG_CHECK_MAGIC(cemi, ECM_CLASSIFIER_EMESH_INSTANCE_MAGIC, "%px: magic failed", cemi);
if ((result = ecm_state_prefix_add(sfi, "emesh"))) {
return result;
}
spin_lock_bh(&ecm_classifier_emesh_lock);
process_response = cemi->process_response;
spin_unlock_bh(&ecm_classifier_emesh_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_emesh_instance_alloc()
* Allocate an instance of the EMESH classifier
*/
struct ecm_classifier_emesh_instance *ecm_classifier_emesh_instance_alloc(struct ecm_db_connection_instance *ci)
{
struct ecm_classifier_emesh_instance *cemi;
/*
* Allocate the instance
*/
cemi = (struct ecm_classifier_emesh_instance *)kzalloc(sizeof(struct ecm_classifier_emesh_instance), GFP_ATOMIC | __GFP_NOWARN);
if (!cemi) {
DEBUG_WARN("Failed to allocate EMESH instance\n");
return NULL;
}
DEBUG_SET_MAGIC(cemi, ECM_CLASSIFIER_EMESH_INSTANCE_MAGIC);
cemi->refs = 1;
cemi->base.process = ecm_classifier_emesh_process;
cemi->base.sync_from_v4 = ecm_classifier_emesh_sync_from_v4;
cemi->base.sync_to_v4 = ecm_classifier_emesh_sync_to_v4;
cemi->base.sync_from_v6 = ecm_classifier_emesh_sync_from_v6;
cemi->base.sync_to_v6 = ecm_classifier_emesh_sync_to_v6;
cemi->base.type_get = ecm_classifier_emesh_type_get;
cemi->base.last_process_response_get = ecm_classifier_emesh_last_process_response_get;
cemi->base.reclassify_allowed = ecm_classifier_emesh_reclassify_allowed;
cemi->base.reclassify = ecm_classifier_emesh_reclassify;
#ifdef ECM_STATE_OUTPUT_ENABLE
cemi->base.state_get = ecm_classifier_emesh_state_get;
#endif
cemi->base.ref = ecm_classifier_emesh_ref;
cemi->base.deref = ecm_classifier_emesh_deref;
cemi->ci_serial = ecm_db_connection_serial_get(ci);
cemi->process_response.process_actions = 0;
cemi->process_response.relevance = ECM_CLASSIFIER_RELEVANCE_MAYBE;
spin_lock_bh(&ecm_classifier_emesh_lock);
/*
* Final check if we are pending termination
*/
if (ecm_classifier_emesh_terminate_pending) {
spin_unlock_bh(&ecm_classifier_emesh_lock);
DEBUG_INFO("%px: Terminating\n", ci);
kfree(cemi);
return NULL;
}
/*
* Link the new instance into our list at the head
*/
cemi->next = ecm_classifier_emesh_instances;
if (ecm_classifier_emesh_instances) {
ecm_classifier_emesh_instances->prev = cemi;
}
ecm_classifier_emesh_instances = cemi;
/*
* Increment stats
*/
ecm_classifier_emesh_count++;
DEBUG_ASSERT(ecm_classifier_emesh_count > 0, "%px: ecm_classifier_emesh_count wrap\n", cemi);
spin_unlock_bh(&ecm_classifier_emesh_lock);
DEBUG_INFO("EMESH instance alloc: %px\n", cemi);
return cemi;
}
EXPORT_SYMBOL(ecm_classifier_emesh_instance_alloc);
/*
* ecm_classifier_emesh_rule_update_cb()
* Callback for service prioritization notification update.
*/
static void ecm_classifier_emesh_rule_update_cb(uint8_t add_rm_md,
uint32_t valid_flag, struct sp_rule *r)
{
ip_addr_t ip_addr;
struct in6_addr ipv6addr = IN6ADDR_ANY_INIT;
/*
* Return if E-Mesh functionality is not enabled.
*/
if (!ecm_classifier_emesh_enabled) {
return;
}
DEBUG_TRACE("SP rule update notification received\n");
/*
* Order of priority of rule fields to match and flush connections:
* Port ---> IP address ---> Mac Address ---> Protocol
* Flush connections for both directions as ECM creates reverse
* direction rule as well
*/
if (valid_flag & SP_RULE_FLAG_MATCH_SRC_PORT) {
ecm_db_connection_defunct_by_port(r->inner.src_port, ECM_DB_OBJ_DIR_FROM);
ecm_db_connection_defunct_by_port(r->inner.src_port, ECM_DB_OBJ_DIR_TO);
return;
}
if (valid_flag & SP_RULE_FLAG_MATCH_DST_PORT) {
ecm_db_connection_defunct_by_port(r->inner.dst_port, ECM_DB_OBJ_DIR_FROM);
ecm_db_connection_defunct_by_port(r->inner.dst_port, ECM_DB_OBJ_DIR_TO);
return;
}
if (valid_flag & SP_RULE_FLAG_MATCH_SRC_IPV4) {
ECM_NIN4_ADDR_TO_IP_ADDR(ip_addr, r->inner.src_ipv4_addr);
ecm_db_host_connections_defunct_by_dir(ip_addr, ECM_DB_OBJ_DIR_FROM);
ecm_db_host_connections_defunct_by_dir(ip_addr, ECM_DB_OBJ_DIR_TO);
return;
}
if (valid_flag & SP_RULE_FLAG_MATCH_DST_IPV4) {
ECM_NIN4_ADDR_TO_IP_ADDR(ip_addr, r->inner.dst_ipv4_addr);
ecm_db_host_connections_defunct_by_dir(ip_addr, ECM_DB_OBJ_DIR_FROM);
ecm_db_host_connections_defunct_by_dir(ip_addr, ECM_DB_OBJ_DIR_TO);
return;
}
if (valid_flag & SP_RULE_FLAG_MATCH_SRC_IPV6) {
memcpy(ipv6addr.s6_addr32, r->inner.src_ipv6_addr, 4);
ECM_NIN6_ADDR_TO_IP_ADDR(ip_addr, ipv6addr);
ecm_db_host_connections_defunct_by_dir(ip_addr, ECM_DB_OBJ_DIR_FROM);
ecm_db_host_connections_defunct_by_dir(ip_addr, ECM_DB_OBJ_DIR_TO);
return;
}
if (valid_flag & SP_RULE_FLAG_MATCH_DST_IPV6) {
memcpy(ipv6addr.s6_addr32, r->inner.dst_ipv6_addr, 4);
ECM_NIN6_ADDR_TO_IP_ADDR(ip_addr, ipv6addr);
ecm_db_host_connections_defunct_by_dir(ip_addr, ECM_DB_OBJ_DIR_FROM);
ecm_db_host_connections_defunct_by_dir(ip_addr, ECM_DB_OBJ_DIR_TO);
return;
}
if (valid_flag & SP_RULE_FLAG_MATCH_SOURCE_MAC) {
ecm_interface_node_connections_defunct((uint8_t *)r->inner.sa, ECM_DB_IP_VERSION_IGNORE);
return;
}
if (valid_flag & SP_RULE_FLAG_MATCH_DST_MAC) {
ecm_interface_node_connections_defunct((uint8_t *)r->inner.da, ECM_DB_IP_VERSION_IGNORE);
return;
}
if (valid_flag & SP_RULE_FLAG_MATCH_PROTOCOL) {
ecm_db_connection_defunct_by_protocol(r->inner.protocol_number);
return;
}
/*
* Destroy all the connections that are currently assigned to Emesh classifier
* The usage of the incoming parameters in this service prioritization
* callback will be done in future to perform more refined flush of
* connections.
*/
ecm_db_connection_make_defunct_by_assignment_type(ECM_CLASSIFIER_TYPE_EMESH);
}
/*
* ecm_classifier_emesh_latency_config_callback_register()
*/
int ecm_classifier_emesh_latency_config_callback_register(struct ecm_classifier_emesh_callbacks *emesh_cb)
{
spin_lock_bh(&ecm_classifier_emesh_lock);
if (ecm_emesh.update_peer_mesh_latency_params) {
spin_unlock_bh(&ecm_classifier_emesh_lock);
DEBUG_ERROR("EMESH latency config callbacks are registered\n");
return -1;
}
ecm_emesh.update_peer_mesh_latency_params = emesh_cb->update_peer_mesh_latency_params;
spin_unlock_bh(&ecm_classifier_emesh_lock);
return 0;
}
EXPORT_SYMBOL(ecm_classifier_emesh_latency_config_callback_register);
/*
* ecm_classifier_emesh_latency_config_callback_unregister()
*/
void ecm_classifier_emesh_latency_config_callback_unregister(void)
{
spin_lock_bh(&ecm_classifier_emesh_lock);
ecm_emesh.update_peer_mesh_latency_params = NULL;
spin_unlock_bh(&ecm_classifier_emesh_lock);
}
EXPORT_SYMBOL(ecm_classifier_emesh_latency_config_callback_unregister);
/*
* ecm_classifier_emesh_init()
*/
int ecm_classifier_emesh_init(struct dentry *dentry)
{
int ret;
DEBUG_INFO("EMESH classifier Module init\n");
ecm_classifier_emesh_dentry = debugfs_create_dir("ecm_classifier_emesh", dentry);
if (!ecm_classifier_emesh_dentry) {
DEBUG_ERROR("Failed to create ecm emesh directory in debugfs\n");
return -1;
}
if (!debugfs_create_u32("enabled", S_IRUGO | S_IWUSR, ecm_classifier_emesh_dentry,
(u32 *)&ecm_classifier_emesh_enabled)) {
DEBUG_ERROR("Failed to create ecm emesh classifier enabled file in debugfs\n");
debugfs_remove_recursive(ecm_classifier_emesh_dentry);
return -1;
}
if (!debugfs_create_u32("latency_config_enabled", S_IRUGO | S_IWUSR, ecm_classifier_emesh_dentry,
(u32 *)&ecm_classifier_emesh_latency_config_enabled)) {
DEBUG_ERROR("Failed to create ecm emesh classifier latency config enabled file in debugfs\n");
debugfs_remove_recursive(ecm_classifier_emesh_dentry);
return -1;
}
/*
* Register for service prioritization notification update.
*/
ret = sp_mapdb_rule_update_register_notify(ecm_classifier_emesh_rule_update_cb);
if (ret) {
DEBUG_ERROR("SP update registration failed: %d\n", ret);
debugfs_remove_recursive(ecm_classifier_emesh_dentry);
return -1;
}
return 0;
}
EXPORT_SYMBOL(ecm_classifier_emesh_init);
/*
* ecm_classifier_emesh_exit()
*/
void ecm_classifier_emesh_exit(void)
{
DEBUG_INFO("Emesh classifier Module exit\n");
spin_lock_bh(&ecm_classifier_emesh_lock);
ecm_classifier_emesh_terminate_pending = true;
spin_unlock_bh(&ecm_classifier_emesh_lock);
/*
* Remove the debugfs files recursively.
*/
if (ecm_classifier_emesh_dentry) {
debugfs_remove_recursive(ecm_classifier_emesh_dentry);
}
/*
* De-register service prioritization notification update.
*/
sp_mapdb_rule_update_unregister_notify();
}
EXPORT_SYMBOL(ecm_classifier_emesh_exit);