blob: 73abb28a5aa4f495dca765eed59235dd4fed7253 [file] [log] [blame]
/* Copyright (c) 2018-2020 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* RMNET Data Smart Hash Workqueue solution
*
*/
#include "rmnet_shs.h"
#include "rmnet_shs_wq_genl.h"
#include "rmnet_shs_wq_mem.h"
#include <linux/workqueue.h>
#include <linux/list_sort.h>
#include <net/sock.h>
#include <linux/skbuff.h>
MODULE_LICENSE("GPL v2");
/* Local Macros */
#define RMNET_SHS_RX_BPNSEC_TO_BPSEC(x) ((x)*1000000000)
#define RMNET_SHS_SEC_TO_NSEC(x) ((x)*1000000000)
#define RMNET_SHS_NSEC_TO_SEC(x) ((x)/1000000000)
#define RMNET_SHS_BYTE_TO_BIT(x) ((x)*8)
#define RMNET_SHS_MIN_HSTAT_NODES_REQD 16
#define RMNET_SHS_FILTER_PKT_LIMIT 200
#define RMNET_SHS_FILTER_FLOW_RATE 100
#define PERIODIC_CLEAN 0
/* FORCE_CLEAN should only used during module de-init.*/
#define FORCE_CLEAN 1
/* Local Definitions and Declarations */
unsigned int rmnet_shs_cpu_prio_dur __read_mostly = 3;
module_param(rmnet_shs_cpu_prio_dur, uint, 0644);
MODULE_PARM_DESC(rmnet_shs_cpu_prio_dur, "Priority ignore duration (wq intervals)");
#define PRIO_BACKOFF ((!rmnet_shs_cpu_prio_dur) ? 2 : rmnet_shs_cpu_prio_dur)
unsigned int rmnet_shs_wq_interval_ms __read_mostly = RMNET_SHS_WQ_INTERVAL_MS;
module_param(rmnet_shs_wq_interval_ms, uint, 0644);
MODULE_PARM_DESC(rmnet_shs_wq_interval_ms, "Interval between wq runs (ms)");
unsigned long rmnet_shs_max_flow_inactivity_sec __read_mostly =
RMNET_SHS_MAX_SKB_INACTIVE_TSEC;
module_param(rmnet_shs_max_flow_inactivity_sec, ulong, 0644);
MODULE_PARM_DESC(rmnet_shs_max_flow_inactivity_sec,
"Max flow inactive time before clean up");
unsigned int rmnet_shs_wq_tuning __read_mostly = 80;
module_param(rmnet_shs_wq_tuning, uint, 0644);
MODULE_PARM_DESC(rmnet_shs_wq_tuning, "moving average weightage");
unsigned long long rmnet_shs_cpu_rx_max_pps_thresh[MAX_CPUS]__read_mostly = {
RMNET_SHS_UDP_PPS_LPWR_CPU_UTHRESH,
RMNET_SHS_UDP_PPS_LPWR_CPU_UTHRESH,
RMNET_SHS_UDP_PPS_LPWR_CPU_UTHRESH,
RMNET_SHS_UDP_PPS_LPWR_CPU_UTHRESH,
RMNET_SHS_UDP_PPS_PERF_CPU_UTHRESH,
RMNET_SHS_UDP_PPS_PERF_CPU_UTHRESH,
RMNET_SHS_UDP_PPS_PERF_CPU_UTHRESH,
RMNET_SHS_UDP_PPS_PERF_CPU_UTHRESH
};
module_param_array(rmnet_shs_cpu_rx_max_pps_thresh, ullong, 0, 0644);
MODULE_PARM_DESC(rmnet_shs_cpu_rx_max_pps_thresh, "Max pkts core can handle");
unsigned long long rmnet_shs_cpu_rx_min_pps_thresh[MAX_CPUS]__read_mostly = {
RMNET_SHS_UDP_PPS_LPWR_CPU_LTHRESH,
RMNET_SHS_UDP_PPS_LPWR_CPU_LTHRESH,
RMNET_SHS_UDP_PPS_LPWR_CPU_LTHRESH,
RMNET_SHS_UDP_PPS_LPWR_CPU_LTHRESH,
RMNET_SHS_UDP_PPS_PERF_CPU_LTHRESH,
RMNET_SHS_UDP_PPS_PERF_CPU_LTHRESH,
RMNET_SHS_UDP_PPS_PERF_CPU_LTHRESH,
RMNET_SHS_UDP_PPS_PERF_CPU_LTHRESH
};
module_param_array(rmnet_shs_cpu_rx_min_pps_thresh, ullong, 0, 0644);
MODULE_PARM_DESC(rmnet_shs_cpu_rx_min_pps_thresh, "Min pkts core can handle");
unsigned int rmnet_shs_cpu_rx_flows[MAX_CPUS];
module_param_array(rmnet_shs_cpu_rx_flows, uint, 0, 0444);
MODULE_PARM_DESC(rmnet_shs_cpu_rx_flows, "Num flows processed per core");
unsigned int rmnet_shs_cpu_rx_filter_flows[MAX_CPUS];
module_param_array(rmnet_shs_cpu_rx_filter_flows, uint, 0, 0444);
MODULE_PARM_DESC(rmnet_shs_cpu_rx_filter_flows, "Num filtered flows per core");
unsigned long long rmnet_shs_cpu_rx_bytes[MAX_CPUS];
module_param_array(rmnet_shs_cpu_rx_bytes, ullong, 0, 0444);
MODULE_PARM_DESC(rmnet_shs_cpu_rx_bytes, "SHS stamp bytes per CPU");
unsigned long long rmnet_shs_cpu_rx_pkts[MAX_CPUS];
module_param_array(rmnet_shs_cpu_rx_pkts, ullong, 0, 0444);
MODULE_PARM_DESC(rmnet_shs_cpu_rx_pkts, "SHS stamp total pkts per CPU");
unsigned long long rmnet_shs_cpu_rx_bps[MAX_CPUS];
module_param_array(rmnet_shs_cpu_rx_bps, ullong, 0, 0444);
MODULE_PARM_DESC(rmnet_shs_cpu_rx_bps, "SHS stamp enq rate per CPU");
unsigned long long rmnet_shs_cpu_rx_pps[MAX_CPUS];
module_param_array(rmnet_shs_cpu_rx_pps, ullong, 0, 0444);
MODULE_PARM_DESC(rmnet_shs_cpu_rx_pps, "SHS stamp pkt enq rate per CPU");
unsigned long long rmnet_shs_cpu_qhead_diff[MAX_CPUS];
module_param_array(rmnet_shs_cpu_qhead_diff, ullong, 0, 0444);
MODULE_PARM_DESC(rmnet_shs_cpu_qhead_diff, "SHS nw stack queue processed diff");
unsigned long long rmnet_shs_cpu_qhead_total[MAX_CPUS];
module_param_array(rmnet_shs_cpu_qhead_total, ullong, 0, 0444);
MODULE_PARM_DESC(rmnet_shs_cpu_qhead_total, "SHS nw stack queue processed total");
unsigned long rmnet_shs_flow_hash[MAX_SUPPORTED_FLOWS_DEBUG];
module_param_array(rmnet_shs_flow_hash, ulong, 0, 0444);
MODULE_PARM_DESC(rmnet_shs_flow_hash, "SHS stamp hash flow");
unsigned long rmnet_shs_flow_proto[MAX_SUPPORTED_FLOWS_DEBUG];
module_param_array(rmnet_shs_flow_proto, ulong, 0, 0444);
MODULE_PARM_DESC(rmnet_shs_flow_proto, "SHS stamp hash transport protocol");
unsigned long long rmnet_shs_flow_inactive_tsec[MAX_SUPPORTED_FLOWS_DEBUG];
module_param_array(rmnet_shs_flow_inactive_tsec, ullong, 0, 0444);
MODULE_PARM_DESC(rmnet_shs_flow_inactive_tsec, "SHS stamp inactive flow time");
int rmnet_shs_flow_cpu[MAX_SUPPORTED_FLOWS_DEBUG] = {
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1};
module_param_array(rmnet_shs_flow_cpu, int, NULL, 0444);
MODULE_PARM_DESC(rmnet_shs_flow_cpu, "SHS stamp flow processing CPU");
int rmnet_shs_flow_cpu_recommended[MAX_SUPPORTED_FLOWS_DEBUG] = {
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1
};
module_param_array(rmnet_shs_flow_cpu_recommended, int, NULL, 0444);
MODULE_PARM_DESC(rmnet_shs_flow_cpu_recommended, "SHS stamp flow proc CPU");
unsigned long long rmnet_shs_flow_rx_bytes[MAX_SUPPORTED_FLOWS_DEBUG];
module_param_array(rmnet_shs_flow_rx_bytes, ullong, 0, 0444);
MODULE_PARM_DESC(rmnet_shs_flow_rx_bytes, "SHS stamp bytes per flow");
unsigned long long rmnet_shs_flow_rx_pkts[MAX_SUPPORTED_FLOWS_DEBUG];
module_param_array(rmnet_shs_flow_rx_pkts, ullong, 0, 0444);
MODULE_PARM_DESC(rmnet_shs_flow_rx_pkts, "SHS stamp total pkts per flow");
unsigned long long rmnet_shs_flow_rx_bps[MAX_SUPPORTED_FLOWS_DEBUG];
module_param_array(rmnet_shs_flow_rx_bps, ullong, 0, 0444);
MODULE_PARM_DESC(rmnet_shs_flow_rx_bps, "SHS stamp enq rate per flow");
unsigned long long rmnet_shs_flow_rx_pps[MAX_SUPPORTED_FLOWS_DEBUG];
module_param_array(rmnet_shs_flow_rx_pps, ullong, 0, 0444);
MODULE_PARM_DESC(rmnet_shs_flow_rx_pps, "SHS stamp pkt enq rate per flow");
/* Counters for suggestions made by wq */
unsigned long long rmnet_shs_flow_silver_to_gold[MAX_SUPPORTED_FLOWS_DEBUG];
module_param_array(rmnet_shs_flow_silver_to_gold, ullong, 0, 0444);
MODULE_PARM_DESC(rmnet_shs_flow_silver_to_gold, "SHS Suggest Silver to Gold");
unsigned long long rmnet_shs_flow_gold_to_silver[MAX_SUPPORTED_FLOWS_DEBUG];
module_param_array(rmnet_shs_flow_gold_to_silver, ullong, 0, 0444);
MODULE_PARM_DESC(rmnet_shs_flow_gold_to_silver, "SHS Suggest Gold to Silver");
unsigned long long rmnet_shs_flow_gold_balance[MAX_SUPPORTED_FLOWS_DEBUG];
module_param_array(rmnet_shs_flow_gold_balance, ullong, 0, 0444);
MODULE_PARM_DESC(rmnet_shs_flow_gold_balance, "SHS Suggest Gold Balance");
static DEFINE_SPINLOCK(rmnet_shs_hstat_tbl_lock);
static DEFINE_SPINLOCK(rmnet_shs_ep_lock);
static time_t rmnet_shs_wq_tnsec;
static struct workqueue_struct *rmnet_shs_wq;
static struct rmnet_shs_delay_wq_s *rmnet_shs_delayed_wq;
static struct rmnet_shs_wq_rx_flow_s rmnet_shs_rx_flow_tbl;
static struct list_head rmnet_shs_wq_hstat_tbl =
LIST_HEAD_INIT(rmnet_shs_wq_hstat_tbl);
static int rmnet_shs_flow_dbg_stats_idx_cnt;
struct list_head rmnet_shs_wq_ep_tbl = LIST_HEAD_INIT(rmnet_shs_wq_ep_tbl);
/* Helper functions to add and remove entries to the table
* that maintains a list of all endpoints (vnd's) available on this device.
*/
void rmnet_shs_wq_ep_tbl_add(struct rmnet_shs_wq_ep_s *ep)
{
trace_rmnet_shs_wq_low(RMNET_SHS_WQ_EP_TBL, RMNET_SHS_WQ_EP_TBL_ADD,
0xDEF, 0xDEF, 0xDEF, 0xDEF, ep, NULL);
list_add(&ep->ep_list_id, &rmnet_shs_wq_ep_tbl);
}
void rmnet_shs_wq_ep_tbl_remove(struct rmnet_shs_wq_ep_s *ep)
{
trace_rmnet_shs_wq_low(RMNET_SHS_WQ_EP_TBL, RMNET_SHS_WQ_EP_TBL_DEL,
0xDEF, 0xDEF, 0xDEF, 0xDEF, ep, NULL);
list_del_init(&ep->ep_list_id);
}
/* Helper functions to add and remove entries to the table
* that maintains a list of all nodes that maintain statistics per flow
*/
void rmnet_shs_wq_hstat_tbl_add(struct rmnet_shs_wq_hstat_s *hnode)
{
unsigned long flags;
trace_rmnet_shs_wq_low(RMNET_SHS_WQ_HSTAT_TBL,
RMNET_SHS_WQ_HSTAT_TBL_ADD,
0xDEF, 0xDEF, 0xDEF, 0xDEF, hnode, NULL);
spin_lock_irqsave(&rmnet_shs_hstat_tbl_lock, flags);
list_add(&hnode->hstat_node_id, &rmnet_shs_wq_hstat_tbl);
spin_unlock_irqrestore(&rmnet_shs_hstat_tbl_lock, flags);
}
void rmnet_shs_wq_hstat_tbl_remove(struct rmnet_shs_wq_hstat_s *hnode)
{
unsigned long flags;
trace_rmnet_shs_wq_low(RMNET_SHS_WQ_HSTAT_TBL,
RMNET_SHS_WQ_HSTAT_TBL_DEL,
0xDEF, 0xDEF, 0xDEF, 0xDEF, hnode, NULL);
spin_lock_irqsave(&rmnet_shs_hstat_tbl_lock, flags);
list_del_init(&hnode->hstat_node_id);
spin_unlock_irqrestore(&rmnet_shs_hstat_tbl_lock, flags);
}
/* We maintain a list of all flow nodes processed by a cpu.
* Below helper functions are used to maintain flow<=>cpu
* association.*
*/
void rmnet_shs_wq_cpu_list_remove(struct rmnet_shs_wq_hstat_s *hnode)
{
unsigned long flags;
trace_rmnet_shs_wq_low(RMNET_SHS_WQ_CPU_HSTAT_TBL,
RMNET_SHS_WQ_CPU_HSTAT_TBL_DEL,
0xDEF, 0xDEF, 0xDEF, 0xDEF, hnode, NULL);
spin_lock_irqsave(&rmnet_shs_hstat_tbl_lock, flags);
list_del_init(&hnode->cpu_node_id);
spin_unlock_irqrestore(&rmnet_shs_hstat_tbl_lock, flags);
}
void rmnet_shs_wq_cpu_list_add(struct rmnet_shs_wq_hstat_s *hnode,
struct list_head *head)
{
unsigned long flags;
trace_rmnet_shs_wq_low(RMNET_SHS_WQ_CPU_HSTAT_TBL,
RMNET_SHS_WQ_CPU_HSTAT_TBL_ADD,
0xDEF, 0xDEF, 0xDEF, 0xDEF, hnode, NULL);
spin_lock_irqsave(&rmnet_shs_hstat_tbl_lock, flags);
list_add(&hnode->cpu_node_id, head);
spin_unlock_irqrestore(&rmnet_shs_hstat_tbl_lock, flags);
}
void rmnet_shs_wq_cpu_list_move(struct rmnet_shs_wq_hstat_s *hnode,
struct list_head *head)
{
unsigned long flags;
trace_rmnet_shs_wq_low(RMNET_SHS_WQ_CPU_HSTAT_TBL,
RMNET_SHS_WQ_CPU_HSTAT_TBL_MOVE,
hnode->current_cpu,
0xDEF, 0xDEF, 0xDEF, hnode, NULL);
spin_lock_irqsave(&rmnet_shs_hstat_tbl_lock, flags);
list_move(&hnode->cpu_node_id, head);
spin_unlock_irqrestore(&rmnet_shs_hstat_tbl_lock, flags);
}
/* Resets all the parameters used to maintain hash statistics */
void rmnet_shs_wq_hstat_reset_node(struct rmnet_shs_wq_hstat_s *hnode)
{
hnode->c_epoch = 0;
hnode->l_epoch = 0;
hnode->node = NULL;
hnode->inactive_duration = 0;
hnode->rx_skb = 0;
hnode->rx_bytes = 0;
hnode->rx_pps = 0;
hnode->rx_bps = 0;
hnode->last_rx_skb = 0;
hnode->last_rx_bytes = 0;
hnode->rps_config_msk = 0;
hnode->current_core_msk = 0;
hnode->def_core_msk = 0;
hnode->pri_core_msk = 0;
hnode->available_core_msk = 0;
hnode->hash = 0;
hnode->suggested_cpu = 0;
hnode->current_cpu = 0;
hnode->skb_tport_proto = 0;
hnode->stat_idx = -1;
INIT_LIST_HEAD(&hnode->cpu_node_id);
hnode->is_new_flow = 0;
/* clear in use flag as a last action. This is required to ensure
* the same node does not get allocated until all the paramaeters
* are cleared.
*/
hnode->in_use = 0;
trace_rmnet_shs_wq_low(RMNET_SHS_WQ_HSTAT_TBL,
RMNET_SHS_WQ_HSTAT_TBL_NODE_RESET,
hnode->is_perm, 0xDEF, 0xDEF, 0xDEF, hnode, NULL);
}
/* Preallocates a set of flow nodes that maintain flow level statistics*/
void rmnet_shs_wq_hstat_alloc_nodes(u8 num_nodes_to_allocate, u8 is_store_perm)
{
struct rmnet_shs_wq_hstat_s *hnode = NULL;
while (num_nodes_to_allocate > 0) {
hnode = kzalloc(sizeof(*hnode), GFP_ATOMIC);
if (hnode) {
hnode->is_perm = is_store_perm;
rmnet_shs_wq_hstat_reset_node(hnode);
INIT_LIST_HEAD(&hnode->hstat_node_id);
INIT_LIST_HEAD(&hnode->cpu_node_id);
rmnet_shs_wq_hstat_tbl_add(hnode);
} else {
rmnet_shs_crit_err[RMNET_SHS_WQ_ALLOC_HSTAT_ERR]++;
}
hnode = NULL;
num_nodes_to_allocate--;
}
}
/* If there is an already pre-allocated node available and not in use,
* we will try to re-use them.
*/
struct rmnet_shs_wq_hstat_s *rmnet_shs_wq_get_new_hstat_node(void)
{
struct rmnet_shs_wq_hstat_s *hnode = NULL;
struct rmnet_shs_wq_hstat_s *ret_node = NULL;
unsigned long flags;
spin_lock_irqsave(&rmnet_shs_hstat_tbl_lock, flags);
list_for_each_entry(hnode, &rmnet_shs_wq_hstat_tbl, hstat_node_id) {
if (hnode == NULL)
continue;
if (hnode->in_use == 0) {
ret_node = hnode;
ret_node->in_use = 1;
ret_node->is_new_flow = 1;
break;
}
}
spin_unlock_irqrestore(&rmnet_shs_hstat_tbl_lock, flags);
if (ret_node) {
trace_rmnet_shs_wq_low(RMNET_SHS_WQ_HSTAT_TBL,
RMNET_SHS_WQ_HSTAT_TBL_NODE_REUSE,
hnode->is_perm, 0xDEF, 0xDEF, 0xDEF,
hnode, NULL);
return ret_node;
}
/* We have reached a point where all pre-allocated nodes are in use
* Allocating memory to maintain the flow level stats for new flow.
* However, this newly allocated memory will be released as soon as we
* realize that this flow is inactive
*/
ret_node = kzalloc(sizeof(*hnode), GFP_ATOMIC);
if (!ret_node) {
rmnet_shs_crit_err[RMNET_SHS_WQ_ALLOC_HSTAT_ERR]++;
return NULL;
}
rmnet_shs_wq_hstat_reset_node(ret_node);
ret_node->is_perm = 0;
ret_node->in_use = 1;
ret_node->is_new_flow = 1;
INIT_LIST_HEAD(&ret_node->hstat_node_id);
INIT_LIST_HEAD(&ret_node->cpu_node_id);
trace_rmnet_shs_wq_low(RMNET_SHS_WQ_HSTAT_TBL,
RMNET_SHS_WQ_HSTAT_TBL_NODE_DYN_ALLOCATE,
ret_node->is_perm, 0xDEF, 0xDEF, 0xDEF,
ret_node, NULL);
rmnet_shs_wq_hstat_tbl_add(ret_node);
return ret_node;
}
void rmnet_shs_wq_create_new_flow(struct rmnet_shs_skbn_s *node_p)
{
struct timespec time;
if (!node_p) {
rmnet_shs_crit_err[RMNET_SHS_WQ_INVALID_PTR_ERR]++;
return;
}
node_p->hstats = rmnet_shs_wq_get_new_hstat_node();
if (node_p->hstats != NULL) {
(void)getnstimeofday(&time);
node_p->hstats->hash = node_p->hash;
node_p->hstats->skb_tport_proto = node_p->skb_tport_proto;
node_p->hstats->current_cpu = node_p->map_cpu;
node_p->hstats->suggested_cpu = node_p->map_cpu;
/* Start TCP flows with segmentation if userspace connected */
if (rmnet_shs_userspace_connected &&
node_p->hstats->skb_tport_proto == IPPROTO_TCP)
node_p->hstats->segment_enable = 1;
node_p->hstats->node = node_p;
node_p->hstats->c_epoch = RMNET_SHS_SEC_TO_NSEC(time.tv_sec) +
time.tv_nsec;
node_p->hstats->l_epoch = RMNET_SHS_SEC_TO_NSEC(time.tv_sec) +
time.tv_nsec;
}
trace_rmnet_shs_wq_high(RMNET_SHS_WQ_HSTAT_TBL,
RMNET_SHS_WQ_HSTAT_TBL_NODE_NEW_REQ,
0xDEF, 0xDEF, 0xDEF, 0xDEF,
node_p, node_p->hstats);
}
/* Compute the average pps for a flow based on tuning param
* Often when we decide to switch from a small cluster core,
* it is because of the heavy traffic on that core. In such
* circumstances, we want to switch to a big cluster
* core as soon as possible. Therefore, we will provide a
* greater weightage to the most recent sample compared to
* the previous samples.
*
* On the other hand, when a flow which is on a big cluster
* cpu suddenly starts to receive low traffic we move to a
* small cluster core after observing low traffic for some
* more samples. This approach avoids switching back and forth
* to small cluster cpus due to momentary decrease in data
* traffic.
*/
static u64 rmnet_shs_wq_get_flow_avg_pps(struct rmnet_shs_wq_hstat_s *hnode)
{
u64 avg_pps, mov_avg_pps;
u16 new_weight, old_weight;
if (!hnode) {
rmnet_shs_crit_err[RMNET_SHS_WQ_INVALID_PTR_ERR]++;
return 0;
}
if (rmnet_shs_is_lpwr_cpu(hnode->current_cpu)) {
/* More weight to current value */
new_weight = rmnet_shs_wq_tuning;
old_weight = 100 - rmnet_shs_wq_tuning;
} else {
old_weight = rmnet_shs_wq_tuning;
new_weight = 100 - rmnet_shs_wq_tuning;
}
/* computing weighted average per flow, if the flow has just started,
* there is no past values, so we use the current pps as the avg
*/
if (hnode->last_pps == 0) {
avg_pps = hnode->rx_pps;
} else {
mov_avg_pps = (hnode->last_pps + hnode->avg_pps) / 2;
avg_pps = (((new_weight * hnode->rx_pps) +
(old_weight * mov_avg_pps)) /
(new_weight + old_weight));
}
return avg_pps;
}
static u64 rmnet_shs_wq_get_cpu_avg_pps(u16 cpu_num)
{
u64 avg_pps, mov_avg_pps;
u16 new_weight, old_weight;
struct rmnet_shs_wq_cpu_rx_pkt_q_s *cpu_node;
struct rmnet_shs_wq_rx_flow_s *rx_flow_tbl_p = &rmnet_shs_rx_flow_tbl;
if (cpu_num >= MAX_CPUS) {
rmnet_shs_crit_err[RMNET_SHS_WQ_INVALID_CPU_ERR]++;
return 0;
}
cpu_node = &rx_flow_tbl_p->cpu_list[cpu_num];
if (rmnet_shs_is_lpwr_cpu(cpu_num)) {
/* More weight to current value */
new_weight = rmnet_shs_wq_tuning;
old_weight = 100 - rmnet_shs_wq_tuning;
} else {
old_weight = rmnet_shs_wq_tuning;
new_weight = 100 - rmnet_shs_wq_tuning;
}
/* computing weighted average per flow, if the cpu has not past values
* for pps, we use the current value as the average
*/
if (cpu_node->last_rx_pps == 0) {
avg_pps = cpu_node->avg_pps;
} else {
mov_avg_pps = (cpu_node->last_rx_pps + cpu_node->avg_pps) / 2;
avg_pps = (((new_weight * cpu_node->rx_pps) +
(old_weight * mov_avg_pps)) /
(new_weight + old_weight));
}
trace_rmnet_shs_wq_high(RMNET_SHS_WQ_CPU_STATS,
RMNET_SHS_WQ_CPU_STATS_CORE2SWITCH_EVAL_CPU,
cpu_num, cpu_node->rx_pps, cpu_node->last_rx_pps,
avg_pps, NULL, NULL);
return avg_pps;
}
/* Refresh the RPS mask associated with this flow */
void rmnet_shs_wq_update_hstat_rps_msk(struct rmnet_shs_wq_hstat_s *hstat_p)
{
struct rmnet_shs_skbn_s *node_p = NULL;
struct rmnet_shs_wq_ep_s *ep = NULL;
if (!hstat_p) {
rmnet_shs_crit_err[RMNET_SHS_WQ_INVALID_PTR_ERR]++;
return;
}
node_p = hstat_p->node;
/*Map RPS mask from the endpoint associated with this flow*/
list_for_each_entry(ep, &rmnet_shs_wq_ep_tbl, ep_list_id) {
if (ep && (node_p->dev == ep->ep)) {
hstat_p->rps_config_msk = ep->rps_config_msk;
hstat_p->def_core_msk = ep->default_core_msk;
hstat_p->pri_core_msk = ep->pri_core_msk;
/* Update ep tput stats while we're here */
if (hstat_p->skb_tport_proto == IPPROTO_TCP) {
rm_err("SHS_UDP: adding TCP bps %lu to ep_total %lu ep name %s",
hstat_p->rx_bps, ep->tcp_rx_bps, node_p->dev->name);
ep->tcp_rx_bps += hstat_p->rx_bps;
} else if (hstat_p->skb_tport_proto == IPPROTO_UDP) {
rm_err("SHS_UDP: adding UDP rx_bps %lu to ep_total %lu ep name %s",
hstat_p->rx_bps, ep->udp_rx_bps, node_p->dev->name);
ep->udp_rx_bps += hstat_p->rx_bps;
}
break;
}
}
trace_rmnet_shs_wq_low(RMNET_SHS_WQ_FLOW_STATS,
RMNET_SHS_WQ_FLOW_STATS_UPDATE_MSK,
hstat_p->rps_config_msk,
hstat_p->def_core_msk,
hstat_p->pri_core_msk,
0xDEF, hstat_p, node_p);
}
void rmnet_shs_wq_update_hash_stats_debug(struct rmnet_shs_wq_hstat_s *hstats_p,
struct rmnet_shs_skbn_s *node_p)
{
int idx = rmnet_shs_flow_dbg_stats_idx_cnt;
if (!rmnet_shs_stats_enabled)
return;
if (!hstats_p || !node_p) {
rmnet_shs_crit_err[RMNET_SHS_WQ_INVALID_PTR_ERR]++;
return;
}
if (hstats_p->stat_idx < 0) {
idx = idx % MAX_SUPPORTED_FLOWS_DEBUG;
hstats_p->stat_idx = idx;
rmnet_shs_flow_dbg_stats_idx_cnt++;
}
rmnet_shs_flow_hash[hstats_p->stat_idx] = hstats_p->hash;
rmnet_shs_flow_proto[hstats_p->stat_idx] = node_p->skb_tport_proto;
rmnet_shs_flow_inactive_tsec[hstats_p->stat_idx] =
RMNET_SHS_NSEC_TO_SEC(hstats_p->inactive_duration);
rmnet_shs_flow_rx_bps[hstats_p->stat_idx] = hstats_p->rx_bps;
rmnet_shs_flow_rx_pps[hstats_p->stat_idx] = hstats_p->rx_pps;
rmnet_shs_flow_rx_bytes[hstats_p->stat_idx] = hstats_p->rx_bytes;
rmnet_shs_flow_rx_pkts[hstats_p->stat_idx] = hstats_p->rx_skb;
rmnet_shs_flow_cpu[hstats_p->stat_idx] = hstats_p->current_cpu;
rmnet_shs_flow_cpu_recommended[hstats_p->stat_idx] =
hstats_p->suggested_cpu;
rmnet_shs_flow_silver_to_gold[hstats_p->stat_idx] =
hstats_p->rmnet_shs_wq_suggs[RMNET_SHS_WQ_SUGG_SILVER_TO_GOLD];
rmnet_shs_flow_gold_to_silver[hstats_p->stat_idx] =
hstats_p->rmnet_shs_wq_suggs[RMNET_SHS_WQ_SUGG_GOLD_TO_SILVER];
rmnet_shs_flow_gold_balance[hstats_p->stat_idx] =
hstats_p->rmnet_shs_wq_suggs[RMNET_SHS_WQ_SUGG_GOLD_BALANCE];
}
/* Returns TRUE if this flow received a new packet
* FALSE otherwise
*/
u8 rmnet_shs_wq_is_hash_rx_new_pkt(struct rmnet_shs_wq_hstat_s *hstats_p,
struct rmnet_shs_skbn_s *node_p)
{
if (!hstats_p || !node_p) {
rmnet_shs_crit_err[RMNET_SHS_WQ_INVALID_PTR_ERR]++;
return 0;
}
if (node_p->num_skb == hstats_p->rx_skb)
return 0;
return 1;
}
void rmnet_shs_wq_update_hash_tinactive(struct rmnet_shs_wq_hstat_s *hstats_p,
struct rmnet_shs_skbn_s *node_p)
{
time_t tdiff;
if (!hstats_p || !node_p) {
rmnet_shs_crit_err[RMNET_SHS_WQ_INVALID_PTR_ERR]++;
return;
}
tdiff = rmnet_shs_wq_tnsec - hstats_p->c_epoch;
hstats_p->inactive_duration = tdiff;
trace_rmnet_shs_wq_low(RMNET_SHS_WQ_FLOW_STATS,
RMNET_SHS_WQ_FLOW_STATS_FLOW_INACTIVE,
hstats_p->hash, tdiff, 0xDEF, 0xDEF,
hstats_p, NULL);
}
void rmnet_shs_wq_update_hash_stats(struct rmnet_shs_wq_hstat_s *hstats_p)
{
time_t tdiff;
u64 skb_diff, bytes_diff;
struct rmnet_shs_skbn_s *node_p;
if (!hstats_p) {
rmnet_shs_crit_err[RMNET_SHS_WQ_INVALID_PTR_ERR]++;
return;
}
node_p = hstats_p->node;
if (!rmnet_shs_wq_is_hash_rx_new_pkt(hstats_p, node_p)) {
hstats_p->rx_pps = 0;
hstats_p->avg_pps = 0;
hstats_p->rx_bps = 0;
rmnet_shs_wq_update_hash_tinactive(hstats_p, node_p);
rmnet_shs_wq_update_hash_stats_debug(hstats_p, node_p);
return;
}
trace_rmnet_shs_wq_low(RMNET_SHS_WQ_FLOW_STATS,
RMNET_SHS_WQ_FLOW_STATS_START,
hstats_p->hash, 0xDEF, hstats_p->rx_pps,
hstats_p->rx_bps, hstats_p, NULL);
rmnet_shs_wq_update_hstat_rps_msk(hstats_p);
hstats_p->inactive_duration = 0;
hstats_p->l_epoch = node_p->hstats->c_epoch;
hstats_p->last_rx_skb = node_p->hstats->rx_skb;
hstats_p->last_rx_bytes = node_p->hstats->rx_bytes;
hstats_p->c_epoch = rmnet_shs_wq_tnsec;
hstats_p->rx_skb = node_p->num_skb;
hstats_p->rx_bytes = node_p->num_skb_bytes;
tdiff = (hstats_p->c_epoch - hstats_p->l_epoch);
skb_diff = hstats_p->rx_skb - hstats_p->last_rx_skb;
bytes_diff = hstats_p->rx_bytes - hstats_p->last_rx_bytes;
hstats_p->rx_pps = RMNET_SHS_RX_BPNSEC_TO_BPSEC(skb_diff)/(tdiff);
hstats_p->rx_bps = RMNET_SHS_RX_BPNSEC_TO_BPSEC(bytes_diff)/(tdiff);
hstats_p->rx_bps = RMNET_SHS_BYTE_TO_BIT(hstats_p->rx_bps);
hstats_p->avg_pps = rmnet_shs_wq_get_flow_avg_pps(hstats_p);
hstats_p->last_pps = hstats_p->rx_pps;
rmnet_shs_wq_update_hash_stats_debug(hstats_p, node_p);
trace_rmnet_shs_wq_high(RMNET_SHS_WQ_FLOW_STATS,
RMNET_SHS_WQ_FLOW_STATS_END,
hstats_p->hash, hstats_p->rx_pps,
hstats_p->rx_bps, (tdiff/1000000),
hstats_p, NULL);
}
static void rmnet_shs_wq_refresh_cpu_rates_debug(u16 cpu,
struct rmnet_shs_wq_cpu_rx_pkt_q_s *cpu_p)
{
if (!rmnet_shs_stats_enabled)
return;
if (cpu >= MAX_CPUS) {
rmnet_shs_crit_err[RMNET_SHS_WQ_INVALID_CPU_ERR]++;
return;
}
if (!cpu_p) {
rmnet_shs_crit_err[RMNET_SHS_WQ_INVALID_PTR_ERR]++;
return;
}
rmnet_shs_cpu_rx_bps[cpu] = cpu_p->rx_bps;
rmnet_shs_cpu_rx_pps[cpu] = cpu_p->rx_pps;
rmnet_shs_cpu_rx_flows[cpu] = cpu_p->flows;
rmnet_shs_cpu_rx_bytes[cpu] = cpu_p->rx_bytes;
rmnet_shs_cpu_rx_pkts[cpu] = cpu_p->rx_skbs;
rmnet_shs_cpu_qhead_diff[cpu] = cpu_p->qhead_diff;
rmnet_shs_cpu_qhead_total[cpu] = cpu_p->qhead_total;
}
static void rmnet_shs_wq_refresh_dl_mrkr_stats(void)
{
struct rmnet_shs_wq_rx_flow_s *tbl_p = &rmnet_shs_rx_flow_tbl;
struct rmnet_port *port;
u64 pkt_diff, byte_diff;
time_t tdiff;
tbl_p->dl_mrk_last_rx_bytes = tbl_p->dl_mrk_rx_bytes;
tbl_p->dl_mrk_last_rx_pkts = tbl_p->dl_mrk_rx_pkts;
port = rmnet_shs_cfg.port;
if (!port) {
rmnet_shs_crit_err[RMNET_SHS_WQ_GET_RMNET_PORT_ERR]++;
return;
}
tbl_p->dl_mrk_rx_pkts = port->stats.dl_hdr_total_pkts;
tbl_p->dl_mrk_rx_bytes = port->stats.dl_hdr_total_bytes;
tdiff = rmnet_shs_wq_tnsec - tbl_p->l_epoch;
pkt_diff = tbl_p->dl_mrk_rx_pkts - tbl_p->dl_mrk_last_rx_pkts;
byte_diff = tbl_p->dl_mrk_rx_bytes - tbl_p->dl_mrk_last_rx_bytes;
tbl_p->dl_mrk_rx_pps = RMNET_SHS_RX_BPNSEC_TO_BPSEC(pkt_diff)/tdiff;
tbl_p->dl_mrk_rx_bps = RMNET_SHS_RX_BPNSEC_TO_BPSEC(byte_diff)/tdiff;
tbl_p->dl_mrk_rx_bps = RMNET_SHS_BYTE_TO_BIT(tbl_p->dl_mrk_rx_bps);
}
static void rmnet_shs_wq_refresh_total_stats(void)
{
struct rmnet_shs_wq_rx_flow_s *tbl_p = &rmnet_shs_rx_flow_tbl;
u64 pkt_diff, byte_diff, pps, bps;
time_t tdiff;
tdiff = rmnet_shs_wq_tnsec - tbl_p->l_epoch;
pkt_diff = (tbl_p->rx_skbs - tbl_p->last_rx_skbs);
byte_diff = tbl_p->rx_bytes - tbl_p->last_rx_bytes;
pps = RMNET_SHS_RX_BPNSEC_TO_BPSEC(pkt_diff)/tdiff;
bps = RMNET_SHS_RX_BPNSEC_TO_BPSEC(byte_diff)/tdiff;
tbl_p->last_rx_bps = tbl_p->rx_bps;
tbl_p->last_rx_pps = tbl_p->rx_pps;
tbl_p->rx_bps = RMNET_SHS_BYTE_TO_BIT(bps);
tbl_p->rx_pps = pps;
tbl_p->l_epoch = rmnet_shs_wq_tnsec;
tbl_p->last_rx_bytes = tbl_p->rx_bytes;
tbl_p->last_rx_skbs = tbl_p->rx_skbs;
trace_rmnet_shs_wq_high(RMNET_SHS_WQ_TOTAL_STATS,
RMNET_SHS_WQ_TOTAL_STATS_UPDATE,
tbl_p->rx_pps,
tbl_p->dl_mrk_rx_pps,
tbl_p->rx_bps,
tbl_p->dl_mrk_rx_bps, NULL, NULL);
}
static void rmnet_shs_wq_refresh_cpu_stats(u16 cpu)
{
struct rmnet_shs_wq_cpu_rx_pkt_q_s *cpu_p;
time_t tdiff;
u64 new_skbs, new_bytes;
u64 last_rx_bps, last_rx_pps;
u32 new_qhead;
if (cpu >= MAX_CPUS) {
rmnet_shs_crit_err[RMNET_SHS_WQ_INVALID_CPU_ERR]++;
return;
}
cpu_p = &rmnet_shs_rx_flow_tbl.cpu_list[cpu];
new_skbs = cpu_p->rx_skbs - cpu_p->last_rx_skbs;
new_qhead = rmnet_shs_get_cpu_qhead(cpu);
if (cpu_p->qhead_start == 0)
cpu_p->qhead_start = new_qhead;
cpu_p->last_qhead = cpu_p->qhead;
cpu_p->qhead = new_qhead;
cpu_p->qhead_diff = cpu_p->qhead - cpu_p->last_qhead;
cpu_p->qhead_total = cpu_p->qhead - cpu_p->qhead_start;
if (rmnet_shs_cpu_node_tbl[cpu].wqprio)
rmnet_shs_cpu_node_tbl[cpu].wqprio = (rmnet_shs_cpu_node_tbl[cpu].wqprio + 1)
% (PRIO_BACKOFF);
if (new_skbs == 0) {
cpu_p->l_epoch = rmnet_shs_wq_tnsec;
cpu_p->rx_bps = 0;
cpu_p->rx_pps = 0;
cpu_p->avg_pps = 0;
if (rmnet_shs_userspace_connected) {
rmnet_shs_wq_cpu_caps_list_add(&rmnet_shs_rx_flow_tbl,
cpu_p, &cpu_caps);
}
rmnet_shs_wq_refresh_cpu_rates_debug(cpu, cpu_p);
return;
}
tdiff = rmnet_shs_wq_tnsec - cpu_p->l_epoch;
new_bytes = cpu_p->rx_bytes - cpu_p->last_rx_bytes;
last_rx_bps = cpu_p->rx_bps;
last_rx_pps = cpu_p->rx_pps;
cpu_p->rx_pps = RMNET_SHS_RX_BPNSEC_TO_BPSEC(new_skbs)/tdiff;
cpu_p->rx_bps = RMNET_SHS_RX_BPNSEC_TO_BPSEC(new_bytes)/tdiff;
cpu_p->rx_bps = RMNET_SHS_BYTE_TO_BIT(cpu_p->rx_bps);
cpu_p->avg_pps = rmnet_shs_wq_get_cpu_avg_pps(cpu);
cpu_p->last_rx_bps = last_rx_bps;
cpu_p->last_rx_pps = last_rx_pps;
cpu_p->l_epoch = rmnet_shs_wq_tnsec;
cpu_p->last_rx_skbs = cpu_p->rx_skbs;
cpu_p->last_rx_bytes = cpu_p->rx_bytes;
cpu_p->rx_bps_est = cpu_p->rx_bps;
if (rmnet_shs_userspace_connected) {
rmnet_shs_wq_cpu_caps_list_add(&rmnet_shs_rx_flow_tbl,
cpu_p, &cpu_caps);
}
trace_rmnet_shs_wq_high(RMNET_SHS_WQ_CPU_STATS,
RMNET_SHS_WQ_CPU_STATS_UPDATE, cpu,
cpu_p->flows, cpu_p->rx_pps,
cpu_p->rx_bps, NULL, NULL);
rmnet_shs_wq_refresh_cpu_rates_debug(cpu, cpu_p);
}
static void rmnet_shs_wq_refresh_all_cpu_stats(void)
{
u16 cpu;
trace_rmnet_shs_wq_high(RMNET_SHS_WQ_CPU_STATS,
RMNET_SHS_WQ_CPU_STATS_START,
0xDEF, 0xDEF, 0xDEF, 0xDEF, NULL, NULL);
for (cpu = 0; cpu < MAX_CPUS; cpu++)
rmnet_shs_wq_refresh_cpu_stats(cpu);
trace_rmnet_shs_wq_high(RMNET_SHS_WQ_CPU_STATS,
RMNET_SHS_WQ_CPU_STATS_END,
0xDEF, 0xDEF, 0xDEF, 0xDEF, NULL, NULL);
}
void rmnet_shs_wq_update_cpu_rx_tbl(struct rmnet_shs_wq_hstat_s *hstat_p)
{
struct rmnet_shs_wq_rx_flow_s *tbl_p = &rmnet_shs_rx_flow_tbl;
struct rmnet_shs_skbn_s *node_p;
u64 skb_diff, byte_diff;
u16 cpu_num;
if (!hstat_p) {
rmnet_shs_crit_err[RMNET_SHS_WQ_INVALID_PTR_ERR]++;
return;
}
node_p = hstat_p->node;
if (hstat_p->inactive_duration > 0)
return;
cpu_num = node_p->map_cpu;
if (cpu_num >= MAX_CPUS) {
rmnet_shs_crit_err[RMNET_SHS_INVALID_CPU_ERR]++;
return;
}
skb_diff = hstat_p->rx_skb - hstat_p->last_rx_skb;
byte_diff = hstat_p->rx_bytes - hstat_p->last_rx_bytes;
if (hstat_p->is_new_flow) {
rmnet_shs_wq_cpu_list_add(hstat_p,
&tbl_p->cpu_list[cpu_num].hstat_id);
rm_err("SHS_FLOW: adding flow 0x%x on cpu[%d] "
"pps: %llu | avg_pps %llu",
hstat_p->hash, hstat_p->current_cpu,
hstat_p->rx_pps, hstat_p->avg_pps);
hstat_p->is_new_flow = 0;
}
/* check if the flow has switched to another CPU*/
if (cpu_num != hstat_p->current_cpu) {
rm_err("SHS_FLOW: moving flow 0x%x on cpu[%d] to cpu[%d] "
"pps: %llu | avg_pps %llu",
hstat_p->hash, hstat_p->current_cpu, cpu_num,
hstat_p->rx_pps, hstat_p->avg_pps);
trace_rmnet_shs_wq_high(RMNET_SHS_WQ_FLOW_STATS,
RMNET_SHS_WQ_FLOW_STATS_UPDATE_NEW_CPU,
hstat_p->hash, hstat_p->current_cpu,
cpu_num, 0xDEF, hstat_p, NULL);
rmnet_shs_wq_cpu_list_move(hstat_p,
&tbl_p->cpu_list[cpu_num].hstat_id);
rmnet_shs_wq_inc_cpu_flow(cpu_num);
rmnet_shs_wq_dec_cpu_flow(hstat_p->current_cpu);
hstat_p->current_cpu = cpu_num;
}
/* Assuming that the data transfers after the last refresh
* interval have happened with the newer CPU
*/
tbl_p->cpu_list[cpu_num].rx_skbs += skb_diff;
tbl_p->cpu_list[cpu_num].rx_bytes += byte_diff;
tbl_p->rx_skbs += skb_diff;
tbl_p->rx_bytes += byte_diff;
}
void rmnet_shs_wq_chng_suggested_cpu(u16 old_cpu, u16 new_cpu,
struct rmnet_shs_wq_ep_s *ep)
{
struct rmnet_shs_skbn_s *node_p;
struct rmnet_shs_wq_hstat_s *hstat_p;
u16 bkt;
hash_for_each(RMNET_SHS_HT, bkt, node_p, list) {
if (!node_p)
continue;
if (!node_p->hstats)
continue;
hstat_p = node_p->hstats;
if ((hstat_p->suggested_cpu == old_cpu) &&
(node_p->dev == ep->ep)) {
trace_rmnet_shs_wq_high(RMNET_SHS_WQ_FLOW_STATS,
RMNET_SHS_WQ_FLOW_STATS_SUGGEST_NEW_CPU,
hstat_p->hash, hstat_p->suggested_cpu,
new_cpu, 0xDEF, hstat_p, NULL);
node_p->hstats->suggested_cpu = new_cpu;
}
}
}
/* Increment the per-flow counter for suggestion type */
static void rmnet_shs_wq_inc_sugg_type(u32 sugg_type,
struct rmnet_shs_wq_hstat_s *hstat_p)
{
if (sugg_type >= RMNET_SHS_WQ_SUGG_MAX || hstat_p == NULL)
return;
hstat_p->rmnet_shs_wq_suggs[sugg_type] += 1;
}
/* Change suggested cpu, return 1 if suggestion was made, 0 otherwise */
static int rmnet_shs_wq_chng_flow_cpu(u16 old_cpu, u16 new_cpu,
struct rmnet_shs_wq_ep_s *ep,
u32 hash_to_move, u32 sugg_type)
{
struct rmnet_shs_skbn_s *node_p;
struct rmnet_shs_wq_hstat_s *hstat_p;
int rc = 0;
u16 bkt;
if (!ep) {
rmnet_shs_crit_err[RMNET_SHS_WQ_EP_ACCESS_ERR]++;
return 0;
}
if (old_cpu >= MAX_CPUS || new_cpu >= MAX_CPUS) {
rmnet_shs_crit_err[RMNET_SHS_WQ_INVALID_CPU_ERR]++;
return 0;
}
hash_for_each(RMNET_SHS_HT, bkt, node_p, list) {
if (!node_p)
continue;
if (!node_p->hstats)
continue;
hstat_p = node_p->hstats;
if (hash_to_move != 0) {
/* If hash_to_move is given, only move that flow,
* otherwise move all the flows on that cpu
*/
if (hstat_p->hash != hash_to_move)
continue;
}
rm_err("SHS_HT: >> sugg cpu %d | old cpu %d | new_cpu %d | "
"map_cpu = %d | flow 0x%x",
hstat_p->suggested_cpu, old_cpu, new_cpu,
node_p->map_cpu, hash_to_move);
if ((hstat_p->suggested_cpu == old_cpu) &&
(node_p->dev == ep->ep)) {
trace_rmnet_shs_wq_high(RMNET_SHS_WQ_FLOW_STATS,
RMNET_SHS_WQ_FLOW_STATS_SUGGEST_NEW_CPU,
hstat_p->hash, hstat_p->suggested_cpu,
new_cpu, 0xDEF, hstat_p, NULL);
node_p->hstats->suggested_cpu = new_cpu;
rmnet_shs_wq_inc_sugg_type(sugg_type, hstat_p);
if (hash_to_move) { /* Stop after moving one flow */
rm_err("SHS_CHNG: moving single flow: flow 0x%x "
"sugg_cpu changed from %d to %d",
hstat_p->hash, old_cpu,
node_p->hstats->suggested_cpu);
return 1;
}
rm_err("SHS_CHNG: moving all flows: flow 0x%x "
"sugg_cpu changed from %d to %d",
hstat_p->hash, old_cpu,
node_p->hstats->suggested_cpu);
rc |= 1;
}
}
return rc;
}
u64 rmnet_shs_wq_get_max_pps_among_cores(u32 core_msk)
{
int cpu_num;
u64 max_pps = 0;
struct rmnet_shs_wq_rx_flow_s *rx_flow_tbl_p = &rmnet_shs_rx_flow_tbl;
for (cpu_num = 0; cpu_num < MAX_CPUS; cpu_num++) {
if (((1 << cpu_num) & core_msk) &&
(rx_flow_tbl_p->cpu_list[cpu_num].rx_pps > max_pps)) {
max_pps = rx_flow_tbl_p->cpu_list[cpu_num].rx_pps;
}
}
return max_pps;
}
/* Returns the least utilized core from a core mask
* In order of priority
* 1) Returns leftmost core with no flows (Fully Idle)
* 2) Returns the core with least flows with no pps (Semi Idle)
* 3) Returns the core with the least pps (Non-Idle)
*/
int rmnet_shs_wq_get_least_utilized_core(u16 core_msk)
{
struct rmnet_shs_wq_rx_flow_s *rx_flow_tbl_p = &rmnet_shs_rx_flow_tbl;
struct rmnet_shs_wq_cpu_rx_pkt_q_s *list_p;
u64 min_pps = U64_MAX;
u32 min_flows = U32_MAX;
int ret_val = -1;
int semi_idle_ret = -1;
int full_idle_ret = -1;
int cpu_num = 0;
u16 is_cpu_in_msk;
for (cpu_num = 0; cpu_num < MAX_CPUS; cpu_num++) {
is_cpu_in_msk = (1 << cpu_num) & core_msk;
if (!is_cpu_in_msk)
continue;
list_p = &rx_flow_tbl_p->cpu_list[cpu_num];
trace_rmnet_shs_wq_low(RMNET_SHS_WQ_CPU_STATS,
RMNET_SHS_WQ_CPU_STATS_CURRENT_UTIL,
cpu_num, list_p->rx_pps, min_pps,
0, NULL, NULL);
/* When there are multiple free CPUs the first free CPU will
* be returned
*/
if (list_p->flows == 0) {
full_idle_ret = cpu_num;
break;
}
/* When there are semi-idle CPUs the CPU w/ least flows will
* be returned
*/
if (list_p->rx_pps == 0 && list_p->flows < min_flows) {
min_flows = list_p->flows;
semi_idle_ret = cpu_num;
}
/* Found a core that is processing even lower packets */
if (list_p->rx_pps <= min_pps) {
min_pps = list_p->rx_pps;
ret_val = cpu_num;
}
}
if (full_idle_ret >= 0)
ret_val = full_idle_ret;
else if (semi_idle_ret >= 0)
ret_val = semi_idle_ret;
return ret_val;
}
u16 rmnet_shs_wq_find_cpu_to_move_flows(u16 current_cpu,
struct rmnet_shs_wq_ep_s *ep)
{
struct rmnet_shs_wq_rx_flow_s *rx_flow_tbl_p = &rmnet_shs_rx_flow_tbl;
struct rmnet_shs_wq_cpu_rx_pkt_q_s *cpu_list_p, *cur_cpu_list_p;
u64 cpu_rx_pps, reqd_pps, cur_cpu_rx_pps;
u64 pps_uthresh, pps_lthresh = 0;
u16 cpu_to_move = current_cpu;
u16 cpu_num;
u8 is_core_in_msk;
u32 cpu_to_move_util = 0;
if (!ep) {
rmnet_shs_crit_err[RMNET_SHS_WQ_EP_ACCESS_ERR]++;
return cpu_to_move;
}
cur_cpu_list_p = &rx_flow_tbl_p->cpu_list[current_cpu];
cur_cpu_rx_pps = cur_cpu_list_p->rx_pps;
pps_uthresh = rmnet_shs_cpu_rx_max_pps_thresh[current_cpu];
pps_lthresh = rmnet_shs_cpu_rx_min_pps_thresh[current_cpu];
/* If we are already on a perf core and required pps is beyond
* beyond the capacity that even perf cores aren't sufficient
* there is nothing much we can do. So we will continue to let flows
* process packets on same perf core
*/
if (!rmnet_shs_is_lpwr_cpu(current_cpu) &&
(cur_cpu_rx_pps > pps_lthresh)) {
return cpu_to_move;
}
/* If a core (should only be lpwr was marked prio we don't touch it
* for a few ticks and reset it afterwards
*/
if (rmnet_shs_cpu_node_tbl[current_cpu].wqprio)
return current_cpu;
for (cpu_num = 0; cpu_num < MAX_CPUS; cpu_num++) {
is_core_in_msk = ((1 << cpu_num) & ep->rps_config_msk);
/* We are looking for a core that is configured and that
* can handle traffic better than the current core
*/
if ((cpu_num == current_cpu) || (!is_core_in_msk) ||
!cpu_online(current_cpu))
continue;
pps_uthresh = rmnet_shs_cpu_rx_max_pps_thresh[cpu_num];
pps_lthresh = rmnet_shs_cpu_rx_min_pps_thresh[cpu_num];
cpu_list_p = &rx_flow_tbl_p->cpu_list[cpu_num];
cpu_rx_pps = cpu_list_p->rx_pps;
reqd_pps = cpu_rx_pps + cur_cpu_rx_pps;
trace_rmnet_shs_wq_low(RMNET_SHS_WQ_CPU_STATS,
RMNET_SHS_WQ_CPU_STATS_CORE2SWITCH_FIND,
current_cpu, cpu_num, reqd_pps,
cpu_rx_pps, NULL, NULL);
/* Return the most available valid CPU */
if ((reqd_pps > pps_lthresh) && (reqd_pps < pps_uthresh) &&
cpu_rx_pps <= cpu_to_move_util) {
cpu_to_move = cpu_num;
cpu_to_move_util = cpu_rx_pps;
}
}
trace_rmnet_shs_wq_high(RMNET_SHS_WQ_CPU_STATS,
RMNET_SHS_WQ_CPU_STATS_CORE2SWITCH_FIND,
current_cpu, cpu_to_move, cur_cpu_rx_pps,
rx_flow_tbl_p->cpu_list[cpu_to_move].rx_pps,
NULL, NULL);
return cpu_to_move;
}
void rmnet_shs_wq_find_cpu_and_move_flows(u16 cur_cpu)
{
struct rmnet_shs_wq_ep_s *ep = NULL;
u16 new_cpu;
list_for_each_entry(ep, &rmnet_shs_wq_ep_tbl, ep_list_id) {
if (!ep)
continue;
if (!ep->is_ep_active)
continue;
new_cpu = rmnet_shs_wq_find_cpu_to_move_flows(cur_cpu, ep);
if (new_cpu != cur_cpu)
rmnet_shs_wq_chng_suggested_cpu(cur_cpu, new_cpu, ep);
}
}
/* Return 1 if we can move a flow to dest_cpu for this endpoint,
* otherwise return 0. Basically check rps mask and cpu is online
* Also check that dest cpu is not isolated
*/
int rmnet_shs_wq_check_cpu_move_for_ep(u16 current_cpu, u16 dest_cpu,
struct rmnet_shs_wq_ep_s *ep)
{
u16 cpu_in_rps_mask = 0;
if (!ep) {
rmnet_shs_crit_err[RMNET_SHS_WQ_EP_ACCESS_ERR]++;
return 0;
}
if (current_cpu >= MAX_CPUS || dest_cpu >= MAX_CPUS) {
rmnet_shs_crit_err[RMNET_SHS_WQ_INVALID_CPU_ERR]++;
return 0;
}
cpu_in_rps_mask = (1 << dest_cpu) & ep->rps_config_msk;
rm_err("SHS_MASK: cur cpu [%d] | dest_cpu [%d] | "
"cpu isolation_mask = 0x%x | ep_rps_mask = 0x%x | "
"cpu_online(dest) = %d cpu_in_rps_mask = %d | "
"cpu isolated(dest) = %d",
current_cpu, dest_cpu, __cpu_isolated_mask, ep->rps_config_msk,
cpu_online(dest_cpu), cpu_in_rps_mask, cpu_isolated(dest_cpu));
/* We cannot move to dest cpu if the cur cpu is the same,
* the dest cpu is offline, dest cpu is not in the rps mask,
* or if the dest cpu is isolated
*/
if (current_cpu == dest_cpu || !cpu_online(dest_cpu) ||
!cpu_in_rps_mask || cpu_isolated(dest_cpu)) {
return 0;
}
return 1;
}
/* rmnet_shs_wq_try_to_move_flow - try to make a flow suggestion
* return 1 if flow move was suggested, otherwise return 0
*/
int rmnet_shs_wq_try_to_move_flow(u16 cur_cpu, u16 dest_cpu, u32 hash_to_move,
u32 sugg_type)
{
unsigned long flags;
struct rmnet_shs_wq_ep_s *ep;
if (cur_cpu >= MAX_CPUS || dest_cpu >= MAX_CPUS) {
rmnet_shs_crit_err[RMNET_SHS_WQ_INVALID_CPU_ERR]++;
return 0;
}
/* Traverse end-point list, check if cpu can be used, based
* on it if is online, rps mask, isolation, etc. then make
* suggestion to change the cpu for the flow by passing its hash
*/
spin_lock_irqsave(&rmnet_shs_ep_lock, flags);
list_for_each_entry(ep, &rmnet_shs_wq_ep_tbl, ep_list_id) {
if (!ep)
continue;
if (!ep->is_ep_active)
continue;
if (!rmnet_shs_wq_check_cpu_move_for_ep(cur_cpu,
dest_cpu,
ep)) {
rm_err("SHS_FDESC: >> Cannot move flow 0x%x on ep"
" from cpu[%d] to cpu[%d]",
hash_to_move, cur_cpu, dest_cpu);
continue;
}
if (rmnet_shs_wq_chng_flow_cpu(cur_cpu, dest_cpu, ep,
hash_to_move, sugg_type)) {
rm_err("SHS_FDESC: >> flow 0x%x was suggested to"
" move from cpu[%d] to cpu[%d] sugg_type [%d]",
hash_to_move, cur_cpu, dest_cpu, sugg_type);
spin_unlock_irqrestore(&rmnet_shs_ep_lock, flags);
return 1;
}
}
spin_unlock_irqrestore(&rmnet_shs_ep_lock, flags);
return 0;
}
/* Change flow segmentation, return 1 if set, 0 otherwise */
int rmnet_shs_wq_set_flow_segmentation(u32 hash_to_set, u8 seg_enable)
{
struct rmnet_shs_skbn_s *node_p;
struct rmnet_shs_wq_hstat_s *hstat_p;
unsigned long ht_flags;
u16 bkt;
spin_lock_irqsave(&rmnet_shs_ht_splock, ht_flags);
hash_for_each(RMNET_SHS_HT, bkt, node_p, list) {
if (!node_p)
continue;
if (!node_p->hstats)
continue;
hstat_p = node_p->hstats;
if (hstat_p->hash != hash_to_set)
continue;
rm_err("SHS_HT: >> segmentation on hash 0x%x enable %u",
hash_to_set, seg_enable);
trace_rmnet_shs_wq_high(RMNET_SHS_WQ_FLOW_STATS,
RMNET_SHS_WQ_FLOW_STATS_SET_FLOW_SEGMENTATION,
hstat_p->hash, seg_enable,
0xDEF, 0xDEF, hstat_p, NULL);
node_p->hstats->segment_enable = seg_enable;
spin_unlock_irqrestore(&rmnet_shs_ht_splock, ht_flags);
return 1;
}
spin_unlock_irqrestore(&rmnet_shs_ht_splock, ht_flags);
rm_err("SHS_HT: >> segmentation on hash 0x%x enable %u not set - hash not found",
hash_to_set, seg_enable);
return 0;
}
/* Comparison function to sort gold flow loads - based on flow avg_pps
* return -1 if a is before b, 1 if a is after b, 0 if equal
*/
int cmp_fn_flow_pps(void *priv, struct list_head *a, struct list_head *b)
{
struct rmnet_shs_wq_gold_flow_s *flow_a;
struct rmnet_shs_wq_gold_flow_s *flow_b;
if (!a || !b)
return 0;
flow_a = list_entry(a, struct rmnet_shs_wq_gold_flow_s, gflow_list);
flow_b = list_entry(b, struct rmnet_shs_wq_gold_flow_s, gflow_list);
if (flow_a->avg_pps > flow_b->avg_pps)
return -1;
else if (flow_a->avg_pps < flow_b->avg_pps)
return 1;
return 0;
}
/* Comparison function to sort cpu capacities - based on cpu avg_pps capacity
* return -1 if a is before b, 1 if a is after b, 0 if equal
*/
int cmp_fn_cpu_pps(void *priv, struct list_head *a, struct list_head *b)
{
struct rmnet_shs_wq_cpu_cap_s *cpu_a;
struct rmnet_shs_wq_cpu_cap_s *cpu_b;
if (!a || !b)
return 0;
cpu_a = list_entry(a, struct rmnet_shs_wq_cpu_cap_s, cpu_cap_list);
cpu_b = list_entry(b, struct rmnet_shs_wq_cpu_cap_s, cpu_cap_list);
if (cpu_a->avg_pps_capacity > cpu_b->avg_pps_capacity)
return -1;
else if (cpu_a->avg_pps_capacity < cpu_b->avg_pps_capacity)
return 1;
return 0;
}
/* Prints cpu stats and flows to dmesg for debugging */
void rmnet_shs_wq_debug_print_flows(void)
{
struct rmnet_shs_wq_rx_flow_s *rx_flow_tbl_p = &rmnet_shs_rx_flow_tbl;
struct rmnet_shs_wq_cpu_rx_pkt_q_s *cpu_node;
struct rmnet_shs_wq_hstat_s *hnode;
int flows, i;
u16 cpu_num = 0;
if (!RMNET_SHS_DEBUG)
return;
for (cpu_num = 0; cpu_num < MAX_CPUS; cpu_num++) {
cpu_node = &rx_flow_tbl_p->cpu_list[cpu_num];
flows = rx_flow_tbl_p->cpu_list[cpu_num].flows;
rm_err("SHS_CPU: cpu[%d]: flows=%d pps=%llu bps=%llu "
"qhead_diff %u qhead_total = %u qhead_start = %u "
"qhead = %u qhead_last = %u isolated = %d ",
cpu_num, flows, cpu_node->rx_pps, cpu_node->rx_bps,
cpu_node->qhead_diff, cpu_node->qhead_total,
cpu_node->qhead_start,
cpu_node->qhead, cpu_node->last_qhead,
cpu_isolated(cpu_num));
list_for_each_entry(hnode,
&rmnet_shs_wq_hstat_tbl,
hstat_node_id) {
if (!hnode)
continue;
if (hnode->in_use == 0)
continue;
if (hnode->node) {
if (hnode->current_cpu == cpu_num)
rm_err("SHS_CPU: > flow 0x%x "
"with pps %llu avg_pps %llu rx_bps %llu ",
hnode->hash, hnode->rx_pps,
hnode->avg_pps, hnode->rx_bps);
}
} /* loop per flow */
for (i = 0; i < 3 - flows; i++) {
rm_err("%s", "SHS_CPU: > ");
}
} /* loop per cpu */
}
/* Prints the sorted gold flow list to dmesg */
void rmnet_shs_wq_debug_print_sorted_gold_flows(struct list_head *gold_flows)
{
struct rmnet_shs_wq_gold_flow_s *gflow_node;
if (!RMNET_SHS_DEBUG)
return;
if (!gold_flows) {
rm_err("%s", "SHS_GDMA: Gold Flows List is NULL");
return;
}
rm_err("%s", "SHS_GDMA: List of sorted gold flows:");
list_for_each_entry(gflow_node, gold_flows, gflow_list) {
if (!gflow_node)
continue;
rm_err("SHS_GDMA: > flow 0x%x with pps %llu on cpu[%d]",
gflow_node->hash, gflow_node->rx_pps,
gflow_node->cpu_num);
}
}
/* Userspace evaluation. we send userspace the response to the sync message
* after we update shared memory. shsusr will send a netlink message if
* flows should be moved around.
*/
void rmnet_shs_wq_eval_cpus_caps_and_flows(struct list_head *cpu_caps,
struct list_head *gold_flows,
struct list_head *ss_flows)
{
if (!cpu_caps || !gold_flows || !ss_flows) {
rmnet_shs_crit_err[RMNET_SHS_WQ_INVALID_PTR_ERR]++;
return;
}
list_sort(NULL, cpu_caps, &cmp_fn_cpu_pps);
list_sort(NULL, gold_flows, &cmp_fn_flow_pps);
rmnet_shs_wq_mem_update_cached_cpu_caps(cpu_caps);
rmnet_shs_wq_mem_update_cached_sorted_gold_flows(gold_flows);
rmnet_shs_wq_mem_update_cached_sorted_ss_flows(ss_flows);
rmnet_shs_wq_mem_update_cached_netdevs();
rmnet_shs_genl_send_int_to_userspace_no_info(RMNET_SHS_SYNC_RESP_INT);
trace_rmnet_shs_wq_high(RMNET_SHS_WQ_SHSUSR, RMNET_SHS_WQ_SHSUSR_SYNC_END,
0xDEF, 0xDEF, 0xDEF, 0xDEF, NULL, NULL);
}
/* Default wq evaluation logic, use this if rmnet_shs_userspace_connected is 0 */
void rmnet_shs_wq_eval_suggested_cpu(void)
{
struct rmnet_shs_wq_rx_flow_s *rx_flow_tbl_p = &rmnet_shs_rx_flow_tbl;
struct rmnet_shs_wq_cpu_rx_pkt_q_s *cpu_list_p;
u64 cpu_curr_pps, cpu_last_pps, last_avg_pps;
u64 moving_avg_pps, avg_pps;
u64 pps_uthresh, pps_lthresh = 0;
u16 cpu_num, new_weight, old_weight;
int flows;
for (cpu_num = 0; cpu_num < MAX_CPUS; cpu_num++) {
flows = rx_flow_tbl_p->cpu_list[cpu_num].flows;
/* Nothing to evaluate if there is no traffic on this cpu */
if (flows <= 0)
continue;
cpu_list_p = &rx_flow_tbl_p->cpu_list[cpu_num];
cpu_curr_pps = cpu_list_p->rx_pps;
cpu_last_pps = cpu_list_p->last_rx_pps;
last_avg_pps = cpu_list_p->avg_pps;
pps_uthresh = rmnet_shs_cpu_rx_max_pps_thresh[cpu_num];
pps_lthresh = rmnet_shs_cpu_rx_min_pps_thresh[cpu_num];
/* Often when we decide to switch from a small cluster core,
* it is because of the heavy traffic on that core. In such
* circumstances, we want to switch to a big cluster
* core as soon as possible. Therefore, we will provide a
* greater weightage to the most recent sample compared to
* the previous samples.
*
* On the other hand, when a flow which is on a big cluster
* cpu suddenly starts to receive low traffic we move to a
* small cluster core after observing low traffic for some
* more samples. This approach avoids switching back and forth
* to small cluster cpus due to momentary decrease in data
* traffic.
*/
if (rmnet_shs_is_lpwr_cpu(cpu_num)) {
new_weight = rmnet_shs_wq_tuning;
old_weight = 100 - rmnet_shs_wq_tuning;
} else {
old_weight = rmnet_shs_wq_tuning;
new_weight = 100 - rmnet_shs_wq_tuning;
}
/*computing weighted average*/
moving_avg_pps = (cpu_last_pps + last_avg_pps) / 2;
avg_pps = ((new_weight * cpu_curr_pps) +
(old_weight * moving_avg_pps)) /
(new_weight + old_weight);
cpu_list_p->avg_pps = avg_pps;
trace_rmnet_shs_wq_high(RMNET_SHS_WQ_CPU_STATS,
RMNET_SHS_WQ_CPU_STATS_CORE2SWITCH_EVAL_CPU,
cpu_num, cpu_curr_pps, cpu_last_pps,
avg_pps, NULL, NULL);
if ((avg_pps > pps_uthresh) ||
((avg_pps < pps_lthresh) && (cpu_curr_pps < pps_lthresh)))
rmnet_shs_wq_find_cpu_and_move_flows(cpu_num);
}
}
void rmnet_shs_wq_refresh_new_flow_list_per_ep(struct rmnet_shs_wq_ep_s *ep)
{
int lo_core;
int hi_core;
u16 rps_msk;
u16 lo_msk;
u16 hi_msk;
u8 lo_core_idx = 0;
u8 hi_core_idx = 0;
if (!ep) {
rmnet_shs_crit_err[RMNET_SHS_WQ_EP_ACCESS_ERR]++;
return;
}
rps_msk = ep->rps_config_msk;
lo_msk = ep->default_core_msk;
hi_msk = ep->pri_core_msk;
memset(ep->new_lo_core, -1, sizeof(*ep->new_lo_core) * MAX_CPUS);
memset(ep->new_hi_core, -1, sizeof(*ep->new_hi_core) * MAX_CPUS);
do {
lo_core = rmnet_shs_wq_get_least_utilized_core(lo_msk);
if (lo_core >= 0) {
ep->new_lo_core[lo_core_idx] = lo_core;
lo_msk = lo_msk & ~(1 << lo_core);
lo_core_idx++;
} else {
break;
}
} while (lo_msk != 0);
trace_rmnet_shs_wq_low(RMNET_SHS_WQ_CPU_STATS,
RMNET_SHS_WQ_CPU_STATS_NEW_FLOW_LIST_LO,
ep->new_lo_core[0], ep->new_lo_core[1],
ep->new_lo_core[2], ep->new_lo_max,
ep, NULL);
do {
hi_core = rmnet_shs_wq_get_least_utilized_core(hi_msk);
if (hi_core >= 0) {
ep->new_hi_core[hi_core_idx] = hi_core;
hi_msk = hi_msk & ~(1 << hi_core);
hi_core_idx++;
} else
break;
} while (hi_msk != 0);
ep->new_lo_max = lo_core_idx;
ep->new_hi_max = hi_core_idx;
ep->new_lo_idx = 0;
ep->new_hi_idx = 0;
trace_rmnet_shs_wq_low(RMNET_SHS_WQ_CPU_STATS,
RMNET_SHS_WQ_CPU_STATS_NEW_FLOW_LIST_HI,
ep->new_hi_core[0], ep->new_hi_core[1],
ep->new_hi_core[2], ep->new_hi_max,
ep, NULL);
return;
}
void rmnet_shs_wq_refresh_new_flow_list(void)
{
struct rmnet_shs_wq_ep_s *ep = NULL;
list_for_each_entry(ep, &rmnet_shs_wq_ep_tbl, ep_list_id) {
if (!ep)
continue;
if (!ep->is_ep_active)
continue;
rmnet_shs_wq_refresh_new_flow_list_per_ep(ep);
}
}
/* Return Invalid core if only pri core available*/
int rmnet_shs_wq_get_lpwr_cpu_new_flow(struct net_device *dev)
{
u8 lo_idx;
u8 lo_max;
int cpu_assigned = -1;
u8 is_match_found = 0;
struct rmnet_shs_wq_ep_s *ep = NULL;
unsigned long flags;
if (!dev) {
rmnet_shs_crit_err[RMNET_SHS_NETDEV_ERR]++;
return cpu_assigned;
}
spin_lock_irqsave(&rmnet_shs_ep_lock, flags);
list_for_each_entry(ep, &rmnet_shs_wq_ep_tbl, ep_list_id) {
if (!ep)
continue;
if (!ep->is_ep_active)
continue;
if (ep->ep == dev) {
is_match_found = 1;
break;
}
}
if (!is_match_found) {
rmnet_shs_crit_err[RMNET_SHS_WQ_EP_ACCESS_ERR]++;
spin_unlock_irqrestore(&rmnet_shs_ep_lock, flags);
return cpu_assigned;
}
lo_idx = ep->new_lo_idx;
lo_max = ep->new_lo_max;
while (lo_idx < lo_max) {
if (ep->new_lo_core[lo_idx] >= 0) {
cpu_assigned = ep->new_lo_core[lo_idx];
break;
}
lo_idx++;
}
/* Increment CPU assignment idx to be ready for next flow assignment*/
if ((cpu_assigned >= 0) || ((ep->new_lo_idx + 1) >= ep->new_lo_max))
ep->new_lo_idx = ((ep->new_lo_idx + 1) % ep->new_lo_max);
spin_unlock_irqrestore(&rmnet_shs_ep_lock, flags);
return cpu_assigned;
}
int rmnet_shs_wq_get_perf_cpu_new_flow(struct net_device *dev)
{
struct rmnet_shs_wq_ep_s *ep = NULL;
int cpu_assigned = -1;
u8 hi_idx;
u8 hi_max;
u8 is_match_found = 0;
unsigned long flags;
if (!dev) {
rmnet_shs_crit_err[RMNET_SHS_NETDEV_ERR]++;
return cpu_assigned;
}
spin_lock_irqsave(&rmnet_shs_ep_lock, flags);
list_for_each_entry(ep, &rmnet_shs_wq_ep_tbl, ep_list_id) {
if (!ep)
continue;
if (!ep->is_ep_active)
continue;
if (ep->ep == dev) {
is_match_found = 1;
break;
}
}
if (!is_match_found) {
rmnet_shs_crit_err[RMNET_SHS_WQ_EP_ACCESS_ERR]++;
spin_unlock_irqrestore(&rmnet_shs_ep_lock, flags);
return cpu_assigned;
}
hi_idx = ep->new_hi_idx;
hi_max = ep->new_hi_max;
while (hi_idx < hi_max) {
if (ep->new_hi_core[hi_idx] >= 0) {
cpu_assigned = ep->new_hi_core[hi_idx];
break;
}
hi_idx++;
}
/* Increment CPU assignment idx to be ready for next flow assignment*/
if (cpu_assigned >= 0)
ep->new_hi_idx = ((hi_idx + 1) % hi_max);
spin_unlock_irqrestore(&rmnet_shs_ep_lock, flags);
return cpu_assigned;
}
static int rmnet_shs_wq_time_check(time_t time, int num_flows)
{
int ret = false;
if (time > rmnet_shs_max_flow_inactivity_sec)
ret = true;
else if (num_flows > FLOW_LIMIT2 && time > INACTIVE_TSEC2)
ret = true;
else if (num_flows > FLOW_LIMIT1 && time > INACTIVE_TSEC1)
ret = true;
return ret;
}
void rmnet_shs_wq_cleanup_hash_tbl(u8 force_clean)
{
struct rmnet_shs_skbn_s *node_p = NULL;
time_t tns2s;
unsigned long ht_flags;
struct rmnet_shs_wq_hstat_s *hnode = NULL;
struct list_head *ptr = NULL, *next = NULL;
list_for_each_safe(ptr, next, &rmnet_shs_wq_hstat_tbl) {
hnode = list_entry(ptr,
struct rmnet_shs_wq_hstat_s, hstat_node_id);
if (hnode == NULL)
continue;
if (hnode->node == NULL)
continue;
node_p = hnode->node;
tns2s = RMNET_SHS_NSEC_TO_SEC(hnode->inactive_duration);
/* Flows are cleanup from book keeping faster if
* there are a lot of active flows already in memory
*/
if (rmnet_shs_wq_time_check(tns2s, rmnet_shs_cfg.num_flows) ||
force_clean) {
trace_rmnet_shs_wq_low(RMNET_SHS_WQ_FLOW_STATS,
RMNET_SHS_WQ_FLOW_STATS_FLOW_INACTIVE_TIMEOUT,
node_p->hash, tns2s, 0xDEF, 0xDEF, node_p, hnode);
spin_lock_irqsave(&rmnet_shs_ht_splock, ht_flags);
rmnet_shs_clear_node(node_p, RMNET_WQ_CTXT);
rmnet_shs_wq_dec_cpu_flow(hnode->current_cpu);
if (node_p) {
rmnet_shs_cpu_node_remove(node_p);
hash_del_rcu(&node_p->list);
kfree(node_p);
}
rm_err("SHS_FLOW: removing flow 0x%x on cpu[%d] "
"pps: %llu avg_pps: %llu",
hnode->hash, hnode->current_cpu,
hnode->rx_pps, hnode->avg_pps);
rmnet_shs_wq_cpu_list_remove(hnode);
if (hnode->is_perm == 0 || force_clean) {
rmnet_shs_wq_hstat_tbl_remove(hnode);
kfree(hnode);
} else {
rmnet_shs_wq_hstat_reset_node(hnode);
}
rmnet_shs_cfg.num_flows--;
spin_unlock_irqrestore(&rmnet_shs_ht_splock, ht_flags);
}
}
}
void rmnet_shs_wq_update_ep_rps_msk(struct rmnet_shs_wq_ep_s *ep)
{
struct rps_map *map;
u8 len = 0;
if (!ep || !ep->ep ) {
rmnet_shs_crit_err[RMNET_SHS_WQ_EP_ACCESS_ERR]++;
return;
}
rcu_read_lock();
if (!ep->ep) {
pr_info(" rmnet_shs invalid state %p", ep->ep);
rmnet_shs_crit_err[RMNET_SHS_WQ_EP_ACCESS_ERR]++;
return;
}
map = rcu_dereference(ep->ep->_rx->rps_map);
ep->rps_config_msk = 0;
if (map != NULL) {
for (len = 0; len < map->len; len++)
ep->rps_config_msk |= (1 << map->cpus[len]);
}
rcu_read_unlock();
ep->default_core_msk = ep->rps_config_msk & 0x0F;
ep->pri_core_msk = ep->rps_config_msk & 0xF0;
}
void rmnet_shs_wq_reset_ep_active(struct net_device *dev)
{
struct rmnet_shs_wq_ep_s *ep = NULL;
struct rmnet_shs_wq_ep_s *tmp = NULL;
unsigned long flags;
if (!dev) {
rmnet_shs_crit_err[RMNET_SHS_NETDEV_ERR]++;
return;
}
spin_lock_irqsave(&rmnet_shs_ep_lock, flags);
list_for_each_entry_safe(ep, tmp, &rmnet_shs_wq_ep_tbl, ep_list_id) {
if (!ep)
continue;
if (ep->ep == dev){
ep->is_ep_active = 0;
rmnet_shs_wq_ep_tbl_remove(ep);
kfree(ep);
break;
}
}
spin_unlock_irqrestore(&rmnet_shs_ep_lock, flags);
}
void rmnet_shs_wq_set_ep_active(struct net_device *dev)
{
struct rmnet_shs_wq_ep_s *ep = NULL;
unsigned long flags;
if (!dev) {
rmnet_shs_crit_err[RMNET_SHS_NETDEV_ERR]++;
return;
}
spin_lock_irqsave(&rmnet_shs_ep_lock, flags);
ep = kzalloc(sizeof(*ep), GFP_ATOMIC);
if (!ep) {
rmnet_shs_crit_err[RMNET_SHS_WQ_ALLOC_EP_TBL_ERR]++;
spin_unlock_irqrestore(&rmnet_shs_ep_lock, flags);
return;
}
ep->ep = dev;
ep->is_ep_active = 1;
INIT_LIST_HEAD(&ep->ep_list_id);
rmnet_shs_wq_update_ep_rps_msk(ep);
rmnet_shs_wq_ep_tbl_add(ep);
spin_unlock_irqrestore(&rmnet_shs_ep_lock, flags);
}
void rmnet_shs_wq_refresh_ep_masks(void)
{
struct rmnet_shs_wq_ep_s *ep = NULL;
list_for_each_entry(ep, &rmnet_shs_wq_ep_tbl, ep_list_id) {
if (!ep)
continue;
if (!ep->is_ep_active)
continue;
rmnet_shs_wq_update_ep_rps_msk(ep);
/* These tput totals get re-added as we go through each flow */
ep->udp_rx_bps = 0;
ep->tcp_rx_bps = 0;
}
}
void rmnet_shs_update_cfg_mask(void)
{
/* Start with most avaible mask all eps could share*/
u8 mask = UPDATE_MASK;
u8 rps_enabled = 0;
struct rmnet_shs_wq_ep_s *ep;
list_for_each_entry(ep, &rmnet_shs_wq_ep_tbl, ep_list_id) {
if (!ep->is_ep_active)
continue;
/* Bitwise and to get common mask from non-null masks.
* VNDs with different mask will have UNDEFINED behavior
*/
if (ep->rps_config_msk) {
mask &= ep->rps_config_msk;
rps_enabled = 1;
}
}
if (!rps_enabled) {
rmnet_shs_cfg.map_mask = 0;
rmnet_shs_cfg.map_len = 0;
return;
} else if (rmnet_shs_cfg.map_mask != mask) {
rmnet_shs_cfg.map_mask = mask;
rmnet_shs_cfg.map_len = rmnet_shs_get_mask_len(mask);
}
}
void rmnet_shs_wq_filter(void)
{
int cpu, cur_cpu;
int temp;
struct rmnet_shs_wq_hstat_s *hnode = NULL;
for (cpu = 0; cpu < MAX_CPUS; cpu++) {
rmnet_shs_cpu_rx_filter_flows[cpu] = 0;
rmnet_shs_cpu_node_tbl[cpu].seg = 0;
}
/* Filter out flows with low pkt count and
* mark CPUS with slowstart flows
*/
list_for_each_entry(hnode, &rmnet_shs_wq_hstat_tbl, hstat_node_id) {
if (hnode->in_use == 0)
continue;
if (hnode->avg_pps > RMNET_SHS_FILTER_FLOW_RATE &&
hnode->rx_skb > RMNET_SHS_FILTER_PKT_LIMIT)
if (hnode->current_cpu < MAX_CPUS){
temp = hnode->current_cpu;
rmnet_shs_cpu_rx_filter_flows[temp]++;
}
cur_cpu = hnode->current_cpu;
if (cur_cpu >= MAX_CPUS) {
continue;
}
if (hnode->node->hstats->segment_enable) {
rmnet_shs_cpu_node_tbl[cur_cpu].seg++;
}
}
}
void rmnet_shs_wq_update_stats(void)
{
struct timespec time;
struct rmnet_shs_wq_hstat_s *hnode = NULL;
(void) getnstimeofday(&time);
rmnet_shs_wq_tnsec = RMNET_SHS_SEC_TO_NSEC(time.tv_sec) + time.tv_nsec;
rmnet_shs_wq_refresh_ep_masks();
rmnet_shs_update_cfg_mask();
list_for_each_entry(hnode, &rmnet_shs_wq_hstat_tbl, hstat_node_id) {
if (!hnode)
continue;
if (hnode->in_use == 0)
continue;
if (hnode->node) {
rmnet_shs_wq_update_hash_stats(hnode);
rmnet_shs_wq_update_cpu_rx_tbl(hnode);
if (rmnet_shs_userspace_connected) {
if (!rmnet_shs_is_lpwr_cpu(hnode->current_cpu)) {
/* Add golds flows to list */
rmnet_shs_wq_gflow_list_add(hnode, &gflows);
}
if (hnode->skb_tport_proto == IPPROTO_TCP) {
rmnet_shs_wq_ssflow_list_add(hnode, &ssflows);
}
} else {
/* Disable segmentation if userspace gets disconnected connected */
hnode->node->hstats->segment_enable = 0;
}
}
}
rmnet_shs_wq_refresh_all_cpu_stats();
rmnet_shs_wq_refresh_total_stats();
rmnet_shs_wq_refresh_dl_mrkr_stats();
if (rmnet_shs_userspace_connected) {
rm_err("%s", "SHS_UPDATE: Userspace connected, relying on userspace evaluation");
rmnet_shs_wq_eval_cpus_caps_and_flows(&cpu_caps, &gflows, &ssflows);
rmnet_shs_wq_cleanup_gold_flow_list(&gflows);
rmnet_shs_wq_cleanup_ss_flow_list(&ssflows);
rmnet_shs_wq_cleanup_cpu_caps_list(&cpu_caps);
} else {
rm_err("%s", "SHS_UPDATE: shs userspace not connected, using default logic");
rmnet_shs_wq_eval_suggested_cpu();
}
rmnet_shs_wq_refresh_new_flow_list();
rmnet_shs_wq_filter();
}
void rmnet_shs_wq_process_wq(struct work_struct *work)
{
unsigned long flags;
unsigned long jiffies;
trace_rmnet_shs_wq_high(RMNET_SHS_WQ_PROCESS_WQ,
RMNET_SHS_WQ_PROCESS_WQ_START,
0xDEF, 0xDEF, 0xDEF, 0xDEF, NULL, NULL);
spin_lock_irqsave(&rmnet_shs_ep_lock, flags);
rmnet_shs_wq_update_stats();
spin_unlock_irqrestore(&rmnet_shs_ep_lock, flags);
/*Invoke after both the locks are released*/
rmnet_shs_wq_cleanup_hash_tbl(PERIODIC_CLEAN);
rmnet_shs_wq_debug_print_flows();
<<<<<<< HEAD
=======
jiffies = msecs_to_jiffies(rmnet_shs_wq_interval_ms);
>>>>>>> LA.UM.9.1.R1.10.00.00.604.038
queue_delayed_work(rmnet_shs_wq, &rmnet_shs_delayed_wq->wq,
jiffies);
trace_rmnet_shs_wq_high(RMNET_SHS_WQ_PROCESS_WQ,
RMNET_SHS_WQ_PROCESS_WQ_END,
0xDEF, 0xDEF, 0xDEF, 0xDEF, NULL, NULL);
}
void rmnet_shs_wq_clean_ep_tbl(void)
{
struct rmnet_shs_wq_ep_s *ep = NULL;
struct list_head *ptr = NULL, *next = NULL;
list_for_each_safe(ptr, next, &rmnet_shs_wq_ep_tbl) {
ep = list_entry(ptr, struct rmnet_shs_wq_ep_s, ep_list_id);
if (!ep)
continue;
trace_rmnet_shs_wq_high(RMNET_SHS_WQ_EP_TBL,
RMNET_SHS_WQ_EP_TBL_CLEANUP,
0xDEF, 0xDEF, 0xDEF, 0xDEF, ep, NULL);
rmnet_shs_wq_ep_tbl_remove(ep);
kfree(ep);
}
}
void rmnet_shs_wq_exit(void)
{
/*If Wq is not initialized, nothing to cleanup */
if (!rmnet_shs_wq || !rmnet_shs_delayed_wq)
return;
rmnet_shs_wq_mem_deinit();
rmnet_shs_genl_send_int_to_userspace_no_info(RMNET_SHS_SYNC_WQ_EXIT);
trace_rmnet_shs_wq_high(RMNET_SHS_WQ_EXIT, RMNET_SHS_WQ_EXIT_START,
0xDEF, 0xDEF, 0xDEF, 0xDEF, NULL, NULL);
cancel_delayed_work_sync(&rmnet_shs_delayed_wq->wq);
drain_workqueue(rmnet_shs_wq);
destroy_workqueue(rmnet_shs_wq);
kfree(rmnet_shs_delayed_wq);
rmnet_shs_delayed_wq = NULL;
rmnet_shs_wq = NULL;
rmnet_shs_wq_cleanup_hash_tbl(FORCE_CLEAN);
rmnet_shs_wq_clean_ep_tbl();
trace_rmnet_shs_wq_high(RMNET_SHS_WQ_EXIT, RMNET_SHS_WQ_EXIT_END,
0xDEF, 0xDEF, 0xDEF, 0xDEF, NULL, NULL);
}
void rmnet_shs_wq_init_cpu_rx_flow_tbl(void)
{
u8 cpu_num;
struct rmnet_shs_wq_cpu_rx_pkt_q_s *rx_flow_tbl_p;
for (cpu_num = 0; cpu_num < MAX_CPUS; cpu_num++) {
trace_rmnet_shs_wq_high(RMNET_SHS_WQ_CPU_HSTAT_TBL,
RMNET_SHS_WQ_CPU_HSTAT_TBL_INIT,
cpu_num, 0xDEF, 0xDEF, 0xDEF,
NULL, NULL);
rx_flow_tbl_p = &rmnet_shs_rx_flow_tbl.cpu_list[cpu_num];
INIT_LIST_HEAD(&rx_flow_tbl_p->hstat_id);
rx_flow_tbl_p->cpu_num = cpu_num;
}
}
void rmnet_shs_wq_pause(void)
{
int cpu;
if (rmnet_shs_wq && rmnet_shs_delayed_wq)
cancel_delayed_work_sync(&rmnet_shs_delayed_wq->wq);
for (cpu = 0; cpu < MAX_CPUS; cpu++)
rmnet_shs_cpu_rx_filter_flows[cpu] = 0;
}
void rmnet_shs_wq_restart(void)
{
if (rmnet_shs_wq && rmnet_shs_delayed_wq)
queue_delayed_work(rmnet_shs_wq, &rmnet_shs_delayed_wq->wq, 0);
}
void rmnet_shs_wq_init(struct net_device *dev)
{
/*If the workqueue is already initialized we should not be
*initializing again
*/
if (rmnet_shs_wq)
return;
if (!dev) {
rmnet_shs_crit_err[RMNET_SHS_NETDEV_ERR]++;
return;
}
rmnet_shs_wq_mem_init();
trace_rmnet_shs_wq_high(RMNET_SHS_WQ_INIT, RMNET_SHS_WQ_INIT_START,
0xDEF, 0xDEF, 0xDEF, 0xDEF, NULL, NULL);
rmnet_shs_wq = alloc_workqueue("rmnet_shs_wq",
WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE, 1);
if (!rmnet_shs_wq) {
rmnet_shs_crit_err[RMNET_SHS_WQ_ALLOC_WQ_ERR]++;
return;
}
rmnet_shs_delayed_wq = kmalloc(sizeof(struct rmnet_shs_delay_wq_s),
GFP_ATOMIC);
if (!rmnet_shs_delayed_wq) {
rmnet_shs_crit_err[RMNET_SHS_WQ_ALLOC_DEL_WQ_ERR]++;
rmnet_shs_wq_exit();
return;
}
/*All hstat nodes allocated during Wq init will be held for ever*/
rmnet_shs_wq_hstat_alloc_nodes(RMNET_SHS_MIN_HSTAT_NODES_REQD, 1);
rmnet_shs_wq_init_cpu_rx_flow_tbl();
INIT_DEFERRABLE_WORK(&rmnet_shs_delayed_wq->wq,
rmnet_shs_wq_process_wq);
trace_rmnet_shs_wq_high(RMNET_SHS_WQ_INIT, RMNET_SHS_WQ_INIT_END,
0xDEF, 0xDEF, 0xDEF, 0xDEF, NULL, NULL);
}
int rmnet_shs_wq_get_num_cpu_flows(u16 cpu)
{
int flows = -1;
if (cpu >= MAX_CPUS) {
rmnet_shs_crit_err[RMNET_SHS_INVALID_CPU_ERR]++;
return flows;
}
flows = rmnet_shs_rx_flow_tbl.cpu_list[cpu].flows;
trace_rmnet_shs_wq_low(RMNET_SHS_WQ_CPU_STATS,
RMNET_SHS_WQ_CPU_STATS_GET_CPU_FLOW,
cpu, flows, 0xDEF, 0xDEF, NULL, NULL);
return flows;
}
int rmnet_shs_wq_get_max_flows_per_core(void)
{
u16 cpu;
int max_flows = -1;
int cpu_flows;
for (cpu = 0; cpu < MAX_CPUS; cpu++) {
cpu_flows = rmnet_shs_wq_get_num_cpu_flows(cpu);
if (cpu_flows > max_flows)
max_flows = cpu_flows;
trace_rmnet_shs_wq_low(RMNET_SHS_WQ_CPU_STATS,
RMNET_SHS_WQ_CPU_STATS_GET_MAX_CPU_FLOW,
cpu, cpu_flows, max_flows, 0xDEF, NULL, NULL);
}
return max_flows;
}
int rmnet_shs_wq_get_max_flows_per_cluster(u16 cpu)
{
u32 big_cluster_mask = 1<<4;
u32 core_mask = 1;
u16 start_core = 0;
u16 end_core = 4;
int max_flows = -1;
int cpu_flows;
if (cpu > MAX_CPUS) {
rmnet_shs_crit_err[RMNET_SHS_INVALID_CPU_ERR]++;
return max_flows;
}
core_mask <<= cpu;
if (core_mask >= big_cluster_mask) {
start_core = 4;
end_core = MAX_CPUS;
}
for (start_core; start_core < end_core; start_core++) {
cpu_flows = rmnet_shs_wq_get_num_cpu_flows(start_core);
if (cpu_flows > max_flows)
max_flows = cpu_flows;
}
trace_rmnet_shs_wq_low(RMNET_SHS_WQ_CPU_STATS,
RMNET_SHS_WQ_CPU_STATS_MAX_FLOW_IN_CLUSTER,
start_core, end_core, cpu, max_flows,
NULL, NULL);
return max_flows;
}
void rmnet_shs_wq_inc_cpu_flow(u16 cpu)
{
if (cpu >= MAX_CPUS) {
rmnet_shs_crit_err[RMNET_SHS_WQ_INVALID_CPU_ERR]++;
return;
}
rmnet_shs_rx_flow_tbl.cpu_list[cpu].flows++;
trace_rmnet_shs_wq_low(RMNET_SHS_WQ_CPU_STATS,
RMNET_SHS_WQ_CPU_STATS_INC_CPU_FLOW,
cpu, rmnet_shs_rx_flow_tbl.cpu_list[cpu].flows,
0xDEF, 0xDEF, NULL, NULL);
}
void rmnet_shs_wq_dec_cpu_flow(u16 cpu)
{
if (cpu >= MAX_CPUS) {
rmnet_shs_crit_err[RMNET_SHS_WQ_INVALID_CPU_ERR]++;
return;
}
if (rmnet_shs_rx_flow_tbl.cpu_list[cpu].flows > 0)
rmnet_shs_rx_flow_tbl.cpu_list[cpu].flows--;
trace_rmnet_shs_wq_low(RMNET_SHS_WQ_CPU_STATS,
RMNET_SHS_WQ_CPU_STATS_DEC_CPU_FLOW,
cpu, rmnet_shs_rx_flow_tbl.cpu_list[cpu].flows,
0xDEF, 0xDEF, NULL, NULL);
}
u64 rmnet_shs_wq_get_max_allowed_pps(u16 cpu)
{
if (cpu >= MAX_CPUS) {
rmnet_shs_crit_err[RMNET_SHS_WQ_INVALID_CPU_ERR]++;
return 0;
}
return rmnet_shs_cpu_rx_max_pps_thresh[cpu];
}
void rmnet_shs_wq_ep_lock_bh(void)
{
spin_lock_bh(&rmnet_shs_ep_lock);
}
void rmnet_shs_wq_ep_unlock_bh(void)
{
spin_unlock_bh(&rmnet_shs_ep_lock);
}