blob: 3348d892df88d72870768af0496f42e4a3531362 [file] [log] [blame]
/*
* Source file for DHD QOS on Socket Flow.
*
* Defines a socket flow and maintains a table of socket flows
* for further analysis in order to upgrade the QOS of the flow.
* Copyright (C) 2020, Broadcom.
*
* Unless you and Broadcom execute a separate written software license
* agreement governing use of this software, this software is licensed to you
* under the terms of the GNU General Public License version 2 (the "GPL"),
* available at http://www.broadcom.com/licenses/GPLv2.php, with the
* following added to such license:
*
* As a special exception, the copyright holders of this software give you
* permission to link this software with independent modules, and to copy and
* distribute the resulting executable under terms of your choice, provided that
* you also meet, for each linked independent module, the terms and conditions of
* the license of that module. An independent module is a module which is not
* derived from this software. The special exception does not apply to any
* modifications of the software.
*
*
* <<Broadcom-WL-IPTag/Open:>>
*
* $Id$
*
*/
#include <dhd_linux_priv.h>
#include <dhd_dbg.h>
#include <bcmstdlib_s.h>
#include <bcmendian.h>
#include <dhd_linux_sock_qos.h>
#include <dhd_qos_algo.h>
#include <dhd.h>
#include <net/sock.h>
#include <linux/sock_diag.h>
#include <linux/netlink.h>
#include <linux/list.h>
#include <linux/sched.h>
#include <linux/math64.h>
#include <linux/pkt_sched.h>
#include <linux_pkt.h>
#include <net/tcp.h>
/* Maximum number of Socket Flows supported */
#define MAX_SOCK_FLOW (1024UL)
#define SOCK_FLOW_UPGRADE_THRESHOLD (3)
/*
* Mark a Socket Flow as inactive and free the resources
* if there is no packet receied for SOCK_IDLE_THREASHOLD_MS
* of time. Note that this parameter is in milli seconds.
*/
#define SOCK_IDLE_THRESHOLD_MS (2000UL)
#define DSCP_TOS_CS7 0XE0u
extern uint dhd_watchdog_ms;
/* Defines Socket Flow */
struct dhd_sock_flow_info
{
/* Unique identifiers */
struct sock *sk;
unsigned long ino;
/* statistics */
qos_stat_t stats;
u64 last_pkt_ns;
kuid_t uid;
/* Elements related to upgrade management */
/* 0 - No upgrade
* 1 - Upgrade
*/
unsigned int cur_up_state;
unsigned int rcm_up_state;
unsigned int bus_flow_id;
/* TODO:
* Handling Out Of Order during upgrade
* Once an upgrade is decided we cannot handover the skb to
* FW in the upgraded Flow Ring ... it will create Out of Order Packets.
* Instead we can have a output_q per socket flow. Once the upgrade is
* decided, we can start adding skbs to the output_q. The last 'skb' given
* to the actual Flow ring should be remembered in 'last_skb_orig_fl'.
* Once we get a Tx completion for last_skb_orig_fl we can flush the
* contents of output_q to the 'upgraded flowring'. In this solution,
* we should also handle the case where output_q hits the watermark
* before the completion for 'last_skb_orig_fl' is received. If this condition
* happens, not to worry about OOO and flush the contents of output_q.
* Probably the last_skb_orig_fl is not sent out due latency in the
* existing flow ... the actual problem we are trying to solve.
*/
/* Management elements */
struct list_head list;
unsigned int in_use;
};
typedef enum _frameburst_state
{
FRMBRST_DISABLED = 0,
FRMBRST_ENABLED = 1
} frameburst_state_t;
/* Sock QOS Module Structure */
typedef struct dhd_sock_qos_info
{
/* Table of Socket Flows */
struct dhd_sock_flow_info *sk_fl;
/* maximum number for socket flows supported */
uint32 max_sock_fl;
/* TODO: need to make it per flow later on */
/* global qos algo parameters */
qos_algo_params_t qos_params;
/* List in which active Socket Flows live */
struct list_head sk_fl_list_head;
void *list_lock;
/* Time interval a socket flow resource is moved out of the active list */
uint32 sock_idle_thresh;
/*
* Keep track of number of flows upgraded.
* If it reaches a threshold we should stop ugrading
* This is to avoid the problem where we overwhelm
* the Dongle with upgraded traffic.
*/
int num_skfl_upgraded;
int skfl_upgrade_thresh;
/* flag that is set to true when the first flow is upgraded
* so that FW frameburst is disabled, and set to false
* when no more flows are in upgraded state, so that
* FW frameburst is re-enabled
*/
bool upgrade_active;
/* fw frameburst state */
frameburst_state_t frmbrst_state;
atomic_t on_off;
atomic_t force_upgrade;
/* required for enabling/disabling watchdog timer at runtime */
uint watchdog_ms;
} dhd_sock_qos_info_t;
#define SK_FL_LIST_LOCK(lock, flags) (flags) = osl_spin_lock(lock)
#define SK_FL_LIST_UNLOCK(lock, flags) osl_spin_unlock((lock), (flags))
int
dhd_init_sock_flows_buf(dhd_info_t *dhd, uint watchdog_ms)
{
unsigned long sz;
unsigned int i;
struct dhd_sock_flow_info *sk_fl = NULL;
int val = 0, ret = 0;
if (dhd == NULL)
return BCME_BADARG;
dhd->psk_qos = MALLOCZ(dhd->pub.osh, sizeof(dhd_sock_qos_info_t));
if (dhd->psk_qos == NULL) {
DHD_ERROR(("%s(): Failed to alloc psk_qos ! \n", __FUNCTION__));
return BCME_NOMEM;
}
dhd->psk_qos->max_sock_fl = MAX_SOCK_FLOW;
sz = sizeof(struct dhd_sock_flow_info) * MAX_SOCK_FLOW;
dhd->psk_qos->sk_fl = MALLOCZ(dhd->pub.osh, sz);
if (dhd->psk_qos->sk_fl == NULL) {
DHD_ERROR(("%s(): Failed to allocated sk_fl \r\n", __FUNCTION__));
return BCME_NOMEM;
}
sk_fl = dhd->psk_qos->sk_fl;
for (i = 0; i < MAX_SOCK_FLOW; i++, sk_fl++) {
sk_fl->in_use = 0;
}
dhd->psk_qos->sock_idle_thresh = SOCK_IDLE_THRESHOLD_MS;
dhd->psk_qos->skfl_upgrade_thresh = SOCK_FLOW_UPGRADE_THRESHOLD;
INIT_LIST_HEAD(&dhd->psk_qos->sk_fl_list_head);
dhd->psk_qos->list_lock = osl_spin_lock_init(dhd->pub.osh);
dhd->psk_qos->watchdog_ms = watchdog_ms;
/* feature is DISABLED by default */
dhd_sock_qos_set_status(dhd, 0);
qos_algo_params_init(&dhd->psk_qos->qos_params);
dhd->psk_qos->frmbrst_state = FRMBRST_ENABLED;
/* read the initial state of frameburst from FW, cannot
* assume that it will always be in enabled state by default.
* We will cache the FW frameburst state in host and change
* it everytime we change it from host during QoS upgrade.
* This decision is taken, because firing an iovar everytime
* to query FW frameburst state before deciding whether to
* changing the frameburst state or not from host, is sub-optimal,
* especially in the Tx path.
*/
ret = dhd_wl_ioctl_cmd(&dhd->pub, WLC_SET_FAKEFRAG, (char *)&val,
sizeof(val), FALSE, 0);
if (ret != BCME_OK) {
DHD_ERROR(("%s: get fw frameburst failed,"
" err=%d\n", __FUNCTION__, ret));
} else {
DHD_INFO(("%s:fw frameburst = %d", __FUNCTION__, val));
dhd->psk_qos->frmbrst_state =
(val == 1) ? FRMBRST_ENABLED : FRMBRST_DISABLED;
}
return BCME_OK;
}
int
dhd_deinit_sock_flows_buf(dhd_info_t *dhd)
{
if (dhd == NULL)
return BCME_BADARG;
if (dhd->psk_qos->sk_fl) {
MFREE(dhd->pub.osh, dhd->psk_qos->sk_fl,
sizeof(struct dhd_sock_flow_info) * MAX_SOCK_FLOW);
dhd->psk_qos->sk_fl = NULL;
}
osl_spin_lock_deinit(dhd->pub.osh, dhd->psk_qos->list_lock);
MFREE(dhd->pub.osh, dhd->psk_qos, sizeof(dhd_sock_qos_info_t));
dhd->psk_qos = NULL;
return BCME_OK;
}
/* Caller should hold list_lock */
static inline struct dhd_sock_flow_info *
__dhd_find_sock_stream_info(dhd_sock_qos_info_t *psk_qos, unsigned long ino)
{
struct dhd_sock_flow_info *sk_fl = NULL;
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
list_for_each_entry(sk_fl, &psk_qos->sk_fl_list_head,
list) {
if (sk_fl && (sk_fl->ino == ino)) {
return sk_fl;
}
} /* end of list iteration */
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
/* If control comes here, the ino is not found */
DHD_INFO(("%s(): ino:%lu not found \r\n", __FUNCTION__, ino));
return NULL;
}
static struct dhd_sock_flow_info *
dhd_alloc_sock_stream_info(dhd_sock_qos_info_t *psk_qos)
{
struct dhd_sock_flow_info *sk_fl = psk_qos->sk_fl;
int i;
for (i = 0; i < psk_qos->max_sock_fl; i++, sk_fl++) {
if (sk_fl->in_use == 0) {
DHD_ERROR(("%s: Use sk_fl %p \r\n", __FUNCTION__, sk_fl));
return sk_fl;
}
}
DHD_INFO(("No Free Socket Stream info \r\n"));
return NULL;
}
/* Caller should hold list_lock */
static inline void
__dhd_free_sock_stream_info(dhd_sock_qos_info_t *psk_qos,
struct dhd_sock_flow_info *sk_fl)
{
/*
* If the socket flow getting freed is an upgraded socket flow,
* we can upgrade one more flow.
*/
if (sk_fl->cur_up_state == 1) {
--psk_qos->num_skfl_upgraded;
ASSERT(psk_qos->num_skfl_upgraded >= 0);
}
/* Remove the flow from active list */
list_del(&sk_fl->list);
DHD_ERROR(("%s(): Cleaning Socket Flow ino:%lu psk_qos->num_skfl_upgraded=%d\r\n",
__FUNCTION__, sk_fl->ino, psk_qos->num_skfl_upgraded));
/* Clear its content */
memset_s(sk_fl, sizeof(*sk_fl), 0, sizeof(*sk_fl));
return;
}
static void
dhd_clean_idle_sock_streams(dhd_sock_qos_info_t *psk_qos)
{
struct dhd_sock_flow_info *sk_fl = NULL, *next = NULL;
u64 now;
u64 diff;
unsigned long flags = 0;
now = local_clock();
SK_FL_LIST_LOCK(psk_qos->list_lock, flags);
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
list_for_each_entry_safe(sk_fl, next, &psk_qos->sk_fl_list_head, list) {
if (sk_fl) {
if (sk_fl->in_use == 0) {
DHD_ERROR_RLMT(("%s:Something wrong,"
" a free sk_fl living in active stream\n",
__FUNCTION__));
DHD_ERROR_RLMT(("sk_fl:%p sk:%p ino:%lu \r\n",
sk_fl, sk_fl->sk, sk_fl->ino));
continue;
}
/* XXX: TODO: need to investigate properly in future.
* it is observed that in some hosts (FC25), the
* current timestamp is lesser than previous timestamp
* leading to false cleanups
*/
if (now <= sk_fl->last_pkt_ns)
continue;
diff = now - sk_fl->last_pkt_ns;
/* Convert diff which is in ns to ms */
diff = div64_u64(diff, 1000000UL);
if (diff >= psk_qos->sock_idle_thresh) {
DHD_ERROR(("sk_fl->sk:%p sk_fl->i_no:%lu \r\n",
sk_fl->sk, sk_fl->ino));
if (sk_fl->cur_up_state == 1 &&
psk_qos->num_skfl_upgraded == 1) {
psk_qos->upgrade_active = FALSE;
}
__dhd_free_sock_stream_info(psk_qos, sk_fl);
}
}
} /* end of list iteration */
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
SK_FL_LIST_UNLOCK(psk_qos->list_lock, flags);
}
static inline int
__dhd_upgrade_sock_flow(dhd_info_t *dhd,
struct dhd_sock_flow_info *sk_fl,
struct sk_buff *skb)
{
dhd_sock_qos_info_t *psk_qos = dhd->psk_qos;
#ifdef DHD_HP2P
dhd_pub_t *dhdp = &dhd->pub;
#endif
uint8 *pktdat = NULL;
struct ether_header *eh = NULL;
struct iphdr *iph = NULL;
/* Before upgrading a flow,
* Check the bound to control the number of flows getting upgraded
*/
if ((sk_fl->rcm_up_state == 1) && (sk_fl->cur_up_state == 0)) {
if (psk_qos->num_skfl_upgraded >= psk_qos->skfl_upgrade_thresh) {
DHD_ERROR_RLMT(("%s(): Thresh hit num_skfl_upgraded:%d"
"skfl_upgrade_thresh:%d \r\n",
__FUNCTION__, psk_qos->num_skfl_upgraded,
psk_qos->skfl_upgrade_thresh));
return BCME_ERROR;
} else {
if (psk_qos->num_skfl_upgraded == 0) {
/* if no flows upgraded till now, and this is the
* first flow to be upgraded,
* then disable frameburst in FW.
* The actual iovar to disable frameburst cannot
* be fired here because Tx can happen in atomic context
* and dhd_iovar can sleep due to proto_block lock being
* held. Instead the flag is checked from
* 'dhd_analyze_sock_flows' which execs in non-atomic context
* and the iovar is fired from there
*/
DHD_TRACE(("%s: disable frameburst ..", __FUNCTION__));
psk_qos->upgrade_active = TRUE;
}
++psk_qos->num_skfl_upgraded;
DHD_ERROR_RLMT(("%s(): upgrade flow sk_fl %p,"
"num_skfl_upgraded:%d skfl_upgrade_thresh:%d \r\n",
__FUNCTION__, sk_fl, psk_qos->num_skfl_upgraded,
psk_qos->skfl_upgrade_thresh));
}
}
/* Upgrade the skb */
#ifdef DHD_HP2P
if (dhdp->hp2p_capable)
skb->priority = TC_PRIO_CONTROL;
else
skb->priority = TC_PRIO_INTERACTIVE;
#else
skb->priority = TC_PRIO_INTERACTIVE;
#endif /* DHD_HP2P */
pktdat = PKTDATA(dhd->pub.osh, skb);
eh = (struct ether_header *) pktdat;
if (pktdat && (eh->ether_type == hton16(ETHER_TYPE_IP))) {
/* 'upgrade' DSCP also, else it is observed that on
* AP side if DSCP value is not in sync with L2 prio
* then out of order packets are observed
*/
iph = (struct iphdr *)(pktdat + sizeof(struct ether_header));
iph->tos = DSCP_TOS_CS7;
/* re-compute ip hdr checksum
* NOTE: this takes around 1us, need to profile more
* accurately to get the number of cpu cycles it takes
* in order to get a better idea of the impact of
* re computing ip hdr chksum in data path
*/
ip_send_check(iph);
}
/* Mark the Flow as 'upgraded' */
if (sk_fl->cur_up_state == 0)
sk_fl->cur_up_state = 1;
return BCME_OK;
}
static inline int
__dhd_downgrade_sock_flow(dhd_info_t *dhd,
struct dhd_sock_flow_info *sk_fl,
struct sk_buff *skb)
{
dhd_sock_qos_info_t *psk_qos = dhd->psk_qos;
if ((sk_fl->rcm_up_state == 0) && (sk_fl->cur_up_state == 1)) {
/* sanity check */
ASSERT(psk_qos->num_skfl_upgraded > 0);
if (psk_qos->num_skfl_upgraded <= 0) {
DHD_ERROR_RLMT(("%s(): FATAL ! no upgraded flows !\n",
__FUNCTION__));
return BCME_ERROR;
}
if (psk_qos->num_skfl_upgraded == 1) {
/* if this is the
* last flow to be downgraded,
* then re-enable frameburst in FW.
* The actual iovar to enable frameburst cannot
* be fired here because Tx can happen in atomic context
* and dhd_iovar can sleep due to proto_block lock being
* held. Instead the flag is checked from
* 'dhd_analyze_sock_flows' which execs in non-atomic context
* and the iovar is fired from there
*/
DHD_TRACE(("%s: enable frameburst ..", __FUNCTION__));
psk_qos->upgrade_active = FALSE;
}
--psk_qos->num_skfl_upgraded;
DHD_ERROR_RLMT(("%s(): downgrade flow sk_fl %p,"
"num_skfl_upgraded:%d \r\n",
__FUNCTION__, sk_fl, psk_qos->num_skfl_upgraded));
}
/* Mark the Flow as 'downgraded' */
if (sk_fl->cur_up_state == 1)
sk_fl->cur_up_state = 0;
return BCME_OK;
}
/*
* Update the stats of a Socket flow.
* Create a new flow if need be.
* If a socket flow has been recommended for upgrade, do so.
*/
void
dhd_update_sock_flows(dhd_info_t *dhd, struct sk_buff *skb)
{
struct sock *sk = NULL;
unsigned long ino = 0;
struct dhd_sock_flow_info *sk_fl = NULL;
dhd_sock_qos_info_t *psk_qos = NULL;
unsigned long flags = 0;
uint8 prio;
BCM_REFERENCE(prio);
if ((dhd == NULL) || (skb == NULL)) {
DHD_ERROR_RLMT(("%s: Invalid args \n", __FUNCTION__));
return;
}
/* If the Feature is disabled, return */
if (dhd_sock_qos_get_status(dhd) == 0)
return;
psk_qos = dhd->psk_qos;
sk = (struct sock *)PKTSOCK(dhd->pub.osh, skb);
/* TODO:
* Some times sk is NULL, what does that mean ...
* is it a broadcast packet generated by Network Stack ????
*/
if (sk == NULL) {
return;
}
ino = sock_i_ino(sk);
/* TODO:
* List Lock need not be held for allocating sock stream .. optimize
*/
SK_FL_LIST_LOCK(psk_qos->list_lock, flags);
sk_fl = __dhd_find_sock_stream_info(psk_qos, ino);
if (sk_fl == NULL) {
/* Allocate new sock stream */
sk_fl = dhd_alloc_sock_stream_info(psk_qos);
if (sk_fl == NULL) {
SK_FL_LIST_UNLOCK(psk_qos->list_lock, flags);
goto done;
}
else {
/* SK Flow elements updated first time */
sk_fl->in_use = 1;
sk_fl->sk = sk;
sk_fl->ino = ino;
/* TODO: Seeing a Kernel Warning ... check */
/* sk_fl->uid = sock_i_uid(sk); */
sk_fl->cur_up_state = 0;
list_add_tail(&sk_fl->list, &psk_qos->sk_fl_list_head);
DHD_ERROR(("%s(): skb %p sk %p sk_fl %p ino %lu"
" prio 0x%x \r\n", __FUNCTION__, skb,
sk, sk_fl, ino, skb->priority));
} /* end of new sk flow allocation */
} /* end of case when sk flow is found */
sk_fl->stats.tx_pkts++;
sk_fl->stats.tx_bytes += skb->len;
sk_fl->last_pkt_ns = local_clock();
SK_FL_LIST_UNLOCK(psk_qos->list_lock, flags);
if (sk_fl->rcm_up_state == 1) {
__dhd_upgrade_sock_flow(dhd, sk_fl, skb);
} else {
__dhd_downgrade_sock_flow(dhd, sk_fl, skb);
}
prio = PKTPRIO(skb);
DHD_INFO(("%s(): skb:%p skb->priority 0x%x prio %d sk_fl %p\r\n", __FUNCTION__, skb,
skb->priority, prio, sk_fl));
done:
return;
}
static int
dhd_change_frameburst_state(frameburst_state_t newstate, dhd_info_t *dhd)
{
int ret = 0, val = 0;
dhd_sock_qos_info_t *psk_qos = NULL;
if (!dhd)
return BCME_BADARG;
if (!dhd->psk_qos)
return BCME_BADARG;
psk_qos = dhd->psk_qos;
/* Check with the cached frameburst state on host
* instead of querying FW frameburst state.
* This decision is taken, because firing an iovar everytime
* to query FW frameburst state before deciding whether to
* changing the frameburst state or not is sub-optimal,
* especially in the Tx path.
*/
if (psk_qos->frmbrst_state == newstate)
return BCME_BADOPTION;
val = (newstate == FRMBRST_ENABLED) ? 1 : 0;
ret = dhd_wl_ioctl_cmd(&dhd->pub, WLC_SET_FAKEFRAG, (char *)&val,
sizeof(val), TRUE, 0);
if (ret != BCME_OK) {
DHD_ERROR_RLMT(("%s: set frameburst=%d failed,"
" err=%d\n", __FUNCTION__, val, ret));
} else {
/* change the state */
DHD_INFO(("%s: set frameburst=%d\n", __FUNCTION__, val));
psk_qos->frmbrst_state = newstate;
}
return ret;
}
void dhd_analyze_sock_flows(dhd_info_t *dhd, uint32 watchdog_ms)
{
struct dhd_sock_flow_info *sk_fl = NULL;
dhd_sock_qos_info_t *psk_qos = NULL;
unsigned long flags = 0;
if (dhd == NULL) {
DHD_ERROR_RLMT(("%s: Bad argument \r\n", __FUNCTION__));
return;
}
/* Check whether the feature is disabled */
if (dhd_sock_qos_get_status(dhd) == 0)
return;
psk_qos = dhd->psk_qos;
dhd_clean_idle_sock_streams(dhd->psk_qos);
/* TODO: Plug in the QoS Algorithm here */
SK_FL_LIST_LOCK(psk_qos->list_lock, flags);
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
list_for_each_entry(sk_fl, &psk_qos->sk_fl_list_head, list) {
sk_fl->rcm_up_state = dhd_qos_algo(dhd, &sk_fl->stats, &psk_qos->qos_params);
/* TODO: Handle downgrades */
/* update sk_flow previous elements on every sampling interval */
sk_fl->stats.tx_pkts_prev = sk_fl->stats.tx_pkts;
sk_fl->stats.tx_bytes_prev = sk_fl->stats.tx_bytes;
/* TODO: Handle the condition where num_skfl_upgraded reaches the threshold */
/* TODO: Handle the condition where we upgrade all the socket flows
* of the uid on which one flow is detected to be upgraded.
*/
} /* end of list iteration */
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
SK_FL_LIST_UNLOCK(psk_qos->list_lock, flags);
/* disable frameburst in FW on the first flow upgraded */
if (psk_qos->upgrade_active) {
dhd_change_frameburst_state(FRMBRST_DISABLED, dhd);
} else {
/* if no upgraded flows remain, either after cleanup,
* or after a downgrade,
* then re-enable frameburst in FW
*/
dhd_change_frameburst_state(FRMBRST_ENABLED, dhd);
}
return;
}
void dhd_sock_qos_update_bus_flowid(dhd_info_t *dhd, void *pktbuf,
uint32 bus_flow_id)
{
BCM_REFERENCE(dhd);
BCM_REFERENCE(pktbuf);
BCM_REFERENCE(bus_flow_id);
return;
}
/* ================= Sysfs interfce support functions ======================== */
unsigned long dhd_sock_qos_get_status(dhd_info_t *dhd)
{
if (dhd == NULL)
return 0;
return (atomic_read(&dhd->psk_qos->on_off));
}
void dhd_sock_qos_set_status(dhd_info_t *dhd, unsigned long on_off)
{
if (dhd == NULL)
return;
atomic_set(&dhd->psk_qos->on_off, on_off);
if (on_off) {
dhd_watchdog_ms = QOS_SAMPLING_INTVL_MS;
/* enable watchdog to monitor the socket flows */
dhd_os_wd_timer(&dhd->pub, QOS_SAMPLING_INTVL_MS);
} else {
dhd_watchdog_ms = dhd->psk_qos->watchdog_ms;
/* disable watchdog or set it back to the original value */
dhd_os_wd_timer(&dhd->pub, dhd->psk_qos->watchdog_ms);
}
return;
}
ssize_t dhd_sock_qos_show_stats(dhd_info_t *dhd, char *buf,
ssize_t sz)
{
dhd_sock_qos_info_t *psk_qos = NULL;
struct dhd_sock_flow_info *sk_fl = NULL;
unsigned long flags = 0;
ssize_t ret = 0;
char *p = buf;
/* TODO: Should be actual record length */
unsigned long rec_len = 100;
if (dhd == NULL)
return -1;
psk_qos = dhd->psk_qos;
ret += scnprintf(p, sz-ret-1, "\nino\t sk\t\t\t tx_pkts\t tx_bytes\t"
"last_pkt_ns\r\n");
p += ret;
SK_FL_LIST_LOCK(psk_qos->list_lock, flags);
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
list_for_each_entry(sk_fl, &psk_qos->sk_fl_list_head, list) {
/* Protect the buffer from over run */
if (ret + rec_len >= sz)
break;
ret += scnprintf(p, sz-ret-1, "%lu\t %p\t %lu\t %lu\t %llu\t \r\n",
sk_fl->ino, sk_fl->sk, sk_fl->stats.tx_pkts, sk_fl->stats.tx_bytes,
sk_fl->last_pkt_ns);
p += ret;
}
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
SK_FL_LIST_UNLOCK(psk_qos->list_lock, flags);
return ret + 1;
}
void dhd_sock_qos_clear_stats(dhd_info_t *dhd)
{
dhd_sock_qos_info_t *psk_qos = NULL;
struct dhd_sock_flow_info *sk_fl = NULL;
unsigned long flags = 0;
if (dhd == NULL)
return;
psk_qos = dhd->psk_qos;
SK_FL_LIST_LOCK(psk_qos->list_lock, flags);
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
list_for_each_entry(sk_fl, &psk_qos->sk_fl_list_head, list) {
sk_fl->stats.tx_pkts = 0;
sk_fl->stats.tx_bytes = 0;
sk_fl->stats.tx_pkts_prev = 0;
sk_fl->stats.tx_bytes_prev = 0;
sk_fl->last_pkt_ns = 0;
}
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
SK_FL_LIST_UNLOCK(psk_qos->list_lock, flags);
return;
}
unsigned long dhd_sock_qos_get_force_upgrade(dhd_info_t *dhd)
{
if (dhd == NULL)
return 0;
return (atomic_read(&dhd->psk_qos->force_upgrade));
}
void dhd_sock_qos_set_force_upgrade(dhd_info_t *dhd, unsigned long force_upgrade)
{
if (dhd == NULL)
return;
atomic_set(&dhd->psk_qos->force_upgrade, force_upgrade);
return;
}
int dhd_sock_qos_get_numfl_upgrd_thresh(dhd_info_t *dhd)
{
if (dhd == NULL)
return 0;
return dhd->psk_qos->skfl_upgrade_thresh;
}
void dhd_sock_qos_set_numfl_upgrd_thresh(dhd_info_t *dhd,
int upgrade_thresh)
{
if (dhd == NULL)
return;
dhd->psk_qos->skfl_upgrade_thresh = upgrade_thresh;
return;
}
void dhd_sock_qos_get_avgpktsize_thresh(dhd_info_t *dhd,
unsigned long *avgpktsize_low,
unsigned long *avgpktsize_high)
{
qos_algo_params_t *pqos_params = NULL;
if (dhd == NULL || avgpktsize_low == NULL ||
avgpktsize_high == NULL) {
return;
}
pqos_params = QOS_PARAMS(dhd);
*avgpktsize_low = pqos_params->avg_pkt_size_low_thresh;
*avgpktsize_high = pqos_params->avg_pkt_size_high_thresh;
return;
}
void dhd_sock_qos_set_avgpktsize_thresh(dhd_info_t *dhd,
unsigned long avgpktsize_low,
unsigned long avgpktsize_high)
{
qos_algo_params_t *pqos_params = NULL;
if (dhd == NULL)
return;
pqos_params = QOS_PARAMS(dhd);
pqos_params->avg_pkt_size_low_thresh = avgpktsize_low;
pqos_params->avg_pkt_size_high_thresh = avgpktsize_high;
return;
}
void dhd_sock_qos_get_numpkts_thresh(dhd_info_t *dhd,
unsigned long *numpkts_low,
unsigned long *numpkts_high)
{
qos_algo_params_t *pqos_params = NULL;
if (dhd == NULL || numpkts_low == NULL ||
numpkts_high == NULL) {
return;
}
pqos_params = QOS_PARAMS(dhd);
*numpkts_low = pqos_params->num_pkts_low_thresh;
*numpkts_high = pqos_params->num_pkts_high_thresh;
}
void dhd_sock_qos_set_numpkts_thresh(dhd_info_t *dhd,
unsigned long numpkts_low,
unsigned long numpkts_high)
{
qos_algo_params_t *pqos_params = NULL;
if (dhd == NULL)
return;
pqos_params = QOS_PARAMS(dhd);
pqos_params->num_pkts_low_thresh = numpkts_low;
pqos_params->num_pkts_high_thresh = numpkts_high;
return;
}
void dhd_sock_qos_get_detectcnt_thresh(dhd_info_t *dhd,
unsigned char *detectcnt_inc,
unsigned char *detectcnt_dec)
{
qos_algo_params_t *pqos_params = NULL;
if (dhd == NULL || detectcnt_inc == NULL ||
detectcnt_dec == NULL) {
return;
}
pqos_params = QOS_PARAMS(dhd);
*detectcnt_inc = pqos_params->detect_cnt_inc_thresh;
*detectcnt_dec = pqos_params->detect_cnt_dec_thresh;
}
void dhd_sock_qos_set_detectcnt_thresh(dhd_info_t *dhd,
unsigned char detectcnt_inc,
unsigned char detectcnt_dec)
{
qos_algo_params_t *pqos_params = NULL;
if (dhd == NULL)
return;
pqos_params = QOS_PARAMS(dhd);
pqos_params->detect_cnt_inc_thresh = detectcnt_inc;
pqos_params->detect_cnt_dec_thresh = detectcnt_dec;
return;
}
int dhd_sock_qos_get_detectcnt_upgrd_thresh(dhd_info_t *dhd)
{
qos_algo_params_t *pqos_params = NULL;
if (dhd == NULL)
return 0;
pqos_params = QOS_PARAMS(dhd);
return pqos_params->detect_cnt_upgrade_thresh;
}
void dhd_sock_qos_set_detectcnt_upgrd_thresh(dhd_info_t *dhd,
unsigned char detect_upgrd_thresh)
{
qos_algo_params_t *pqos_params = NULL;
if (dhd == NULL)
return;
pqos_params = QOS_PARAMS(dhd);
pqos_params->detect_cnt_upgrade_thresh = detect_upgrd_thresh;
}
int dhd_sock_qos_get_maxfl(dhd_info_t *dhd)
{
if (dhd == NULL)
return 0;
return dhd->psk_qos->max_sock_fl;
}
void dhd_sock_qos_set_maxfl(dhd_info_t *dhd,
unsigned int maxfl)
{
if (dhd == NULL)
return;
dhd->psk_qos->max_sock_fl = maxfl;
}
/* ================= End of Sysfs interfce support functions ======================== */
/* ================= QOS Algorithm ======================== */
/*
* Operates on a flow and returns 1 for upgrade and 0 for
* no up-grade - Has the potential of moving into a separate file
* Takes the dhd pointer too in case if it has to access any platform
* functions like MALLOC that takes dhd->pub.osh as argument.
*/
int dhd_qos_algo(dhd_info_t *dhd, qos_stat_t *qos, qos_algo_params_t *pqos_params)
{
unsigned long tx_bytes, tx_pkts, tx_avg_pkt_size;
if (!dhd || !qos || !pqos_params) {
return 0;
}
/* if the user has set the sysfs variable to force upgrade */
if (atomic_read(&dhd->psk_qos->force_upgrade) == 1) {
return 1;
}
DHD_TRACE(("%s(): avgpktsize_thrsh %lu:%lu; "
"numpkts_thrs %lu:%lu; detectcnt_thrs %d:%d;"
" detectcnt_upgrd_thrs %d\n", __FUNCTION__,
pqos_params->avg_pkt_size_low_thresh,
pqos_params->avg_pkt_size_high_thresh,
pqos_params->num_pkts_low_thresh,
pqos_params->num_pkts_high_thresh,
pqos_params->detect_cnt_inc_thresh,
pqos_params->detect_cnt_dec_thresh,
pqos_params->detect_cnt_upgrade_thresh));
tx_bytes = qos->tx_bytes - qos->tx_bytes_prev;
tx_pkts = qos->tx_pkts - qos->tx_pkts_prev;
if ((tx_bytes == 0) || (tx_pkts == 0)) {
return 0;
}
tx_avg_pkt_size = tx_bytes / tx_pkts;
if ((tx_avg_pkt_size > pqos_params->avg_pkt_size_low_thresh) &&
(tx_avg_pkt_size < pqos_params->avg_pkt_size_high_thresh) &&
(tx_pkts > pqos_params->num_pkts_low_thresh) &&
(tx_pkts < pqos_params->num_pkts_high_thresh)) {
if (qos->lowlat_detect_count < pqos_params->detect_cnt_inc_thresh) {
qos->lowlat_detect_count++;
}
} else if (qos->lowlat_detect_count > pqos_params->detect_cnt_dec_thresh) {
qos->lowlat_detect_count--;
}
if (qos->lowlat_detect_count > pqos_params->detect_cnt_upgrade_thresh) {
qos->lowlat_flow = TRUE;
} else if (qos->lowlat_detect_count == 0) {
qos->lowlat_flow = FALSE;
}
DHD_TRACE(("%s(): TX:%lu:%lu:%lu, PUBG:%d::%d\n",
__FUNCTION__, tx_avg_pkt_size, tx_bytes, tx_pkts,
qos->lowlat_detect_count, qos->lowlat_flow));
return (qos->lowlat_flow == TRUE) ? 1 : 0;
}
int qos_algo_params_init(qos_algo_params_t *pqos_params)
{
if (!pqos_params)
return BCME_BADARG;
memset(pqos_params, 0, sizeof(*pqos_params));
pqos_params->avg_pkt_size_low_thresh = LOWLAT_AVG_PKT_SIZE_LOW;
pqos_params->avg_pkt_size_high_thresh = LOWLAT_AVG_PKT_SIZE_HIGH;
pqos_params->num_pkts_low_thresh = LOWLAT_NUM_PKTS_LOW;
pqos_params->num_pkts_high_thresh = LOWLAT_NUM_PKTS_HIGH;
pqos_params->detect_cnt_inc_thresh = LOWLAT_DETECT_CNT_INC_THRESH;
pqos_params->detect_cnt_dec_thresh = LOWLAT_DETECT_CNT_DEC_THRESH;
pqos_params->detect_cnt_upgrade_thresh = LOWLAT_DETECT_CNT_UPGRADE_THRESH;
return BCME_OK;
}
/* ================= End of QOS Algorithm ======================== */