/* * 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. * * * <> * * $Id$ * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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 ======================== */