diff options
Diffstat (limited to 'drivers/net/wireless/nxp/mxm_wifiex/wlan_src/mlan/mlan_wmm.c')
-rw-r--r-- | drivers/net/wireless/nxp/mxm_wifiex/wlan_src/mlan/mlan_wmm.c | 3282 |
1 files changed, 3282 insertions, 0 deletions
diff --git a/drivers/net/wireless/nxp/mxm_wifiex/wlan_src/mlan/mlan_wmm.c b/drivers/net/wireless/nxp/mxm_wifiex/wlan_src/mlan/mlan_wmm.c new file mode 100644 index 000000000000..f32a16687090 --- /dev/null +++ b/drivers/net/wireless/nxp/mxm_wifiex/wlan_src/mlan/mlan_wmm.c @@ -0,0 +1,3282 @@ +/** @file mlan_wmm.c + * + * @brief This file contains functions for WMM. + * + * + * Copyright 2014-2020 NXP + * + * This software file (the File) is distributed by NXP + * under the terms of the GNU General Public License Version 2, June 1991 + * (the License). You may use, redistribute and/or modify the File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + * + */ + +/******************************************************** +Change log: + 10/24/2008: initial version +********************************************************/ + +#include "mlan.h" +#ifdef STA_SUPPORT +#include "mlan_join.h" +#endif +#include "mlan_util.h" +#include "mlan_fw.h" +#include "mlan_main.h" +#include "mlan_wmm.h" +#include "mlan_11n.h" +#ifdef SDIO +#include "mlan_sdio.h" +#endif /* SDIO */ +#ifdef PCIE +#include "mlan_pcie.h" +#endif /* PCIE */ + +/******************************************************** + Local Variables +********************************************************/ + +/** Maximum value FW can accept for driver delay in packet transmission */ +#define DRV_PKT_DELAY_TO_FW_MAX 512 + +/* + * Upper and Lower threshold for packet queuing in the driver + + * - When the number of packets queued reaches the upper limit, + * the driver will stop the net queue in the app/kernel space. + + * - When the number of packets drops beneath the lower limit after + * having reached the upper limit, the driver will restart the net + * queue. + */ + +/** Lower threshold for packet queuing in the driver. + * When the number of packets drops beneath the lower limit after having + * reached the upper limit, the driver will restart the net queue. + */ +#define WMM_QUEUED_PACKET_LOWER_LIMIT 180 + +/** Upper threshold for packet queuing in the driver. + * When the number of packets queued reaches the upper limit, the driver + * will stop the net queue in the app/kernel space. + */ +#define WMM_QUEUED_PACKET_UPPER_LIMIT 200 + +/** Offset for TOS field in the IP header */ +#define IPTOS_OFFSET 5 + +/** WMM information IE */ +static const t_u8 wmm_info_ie[] = {WMM_IE, 0x07, 0x00, 0x50, 0xf2, + 0x02, 0x00, 0x01, 0x00}; + +/** Type enumeration of WMM AC_QUEUES */ +typedef MLAN_PACK_START enum _wmm_ac_e { + AC_BE, + AC_BK, + AC_VI, + AC_VO +} MLAN_PACK_END wmm_ac_e; + +/** + * AC Priorities go from AC_BK to AC_VO. The ACI enumeration for AC_BK (1) + * is higher than the enumeration for AC_BE (0); hence the needed + * mapping conversion for wmm AC to priority Queue Index + */ +static const t_u8 wmm_aci_to_qidx_map[] = {WMM_AC_BE, WMM_AC_BK, WMM_AC_VI, + WMM_AC_VO}; +/** + * This table will be used to store the tid values based on ACs. + * It is initialized to default values per TID. + */ +t_u8 tos_to_tid[] = { + /* TID DSCP_P2 DSCP_P1 DSCP_P0 WMM_AC */ + 0x01, /* 0 1 0 AC_BK */ + 0x02, /* 0 0 0 AC_BK */ + 0x00, /* 0 0 1 AC_BE */ + 0x03, /* 0 1 1 AC_BE */ + 0x04, /* 1 0 0 AC_VI */ + 0x05, /* 1 0 1 AC_VI */ + 0x06, /* 1 1 0 AC_VO */ + 0x07 /* 1 1 1 AC_VO */ +}; + +/** + * This table inverses the tos_to_tid operation to get a priority + * which is in sequential order, and can be compared. + * Use this to compare the priority of two different TIDs. + */ +t_u8 tos_to_tid_inv[] = {0x02, /* from tos_to_tid[2] = 0 */ + 0x00, /* from tos_to_tid[0] = 1 */ + 0x01, /* from tos_to_tid[1] = 2 */ + 0x03, 0x04, 0x05, 0x06, 0x07}; + +/** + * This table will provide the tid value for given ac. This table does not + * change and will be used to copy back the default values to tos_to_tid in + * case of disconnect. + */ +const t_u8 ac_to_tid[4][2] = {{1, 2}, {0, 3}, {4, 5}, {6, 7}}; + +/* Map of TOS UP values to WMM AC */ +static const mlan_wmm_ac_e tos_to_ac[] = {WMM_AC_BE, WMM_AC_BK, WMM_AC_BK, + WMM_AC_BE, WMM_AC_VI, WMM_AC_VI, + WMM_AC_VO, WMM_AC_VO}; + +raListTbl *wlan_wmm_get_ralist_node(pmlan_private priv, t_u8 tid, + t_u8 *ra_addr); + +/******************************************************** + Local Functions +********************************************************/ +#ifdef DEBUG_LEVEL2 +/** + * @brief Debug print function to display the priority parameters for a WMM AC + * + * @param pac_param Pointer to the AC parameters to display + * + * @return N/A + */ +static void +wlan_wmm_ac_debug_print(const IEEEtypes_WmmAcParameters_t *pac_param) +{ + const char *ac_str[] = {"BK", "BE", "VI", "VO"}; + + ENTER(); + + PRINTM(MINFO, + "WMM AC_%s: ACI=%d, ACM=%d, Aifsn=%d, " + "EcwMin=%d, EcwMax=%d, TxopLimit=%d\n", + ac_str[wmm_aci_to_qidx_map[pac_param->aci_aifsn.aci]], + pac_param->aci_aifsn.aci, pac_param->aci_aifsn.acm, + pac_param->aci_aifsn.aifsn, pac_param->ecw.ecw_min, + pac_param->ecw.ecw_max, + wlan_le16_to_cpu(pac_param->tx_op_limit)); + + LEAVE(); +} +/** Print the WMM AC for debug purpose */ +#define PRINTM_AC(pac_param) wlan_wmm_ac_debug_print(pac_param) +#else +/** Print the WMM AC for debug purpose */ +#define PRINTM_AC(pac_param) +#endif + +/** + * @brief Allocate route address + * + * @param pmadapter Pointer to the mlan_adapter structure + * @param ra Pointer to the route address + * + * @return ra_list + */ +static raListTbl *wlan_wmm_allocate_ralist_node(pmlan_adapter pmadapter, + t_u8 *ra) +{ + raListTbl *ra_list = MNULL; + + ENTER(); + + if (pmadapter->callbacks.moal_malloc(pmadapter->pmoal_handle, + sizeof(raListTbl), MLAN_MEM_DEF, + (t_u8 **)&ra_list)) { + PRINTM(MERROR, "Fail to allocate ra_list\n"); + goto done; + } + util_init_list((pmlan_linked_list)ra_list); + util_init_list_head((t_void *)pmadapter->pmoal_handle, + &ra_list->buf_head, MFALSE, + pmadapter->callbacks.moal_init_lock); + + memcpy_ext(pmadapter, ra_list->ra, ra, MLAN_MAC_ADDR_LENGTH, + MLAN_MAC_ADDR_LENGTH); + + ra_list->del_ba_count = 0; + ra_list->total_pkts = 0; + ra_list->tx_pause = 0; + PRINTM(MINFO, "RAList: Allocating buffers for TID %p\n", ra_list); +done: + LEAVE(); + return ra_list; +} + +/** + * @brief Map ACs to TID + * + * @param priv Pointer to the mlan_private driver data struct + * @param queue_priority Queue_priority structure + * + * @return N/A + */ +static void wlan_wmm_queue_priorities_tid(pmlan_private priv, + t_u8 queue_priority[]) +{ + int i; + + ENTER(); + + for (i = 0; i < 4; ++i) { + tos_to_tid[7 - (i * 2)] = ac_to_tid[queue_priority[i]][1]; + tos_to_tid[6 - (i * 2)] = ac_to_tid[queue_priority[i]][0]; + } + + for (i = 0; i < MAX_NUM_TID; i++) + tos_to_tid_inv[tos_to_tid[i]] = (t_u8)i; + + /* in case priorities have changed, force highest priority so + * next packet will check from top to re-establish the highest + */ + util_scalar_write(priv->adapter->pmoal_handle, + &priv->wmm.highest_queued_prio, HIGH_PRIO_TID, + priv->adapter->callbacks.moal_spin_lock, + priv->adapter->callbacks.moal_spin_unlock); + + LEAVE(); +} + +/** + * @brief Evaluate whether or not an AC is to be downgraded + * + * @param priv Pointer to the mlan_private driver data struct + * @param eval_ac AC to evaluate for downgrading + * + * @return WMM AC The eval_ac traffic is to be sent on. + */ +static mlan_wmm_ac_e wlan_wmm_eval_downgrade_ac(pmlan_private priv, + mlan_wmm_ac_e eval_ac) +{ + int down_ac; + mlan_wmm_ac_e ret_ac; + WmmAcStatus_t *pac_status; + + ENTER(); + + pac_status = &priv->wmm.ac_status[eval_ac]; + + if (pac_status->disabled == MFALSE) { + LEAVE(); + /* Okay to use this AC, its enabled */ + return eval_ac; + } + + /* Setup a default return value of the lowest priority */ + ret_ac = WMM_AC_BK; + + /* + * Find the highest AC that is enabled and does not require admission + * control. The spec disallows downgrading to an AC, which is enabled + * due to a completed admission control. Unadmitted traffic is not + * to be sent on an AC with admitted traffic. + */ + for (down_ac = WMM_AC_BK; down_ac < eval_ac; down_ac++) { + pac_status = &priv->wmm.ac_status[down_ac]; + + if ((pac_status->disabled == MFALSE) && + (pac_status->flow_required == MFALSE)) + /* AC is enabled and does not require admission control + */ + ret_ac = (mlan_wmm_ac_e)down_ac; + } + + LEAVE(); + return ret_ac; +} + +/** + * @brief Convert the IP TOS field to an WMM AC Queue assignment + * + * @param pmadapter A pointer to mlan_adapter structure + * @param tos IP TOS field + * + * @return WMM AC Queue mapping of the IP TOS field + */ +static INLINE mlan_wmm_ac_e wlan_wmm_convert_tos_to_ac(pmlan_adapter pmadapter, + t_u32 tos) +{ + ENTER(); + + if (tos >= NELEMENTS(tos_to_ac)) { + LEAVE(); + return WMM_AC_BE; + } + + LEAVE(); + return tos_to_ac[tos]; +} + +/** + * @brief Evaluate a given TID and downgrade it to a lower TID if the + * WMM Parameter IE received from the AP indicates that the AP + * is disabled (due to call admission control (ACM bit). Mapping + * of TID to AC is taken care internally + * + * @param priv Pointer to the mlan_private data struct + * @param tid tid to evaluate for downgrading + * + * @return Same tid as input if downgrading not required or + * the tid the traffic for the given tid should be downgraded to + */ +static INLINE t_u8 wlan_wmm_downgrade_tid(pmlan_private priv, t_u32 tid) +{ + mlan_wmm_ac_e ac_down; + pmlan_adapter pmadapter = priv->adapter; + + ENTER(); + + ac_down = priv->wmm.ac_down_graded_vals[wlan_wmm_convert_tos_to_ac( + pmadapter, tid)]; + LEAVE(); + /* + * Send the index to tid array, picking from the array will be + * taken care by dequeuing function + */ + if (tid == 1 || tid == 2) + return ac_to_tid[ac_down][(tid + 1) % 2]; + else if (tid >= MAX_NUM_TID) + return ac_to_tid[ac_down][0]; + else + return ac_to_tid[ac_down][tid % 2]; +} + +/** + * @brief Delete packets in RA node + * + * @param priv Pointer to the mlan_private driver data struct + * @param ra_list Pointer to raListTbl + * + * @return N/A + */ +static INLINE void wlan_wmm_del_pkts_in_ralist_node(pmlan_private priv, + raListTbl *ra_list) +{ + pmlan_buffer pmbuf; + pmlan_adapter pmadapter = priv->adapter; + + ENTER(); + while ((pmbuf = (pmlan_buffer)util_peek_list(pmadapter->pmoal_handle, + &ra_list->buf_head, MNULL, + MNULL))) { + util_unlink_list(pmadapter->pmoal_handle, &ra_list->buf_head, + (pmlan_linked_list)pmbuf, MNULL, MNULL); + wlan_write_data_complete(pmadapter, pmbuf, MLAN_STATUS_FAILURE); + } + util_free_list_head((t_void *)pmadapter->pmoal_handle, + &ra_list->buf_head, + pmadapter->callbacks.moal_free_lock); + + LEAVE(); +} + +/** + * @brief Delete packets in RA list + * + * @param priv Pointer to the mlan_private driver data struct + * @param ra_list_head ra list header + * + * @return N/A + */ +static INLINE void wlan_wmm_del_pkts_in_ralist(pmlan_private priv, + mlan_list_head *ra_list_head) +{ + raListTbl *ra_list; + + ENTER(); + + ra_list = (raListTbl *)util_peek_list(priv->adapter->pmoal_handle, + ra_list_head, MNULL, MNULL); + + while (ra_list && ra_list != (raListTbl *)ra_list_head) { + wlan_wmm_del_pkts_in_ralist_node(priv, ra_list); + + ra_list = ra_list->pnext; + } + + LEAVE(); +} + +/** + * @brief Clean up the wmm queue + * + * @param priv Pointer to the mlan_private driver data struct + * + * @return N/A + */ +static void wlan_wmm_cleanup_queues(pmlan_private priv) +{ + int i; + + ENTER(); + + for (i = 0; i < MAX_NUM_TID; i++) { + wlan_wmm_del_pkts_in_ralist(priv, + &priv->wmm.tid_tbl_ptr[i].ra_list); + priv->wmm.pkts_queued[i] = 0; + priv->wmm.pkts_paused[i] = 0; + } + util_scalar_write(priv->adapter->pmoal_handle, + &priv->wmm.tx_pkts_queued, 0, MNULL, MNULL); + util_scalar_write(priv->adapter->pmoal_handle, + &priv->wmm.highest_queued_prio, HIGH_PRIO_TID, MNULL, + MNULL); + + LEAVE(); +} + +/** + * @brief Delete all route address from RA list + * + * @param priv Pointer to the mlan_private driver data struct + * + * @return N/A + */ +static void wlan_wmm_delete_all_ralist(pmlan_private priv) +{ + raListTbl *ra_list; + int i; + pmlan_adapter pmadapter = priv->adapter; + + ENTER(); + + for (i = 0; i < MAX_NUM_TID; ++i) { + PRINTM(MINFO, "RAList: Freeing buffers for TID %d\n", i); + while ((ra_list = (raListTbl *)util_peek_list( + pmadapter->pmoal_handle, + &priv->wmm.tid_tbl_ptr[i].ra_list, MNULL, + MNULL))) { + util_unlink_list(pmadapter->pmoal_handle, + &priv->wmm.tid_tbl_ptr[i].ra_list, + (pmlan_linked_list)ra_list, MNULL, + MNULL); + + pmadapter->callbacks.moal_mfree(pmadapter->pmoal_handle, + (t_u8 *)ra_list); + } + + util_init_list( + (pmlan_linked_list)&priv->wmm.tid_tbl_ptr[i].ra_list); + priv->wmm.tid_tbl_ptr[i].ra_list_curr = MNULL; + } + + LEAVE(); +} + +/** + * @brief Get queue RA pointer + * + * @param priv Pointer to the mlan_private driver data struct + * @param tid TID + * @param ra_addr Pointer to the route address + * + * @return ra_list + */ +static raListTbl *wlan_wmm_get_queue_raptr(pmlan_private priv, t_u8 tid, + t_u8 *ra_addr) +{ + raListTbl *ra_list; +#ifdef UAP_SUPPORT + t_u8 bcast_addr[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; +#endif + + ENTER(); + ra_list = wlan_wmm_get_ralist_node(priv, tid, ra_addr); + if (ra_list) { + LEAVE(); + return ra_list; + } +#ifdef UAP_SUPPORT + if ((GET_BSS_ROLE(priv) == MLAN_BSS_ROLE_UAP) && + (0 != + memcmp(priv->adapter, ra_addr, bcast_addr, sizeof(bcast_addr)))) { + if (MNULL == wlan_get_station_entry(priv, ra_addr)) { + PRINTM_NETINTF(MERROR, priv); + PRINTM(MERROR, + "Drop packets to unknow station " MACSTR "\n", + MAC2STR(ra_addr)); + LEAVE(); + return MNULL; + } + } +#endif + wlan_ralist_add(priv, ra_addr); + + ra_list = wlan_wmm_get_ralist_node(priv, tid, ra_addr); + LEAVE(); + return ra_list; +} + +#ifdef STA_SUPPORT +/** + * @brief Sends wmmac host event + * + * @param priv Pointer to the mlan_private driver data struct + * @param type_str Type of host event + * @param src_addr Pointer to the source Address + * @param tid TID + * @param up User priority + * @param status Status code or Reason code + * + * @return N/A + */ +static void wlan_send_wmmac_host_event(pmlan_private priv, char *type_str, + t_u8 *src_addr, t_u8 tid, t_u8 up, + t_u8 status) +{ + t_u8 event_buf[100]; + mlan_event *pevent; + t_u8 *pout_buf; + + ENTER(); + + /* Format one of the following two output strings: + ** - TSPEC:ADDTS_RSP:[<status code>]:TID=X:UP=Y + ** - TSPEC:DELTS_RX:[<reason code>]:TID=X:UP=Y + */ + pevent = (mlan_event *)event_buf; + pout_buf = pevent->event_buf; + + memcpy_ext(priv->adapter, pout_buf, (t_u8 *)"TSPEC:", 6, 6); + pout_buf += 6; + + memcpy_ext(priv->adapter, pout_buf, (t_u8 *)type_str, + wlan_strlen(type_str), wlan_strlen(type_str)); + pout_buf += wlan_strlen(type_str); + + *pout_buf++ = ':'; + *pout_buf++ = '['; + + if (status >= 100) { + *pout_buf++ = (status / 100) + '0'; + status = (status % 100); + } + + if (status >= 10) { + *pout_buf++ = (status / 10) + '0'; + status = (status % 10); + } + + *pout_buf++ = status + '0'; + + memcpy_ext(priv->adapter, pout_buf, (t_u8 *)"]:TID", 5, 5); + pout_buf += 5; + *pout_buf++ = tid + '0'; + + memcpy_ext(priv->adapter, pout_buf, (t_u8 *)":UP", 3, 3); + pout_buf += 3; + *pout_buf++ = up + '0'; + + *pout_buf = '\0'; + + pevent->bss_index = priv->bss_index; + pevent->event_id = MLAN_EVENT_ID_DRV_REPORT_STRING; + pevent->event_len = wlan_strlen((const char *)(pevent->event_buf)); + + wlan_recv_event(priv, MLAN_EVENT_ID_DRV_REPORT_STRING, pevent); + LEAVE(); +} +#endif /* STA_SUPPORT */ + +/** + * @brief This function gets the highest priority list pointer + * + * @param pmadapter A pointer to mlan_adapter + * @param priv A pointer to mlan_private + * @param tid A pointer to return tid + * + * @return raListTbl + */ +static raListTbl *wlan_wmm_get_highest_priolist_ptr(pmlan_adapter pmadapter, + pmlan_private *priv, + int *tid) +{ + pmlan_private priv_tmp; + raListTbl *ptr, *head; + mlan_bssprio_node *bssprio_node, *bssprio_head; + tid_tbl_t *tid_ptr; + int i, j; + int next_prio = 0; + int next_tid = 0; + ENTER(); + + PRINTM(MDAT_D, "POP\n"); + for (j = pmadapter->priv_num - 1; j >= 0; --j) { + if (!(util_peek_list(pmadapter->pmoal_handle, + &pmadapter->bssprio_tbl[j].bssprio_head, + MNULL, MNULL))) + continue; + + if (pmadapter->bssprio_tbl[j].bssprio_cur == + (mlan_bssprio_node *)&pmadapter->bssprio_tbl[j] + .bssprio_head) { + pmadapter->bssprio_tbl[j].bssprio_cur = + pmadapter->bssprio_tbl[j].bssprio_cur->pnext; + } + + bssprio_head = bssprio_node = + pmadapter->bssprio_tbl[j].bssprio_cur; + + do { + priv_tmp = bssprio_node->priv; + if ((priv_tmp->port_ctrl_mode == MTRUE) && + (priv_tmp->port_open == MFALSE)) { + PRINTM(MINFO, + "get_highest_prio_ptr(): " + "PORT_CLOSED Ignore pkts from BSS%d\n", + priv_tmp->bss_index); + /* Ignore data pkts from a BSS if port is closed + */ + goto next_intf; + } + if (priv_tmp->tx_pause == MTRUE) { + PRINTM(MINFO, + "get_highest_prio_ptr(): " + "TX PASUE Ignore pkts from BSS%d\n", + priv_tmp->bss_index); + /* Ignore data pkts from a BSS if tx pause */ + goto next_intf; + } + + pmadapter->callbacks.moal_spin_lock( + pmadapter->pmoal_handle, + priv_tmp->wmm.ra_list_spinlock); + + for (i = util_scalar_read( + pmadapter->pmoal_handle, + &priv_tmp->wmm.highest_queued_prio, MNULL, + MNULL); + i >= LOW_PRIO_TID; --i) { + tid_ptr = &(priv_tmp) + ->wmm + .tid_tbl_ptr[tos_to_tid[i]]; + if (!util_peek_list(pmadapter->pmoal_handle, + &tid_ptr->ra_list, MNULL, + MNULL)) + continue; + + /* + * Always choose the next ra we transmitted + * last time, this way we pick the ra's in + * round robin fashion. + */ + head = ptr = tid_ptr->ra_list_curr->pnext; + if (ptr == (raListTbl *)&tid_ptr->ra_list) + head = ptr = ptr->pnext; + + do { + if (!ptr->tx_pause && + util_peek_list( + pmadapter->pmoal_handle, + &ptr->buf_head, MNULL, + MNULL)) { + /* Because WMM only support + * BK/BE/VI/VO, we have 8 tid + * We should balance the traffic + * of the same AC */ + if (i % 2) + next_prio = i - 1; + else + next_prio = i + 1; + next_tid = + tos_to_tid[next_prio]; + if (priv_tmp->wmm.pkts_queued + [next_tid] && + (priv_tmp->wmm.pkts_queued + [next_tid] > + priv_tmp->wmm.pkts_paused + [next_tid])) + util_scalar_write( + pmadapter->pmoal_handle, + &priv_tmp->wmm + .highest_queued_prio, + next_prio, + MNULL, MNULL); + else + /* if + * highest_queued_prio > + * i, set it to i */ + util_scalar_conditional_write( + pmadapter->pmoal_handle, + &priv_tmp->wmm + .highest_queued_prio, + MLAN_SCALAR_COND_GREATER_THAN, + i, i, MNULL, + MNULL); + *priv = priv_tmp; + *tid = tos_to_tid[i]; + /* hold priv->ra_list_spinlock + * to maintain ptr */ + PRINTM(MDAT_D, + "get highest prio ptr %p, tid %d\n", + ptr, *tid); + LEAVE(); + return ptr; + } + + ptr = ptr->pnext; + if (ptr == + (raListTbl *)&tid_ptr->ra_list) + ptr = ptr->pnext; + } while (ptr != head); + } + + /* If priv still has packets queued, reset to + * HIGH_PRIO_TID */ + if (util_scalar_read(pmadapter->pmoal_handle, + &priv_tmp->wmm.tx_pkts_queued, + MNULL, MNULL)) + util_scalar_write( + pmadapter->pmoal_handle, + &priv_tmp->wmm.highest_queued_prio, + HIGH_PRIO_TID, MNULL, MNULL); + else + /* No packet at any TID for this priv. Mark as + * such to skip checking TIDs for this priv + * (until pkt is added). */ + util_scalar_write( + pmadapter->pmoal_handle, + &priv_tmp->wmm.highest_queued_prio, + NO_PKT_PRIO_TID, MNULL, MNULL); + + pmadapter->callbacks.moal_spin_unlock( + pmadapter->pmoal_handle, + priv_tmp->wmm.ra_list_spinlock); + + next_intf: + bssprio_node = bssprio_node->pnext; + if (bssprio_node == + (mlan_bssprio_node *)&pmadapter->bssprio_tbl[j] + .bssprio_head) + bssprio_node = bssprio_node->pnext; + pmadapter->bssprio_tbl[j].bssprio_cur = bssprio_node; + } while (bssprio_node != bssprio_head); + } + + LEAVE(); + return MNULL; +} + +/** + * @brief This function gets the number of packets in the Tx queue + * + * @param priv A pointer to mlan_private + * @param ptr A pointer to RA list table + * @param max_buf_size Maximum buffer size + * + * @return Packet count + */ +static int wlan_num_pkts_in_txq(mlan_private *priv, raListTbl *ptr, + int max_buf_size) +{ + int count = 0, total_size = 0; + pmlan_buffer pmbuf; + + ENTER(); + + for (pmbuf = (pmlan_buffer)ptr->buf_head.pnext; + pmbuf != (pmlan_buffer)(&ptr->buf_head); pmbuf = pmbuf->pnext) { + total_size += pmbuf->data_len; + if (total_size < max_buf_size) + ++count; + else + break; + } + + LEAVE(); + return count; +} + +/** + * @brief This function sends a single packet + * + * @param priv A pointer to mlan_private + * @param ptr A pointer to RA list table + * @param ptrindex ptr's TID index + * + * @return N/A + */ +static INLINE void wlan_send_single_packet(pmlan_private priv, raListTbl *ptr, + int ptrindex) +{ + pmlan_buffer pmbuf; + pmlan_buffer pmbuf_next; + mlan_tx_param tx_param; + pmlan_adapter pmadapter = priv->adapter; + mlan_status status = MLAN_STATUS_SUCCESS; + + ENTER(); + + pmbuf = (pmlan_buffer)util_dequeue_list(pmadapter->pmoal_handle, + &ptr->buf_head, MNULL, MNULL); + if (pmbuf) { + PRINTM(MINFO, "Dequeuing the packet %p %p\n", ptr, pmbuf); + priv->wmm.pkts_queued[ptrindex]--; + util_scalar_decrement(pmadapter->pmoal_handle, + &priv->wmm.tx_pkts_queued, MNULL, MNULL); + ptr->total_pkts--; + pmbuf_next = (pmlan_buffer)util_peek_list( + pmadapter->pmoal_handle, &ptr->buf_head, MNULL, MNULL); + pmadapter->callbacks.moal_spin_unlock( + pmadapter->pmoal_handle, priv->wmm.ra_list_spinlock); + + tx_param.next_pkt_len = + ((pmbuf_next) ? pmbuf_next->data_len + sizeof(TxPD) : + 0); + status = wlan_process_tx(priv, pmbuf, &tx_param); + + if (status == MLAN_STATUS_RESOURCE) { + /** Queue the packet back at the head */ + PRINTM(MDAT_D, "Queuing pkt back to raList %p %p\n", + ptr, pmbuf); + pmadapter->callbacks.moal_spin_lock( + pmadapter->pmoal_handle, + priv->wmm.ra_list_spinlock); + + if (!wlan_is_ralist_valid(priv, ptr, ptrindex)) { + pmadapter->callbacks.moal_spin_unlock( + pmadapter->pmoal_handle, + priv->wmm.ra_list_spinlock); + wlan_write_data_complete(pmadapter, pmbuf, + MLAN_STATUS_FAILURE); + LEAVE(); + return; + } + priv->wmm.pkts_queued[ptrindex]++; + util_scalar_increment(pmadapter->pmoal_handle, + &priv->wmm.tx_pkts_queued, MNULL, + MNULL); + util_enqueue_list_head(pmadapter->pmoal_handle, + &ptr->buf_head, + (pmlan_linked_list)pmbuf, MNULL, + MNULL); + + ptr->total_pkts++; + pmbuf->flags |= MLAN_BUF_FLAG_REQUEUED_PKT; + pmadapter->callbacks.moal_spin_unlock( + pmadapter->pmoal_handle, + priv->wmm.ra_list_spinlock); + } else { + pmadapter->callbacks.moal_spin_lock( + pmadapter->pmoal_handle, + priv->wmm.ra_list_spinlock); + if (wlan_is_ralist_valid(priv, ptr, ptrindex)) { + priv->wmm.packets_out[ptrindex]++; + priv->wmm.tid_tbl_ptr[ptrindex].ra_list_curr = + ptr; + } + pmadapter->bssprio_tbl[priv->bss_priority].bssprio_cur = + pmadapter->bssprio_tbl[priv->bss_priority] + .bssprio_cur->pnext; + pmadapter->callbacks.moal_spin_unlock( + pmadapter->pmoal_handle, + priv->wmm.ra_list_spinlock); + } + } else { + pmadapter->callbacks.moal_spin_unlock( + pmadapter->pmoal_handle, priv->wmm.ra_list_spinlock); + PRINTM(MINFO, "Nothing to send\n"); + } + + LEAVE(); +} + +/** + * @brief This function checks if this mlan_buffer is already processed. + * + * @param priv A pointer to mlan_private + * @param ptr A pointer to RA list table + * + * @return MTRUE or MFALSE + */ +static INLINE int wlan_is_ptr_processed(mlan_private *priv, raListTbl *ptr) +{ + pmlan_buffer pmbuf; + + pmbuf = (pmlan_buffer)util_peek_list(priv->adapter->pmoal_handle, + &ptr->buf_head, MNULL, MNULL); + if (pmbuf && (pmbuf->flags & MLAN_BUF_FLAG_REQUEUED_PKT)) + return MTRUE; + + return MFALSE; +} + +/** + * @brief This function sends a single packet that has been processed + * + * @param priv A pointer to mlan_private + * @param ptr A pointer to RA list table + * @param ptrindex ptr's TID index + * + * @return N/A + */ +static INLINE void wlan_send_processed_packet(pmlan_private priv, + raListTbl *ptr, int ptrindex) +{ + pmlan_buffer pmbuf_next = MNULL; + mlan_tx_param tx_param; + pmlan_buffer pmbuf; + pmlan_adapter pmadapter = priv->adapter; + mlan_status ret = MLAN_STATUS_FAILURE; + + pmbuf = (pmlan_buffer)util_dequeue_list(pmadapter->pmoal_handle, + &ptr->buf_head, MNULL, MNULL); + if (pmbuf) { + pmbuf_next = (pmlan_buffer)util_peek_list( + pmadapter->pmoal_handle, &ptr->buf_head, MNULL, MNULL); + pmadapter->callbacks.moal_spin_unlock( + pmadapter->pmoal_handle, priv->wmm.ra_list_spinlock); + tx_param.next_pkt_len = + ((pmbuf_next) ? pmbuf_next->data_len + sizeof(TxPD) : + 0); + + ret = pmadapter->ops.host_to_card(priv, MLAN_TYPE_DATA, pmbuf, + &tx_param); + switch (ret) { +#ifdef USB + case MLAN_STATUS_PRESOURCE: + PRINTM(MINFO, "MLAN_STATUS_PRESOURCE is returned\n"); + break; +#endif + case MLAN_STATUS_RESOURCE: + PRINTM(MINFO, "MLAN_STATUS_RESOURCE is returned\n"); + pmadapter->callbacks.moal_spin_lock( + pmadapter->pmoal_handle, + priv->wmm.ra_list_spinlock); + + if (!wlan_is_ralist_valid(priv, ptr, ptrindex)) { + pmadapter->callbacks.moal_spin_unlock( + pmadapter->pmoal_handle, + priv->wmm.ra_list_spinlock); + wlan_write_data_complete(pmadapter, pmbuf, + MLAN_STATUS_FAILURE); + LEAVE(); + return; + } + util_enqueue_list_head(pmadapter->pmoal_handle, + &ptr->buf_head, + (pmlan_linked_list)pmbuf, MNULL, + MNULL); + + pmbuf->flags |= MLAN_BUF_FLAG_REQUEUED_PKT; + pmadapter->callbacks.moal_spin_unlock( + pmadapter->pmoal_handle, + priv->wmm.ra_list_spinlock); + break; + case MLAN_STATUS_FAILURE: + PRINTM(MERROR, "Error: Failed to write data\n"); + pmadapter->dbg.num_tx_host_to_card_failure++; + pmbuf->status_code = MLAN_ERROR_DATA_TX_FAIL; + wlan_write_data_complete(pmadapter, pmbuf, ret); + break; + case MLAN_STATUS_PENDING: + break; + case MLAN_STATUS_SUCCESS: + DBG_HEXDUMP(MDAT_D, "Tx", + pmbuf->pbuf + pmbuf->data_offset, + MIN(pmbuf->data_len + sizeof(TxPD), + MAX_DATA_DUMP_LEN)); + wlan_write_data_complete(pmadapter, pmbuf, ret); + break; + default: + break; + } + if (ret != MLAN_STATUS_RESOURCE) { + pmadapter->callbacks.moal_spin_lock( + pmadapter->pmoal_handle, + priv->wmm.ra_list_spinlock); + if (wlan_is_ralist_valid(priv, ptr, ptrindex)) { + priv->wmm.packets_out[ptrindex]++; + priv->wmm.tid_tbl_ptr[ptrindex].ra_list_curr = + ptr; + ptr->total_pkts--; + } + pmadapter->bssprio_tbl[priv->bss_priority].bssprio_cur = + pmadapter->bssprio_tbl[priv->bss_priority] + .bssprio_cur->pnext; + priv->wmm.pkts_queued[ptrindex]--; + util_scalar_decrement(pmadapter->pmoal_handle, + &priv->wmm.tx_pkts_queued, MNULL, + MNULL); + pmadapter->callbacks.moal_spin_unlock( + pmadapter->pmoal_handle, + priv->wmm.ra_list_spinlock); + } + } else { + pmadapter->callbacks.moal_spin_unlock( + pmadapter->pmoal_handle, priv->wmm.ra_list_spinlock); + } +} + +/** + * @brief This function dequeues a packet + * + * @param pmadapter A pointer to mlan_adapter + * + * @return MLAN_STATUS_SUCCESS or MLAN_STATUS_FAILURE + */ +static int wlan_dequeue_tx_packet(pmlan_adapter pmadapter) +{ + raListTbl *ptr; + pmlan_private priv = MNULL; + int ptrindex = 0; + t_u8 ra[MLAN_MAC_ADDR_LENGTH]; + int tid_del = 0; + int tid = 0; + mlan_buffer *pmbuf = MNULL; + + ENTER(); + + ptr = wlan_wmm_get_highest_priolist_ptr(pmadapter, &priv, &ptrindex); + if (!ptr) { + LEAVE(); + return MLAN_STATUS_FAILURE; + } + + /* Note:- Spinlock is locked in wlan_wmm_get_highest_priolist_ptr + * when it returns a pointer (for the priv it returns), + * and is unlocked in wlan_send_processed_packet, + * wlan_send_single_packet or wlan_11n_aggregate_pkt. + * The spinlock would be required for some parts of both of function. + * But, the the bulk of these function will execute w/o spinlock. + * Unlocking the spinlock inside these function will help us avoid + * taking the spinlock again, check to see if the ptr is still + * valid and then proceed. This is done purely to increase + * execution time. */ + + /* Note:- Also, anybody adding code which does not get into + * wlan_send_processed_packet, wlan_send_single_packet, or + * wlan_11n_aggregate_pkt should make sure ra_list_spinlock + * is freed. Otherwise there would be a lock up. */ + + tid = wlan_get_tid(priv->adapter, ptr); + if (tid >= MAX_NUM_TID) + tid = wlan_wmm_downgrade_tid(priv, tid); + + if (wlan_is_ptr_processed(priv, ptr)) { + wlan_send_processed_packet(priv, ptr, ptrindex); + LEAVE(); + return MLAN_STATUS_SUCCESS; + } + if (ptr->del_ba_count >= DEL_BA_THRESHOLD) + wlan_update_del_ba_count(priv, ptr); + if (pmadapter->tp_state_on) { + pmbuf = (pmlan_buffer)util_peek_list( + pmadapter->pmoal_handle, &ptr->buf_head, MNULL, MNULL); + if (pmbuf) { + pmadapter->callbacks.moal_tp_accounting( + pmadapter->pmoal_handle, pmbuf->pdesc, 3); + if (pmadapter->tp_state_drop_point == 3) { + pmbuf = (pmlan_buffer)util_dequeue_list( + pmadapter->pmoal_handle, &ptr->buf_head, + MNULL, MNULL); + PRINTM(MERROR, "Dequeuing the packet %p %p\n", + ptr, pmbuf); + priv->wmm.pkts_queued[ptrindex]--; + util_scalar_decrement(pmadapter->pmoal_handle, + &priv->wmm.tx_pkts_queued, + MNULL, MNULL); + ptr->total_pkts--; + pmadapter->callbacks.moal_spin_unlock( + pmadapter->pmoal_handle, + priv->wmm.ra_list_spinlock); + wlan_write_data_complete(pmadapter, pmbuf, + MLAN_STATUS_SUCCESS); + LEAVE(); + return MLAN_STATUS_SUCCESS; + } + } + } + if (!ptr->is_11n_enabled || + (ptr->ba_status || ptr->del_ba_count >= DEL_BA_THRESHOLD) +#ifdef STA_SUPPORT + || priv->wps.session_enable +#endif /* STA_SUPPORT */ + ) { + if (ptr->is_11n_enabled && ptr->ba_status && + ptr->amsdu_in_ampdu && + wlan_is_amsdu_allowed(priv, ptr, tid) && + (wlan_num_pkts_in_txq(priv, ptr, pmadapter->tx_buf_size) >= + MIN_NUM_AMSDU)) { + wlan_11n_aggregate_pkt(priv, ptr, priv->intf_hr_len, + ptrindex); + } else + wlan_send_single_packet(priv, ptr, ptrindex); + } else { + if (wlan_is_ampdu_allowed(priv, ptr, tid) && + (ptr->packet_count > ptr->ba_packet_threshold)) { + if (wlan_is_bastream_avail(priv)) { + PRINTM(MINFO, + "BA setup threshold %d reached. tid=%d\n", + ptr->packet_count, tid); + if (!wlan_11n_get_txbastream_tbl( + priv, tid, ptr->ra, MFALSE)) { + wlan_11n_create_txbastream_tbl( + priv, ptr->ra, tid, + BA_STREAM_SETUP_INPROGRESS); + wlan_send_addba(priv, tid, ptr->ra); + } + } else if (wlan_find_stream_to_delete(priv, ptr, tid, + &tid_del, ra)) { + PRINTM(MDAT_D, "tid_del=%d tid=%d\n", tid_del, + tid); + if (!wlan_11n_get_txbastream_tbl( + priv, tid, ptr->ra, MFALSE)) { + wlan_11n_create_txbastream_tbl( + priv, ptr->ra, tid, + BA_STREAM_SETUP_INPROGRESS); + wlan_send_delba(priv, MNULL, tid_del, + ra, 1); + } + } + } + if (wlan_is_amsdu_allowed(priv, ptr, tid) && + (wlan_num_pkts_in_txq(priv, ptr, pmadapter->tx_buf_size) >= + MIN_NUM_AMSDU)) { + wlan_11n_aggregate_pkt(priv, ptr, priv->intf_hr_len, + ptrindex); + } else { + wlan_send_single_packet(priv, ptr, ptrindex); + } + } + + LEAVE(); + return MLAN_STATUS_SUCCESS; +} + +/** + * @brief update tx_pause flag in ra_list + * + * @param priv A pointer to mlan_private + * @param mac peer mac address + * @param tx_pause tx_pause flag (0/1) + * + * + * @return packets queued for this mac + */ +t_u16 wlan_update_ralist_tx_pause(pmlan_private priv, t_u8 *mac, t_u8 tx_pause) +{ + raListTbl *ra_list; + int i; + pmlan_adapter pmadapter = priv->adapter; + t_u32 pkt_cnt = 0; + t_u32 tx_pkts_queued = 0; + ENTER(); + + pmadapter->callbacks.moal_spin_lock(pmadapter->pmoal_handle, + priv->wmm.ra_list_spinlock); + for (i = 0; i < MAX_NUM_TID; ++i) { + ra_list = wlan_wmm_get_ralist_node(priv, i, mac); + if (ra_list && ra_list->tx_pause != tx_pause) { + pkt_cnt += ra_list->total_pkts; + ra_list->tx_pause = tx_pause; + if (tx_pause) + priv->wmm.pkts_paused[i] += ra_list->total_pkts; + else + priv->wmm.pkts_paused[i] -= ra_list->total_pkts; + } + } + if (pkt_cnt) { + tx_pkts_queued = util_scalar_read(pmadapter->pmoal_handle, + &priv->wmm.tx_pkts_queued, + MNULL, MNULL); + if (tx_pause) + tx_pkts_queued -= pkt_cnt; + else + tx_pkts_queued += pkt_cnt; + util_scalar_write(priv->adapter->pmoal_handle, + &priv->wmm.tx_pkts_queued, tx_pkts_queued, + MNULL, MNULL); + util_scalar_write(priv->adapter->pmoal_handle, + &priv->wmm.highest_queued_prio, HIGH_PRIO_TID, + MNULL, MNULL); + } + pmadapter->callbacks.moal_spin_unlock(pmadapter->pmoal_handle, + priv->wmm.ra_list_spinlock); + LEAVE(); + return pkt_cnt; +} + +#ifdef STA_SUPPORT +#endif /* STA_SUPPORT */ +/******************************************************** + Global Functions +********************************************************/ + +/** + * @brief Get the threshold value for BA setup using system time. + * + * @param pmadapter Pointer to the mlan_adapter structure + * + * @return threshold value. + */ +t_u8 wlan_get_random_ba_threshold(pmlan_adapter pmadapter) +{ + t_u32 sec, usec; + t_u8 ba_threshold = 0; + + ENTER(); + + /* setup ba_packet_threshold here random number between + [BA_SETUP_PACKET_OFFSET, + BA_SETUP_PACKET_OFFSET+BA_SETUP_MAX_PACKET_THRESHOLD-1] */ + +#define BA_SETUP_MAX_PACKET_THRESHOLD 16 + + pmadapter->callbacks.moal_get_system_time(pmadapter->pmoal_handle, &sec, + &usec); + sec = (sec & 0xFFFF) + (sec >> 16); + usec = (usec & 0xFFFF) + (usec >> 16); + + ba_threshold = (((sec << 16) + usec) % BA_SETUP_MAX_PACKET_THRESHOLD) + + pmadapter->min_ba_threshold; + PRINTM(MINFO, "pmadapter->min_ba_threshold = %d\n", + pmadapter->min_ba_threshold); + PRINTM(MINFO, "setup BA after %d packets\n", ba_threshold); + + LEAVE(); + return ba_threshold; +} + +/** + * @brief This function cleans Tx/Rx queues + * + * @param priv A pointer to mlan_private + * + * @return N/A + */ +t_void wlan_clean_txrx(pmlan_private priv) +{ + mlan_adapter *pmadapter = priv->adapter; + t_u8 i = 0; + + ENTER(); + wlan_cleanup_bypass_txq(priv); + wlan_11n_cleanup_reorder_tbl(priv); + wlan_11n_deleteall_txbastream_tbl(priv); +#if defined(USB) + if (IS_USB(pmadapter->card_type)) + wlan_reset_usb_tx_aggr(priv->adapter); +#endif +#ifdef PCIE + if (IS_PCIE(pmadapter->card_type)) + wlan_clean_pcie_ring_buf(priv->adapter); +#endif + pmadapter->callbacks.moal_spin_lock(pmadapter->pmoal_handle, + priv->wmm.ra_list_spinlock); + wlan_wmm_cleanup_queues(priv); + wlan_wmm_delete_all_ralist(priv); + memcpy_ext(pmadapter, tos_to_tid, ac_to_tid, sizeof(tos_to_tid), + sizeof(tos_to_tid)); + for (i = 0; i < MAX_NUM_TID; i++) + tos_to_tid_inv[tos_to_tid[i]] = (t_u8)i; +#ifdef UAP_SUPPORT + priv->num_drop_pkts = 0; +#endif +#ifdef SDIO + if (IS_SD(pmadapter->card_type)) { + memset(pmadapter, pmadapter->pcard_sd->mpa_tx_count, 0, + sizeof(pmadapter->pcard_sd->mpa_tx_count)); + pmadapter->pcard_sd->mpa_sent_no_ports = 0; + pmadapter->pcard_sd->mpa_sent_last_pkt = 0; + memset(pmadapter, pmadapter->pcard_sd->mpa_rx_count, 0, + sizeof(pmadapter->pcard_sd->mpa_rx_count)); + } +#endif + pmadapter->callbacks.moal_spin_unlock(pmadapter->pmoal_handle, + priv->wmm.ra_list_spinlock); + + LEAVE(); +} + +/** + * @brief Set the WMM queue priorities to their default values + * + * @param priv Pointer to the mlan_private driver data struct + * + * @return N/A + */ +void wlan_wmm_default_queue_priorities(pmlan_private priv) +{ + ENTER(); + + /* Default queue priorities: VO->VI->BE->BK */ + priv->wmm.queue_priority[0] = WMM_AC_VO; + priv->wmm.queue_priority[1] = WMM_AC_VI; + priv->wmm.queue_priority[2] = WMM_AC_BE; + priv->wmm.queue_priority[3] = WMM_AC_BK; + + LEAVE(); +} + +/** + * @brief Initialize WMM priority queues + * + * @param priv Pointer to the mlan_private driver data struct + * @param pwmm_ie Pointer to the IEEEtypes_WmmParameter_t data struct + * + * @return N/A + */ +void wlan_wmm_setup_queue_priorities(pmlan_private priv, + IEEEtypes_WmmParameter_t *pwmm_ie) +{ + t_u16 cw_min, avg_back_off, tmp[4]; + t_u32 i, j, num_ac; + t_u8 ac_idx; + + ENTER(); + + if (!pwmm_ie || priv->wmm_enabled == MFALSE) { + /* WMM is not enabled, just set the defaults and return */ + wlan_wmm_default_queue_priorities(priv); + LEAVE(); + return; + } + memset(priv->adapter, tmp, 0, sizeof(tmp)); + + HEXDUMP("WMM: setup_queue_priorities: param IE", (t_u8 *)pwmm_ie, + sizeof(IEEEtypes_WmmParameter_t)); + + PRINTM(MINFO, + "WMM Parameter IE: version=%d, " + "qos_info Parameter Set Count=%d, Reserved=%#x\n", + pwmm_ie->vend_hdr.version, pwmm_ie->qos_info.para_set_count, + pwmm_ie->reserved); + + for (num_ac = 0; num_ac < NELEMENTS(pwmm_ie->ac_params); num_ac++) { + cw_min = (1 << pwmm_ie->ac_params[num_ac].ecw.ecw_min) - 1; + avg_back_off = (cw_min >> 1) + + pwmm_ie->ac_params[num_ac].aci_aifsn.aifsn; + + ac_idx = wmm_aci_to_qidx_map[pwmm_ie->ac_params[num_ac] + .aci_aifsn.aci]; + priv->wmm.queue_priority[ac_idx] = ac_idx; + tmp[ac_idx] = avg_back_off; + + PRINTM(MCMND, "WMM: CWmax=%d CWmin=%d Avg Back-off=%d\n", + (1 << pwmm_ie->ac_params[num_ac].ecw.ecw_max) - 1, + cw_min, avg_back_off); + PRINTM_AC(&pwmm_ie->ac_params[num_ac]); + } + + HEXDUMP("WMM: avg_back_off", (t_u8 *)tmp, sizeof(tmp)); + HEXDUMP("WMM: queue_priority", priv->wmm.queue_priority, + sizeof(priv->wmm.queue_priority)); + + /* Bubble sort */ + for (i = 0; i < num_ac; i++) { + for (j = 1; j < num_ac - i; j++) { + if (tmp[j - 1] > tmp[j]) { + SWAP_U16(tmp[j - 1], tmp[j]); + SWAP_U8(priv->wmm.queue_priority[j - 1], + priv->wmm.queue_priority[j]); + } else if (tmp[j - 1] == tmp[j]) { + if (priv->wmm.queue_priority[j - 1] < + priv->wmm.queue_priority[j]) { + SWAP_U8(priv->wmm.queue_priority[j - 1], + priv->wmm.queue_priority[j]); + } + } + } + } + + wlan_wmm_queue_priorities_tid(priv, priv->wmm.queue_priority); + + HEXDUMP("WMM: avg_back_off, sort", (t_u8 *)tmp, sizeof(tmp)); + DBG_HEXDUMP(MCMD_D, "WMM: queue_priority, sort", + priv->wmm.queue_priority, sizeof(priv->wmm.queue_priority)); + LEAVE(); +} + +/** + * @brief Downgrade WMM priority queue + * + * @param priv Pointer to the mlan_private driver data struct + * + * @return N/A + */ +void wlan_wmm_setup_ac_downgrade(pmlan_private priv) +{ + int ac_val; + + ENTER(); + + PRINTM(MINFO, "WMM: AC Priorities: BK(0), BE(1), VI(2), VO(3)\n"); + + if (priv->wmm_enabled == MFALSE) { + /* WMM is not enabled, default priorities */ + for (ac_val = WMM_AC_BK; ac_val <= WMM_AC_VO; ac_val++) { + priv->wmm.ac_down_graded_vals[ac_val] = + (mlan_wmm_ac_e)ac_val; + } + } else { + for (ac_val = WMM_AC_BK; ac_val <= WMM_AC_VO; ac_val++) { + priv->wmm.ac_down_graded_vals[ac_val] = + wlan_wmm_eval_downgrade_ac( + priv, (mlan_wmm_ac_e)ac_val); + PRINTM(MINFO, "WMM: AC PRIO %d maps to %d\n", ac_val, + priv->wmm.ac_down_graded_vals[ac_val]); + } + } + + LEAVE(); +} + +/** + * @brief Allocate and add a RA list for all TIDs with the given RA + * + * @param priv Pointer to the mlan_private driver data struct + * @param ra Address of the receiver STA (AP in case of infra) + * + * @return N/A + */ +void wlan_ralist_add(mlan_private *priv, t_u8 *ra) +{ + int i; + raListTbl *ra_list; + pmlan_adapter pmadapter = priv->adapter; + + ENTER(); + + for (i = 0; i < MAX_NUM_TID; ++i) { + ra_list = wlan_wmm_allocate_ralist_node(pmadapter, ra); + PRINTM(MINFO, "Creating RA List %p for tid %d\n", ra_list, i); + if (!ra_list) + break; + ra_list->max_amsdu = 0; + ra_list->ba_status = BA_STREAM_NOT_SETUP; + ra_list->amsdu_in_ampdu = MFALSE; + if (queuing_ra_based(priv)) { + ra_list->is_11n_enabled = wlan_is_11n_enabled(priv, ra); + if (ra_list->is_11n_enabled) + ra_list->max_amsdu = + get_station_max_amsdu_size(priv, ra); + ra_list->tx_pause = wlan_is_tx_pause(priv, ra); + } else { + ra_list->is_11n_enabled = IS_11N_ENABLED(priv); + if (ra_list->is_11n_enabled) + ra_list->max_amsdu = priv->max_amsdu; + } + + PRINTM_NETINTF(MDATA, priv); + PRINTM(MDATA, "ralist %p: is_11n_enabled=%d max_amsdu=%d\n", + ra_list, ra_list->is_11n_enabled, ra_list->max_amsdu); + + if (ra_list->is_11n_enabled) { + ra_list->packet_count = 0; + ra_list->ba_packet_threshold = + wlan_get_random_ba_threshold(pmadapter); + } + + util_enqueue_list_tail(pmadapter->pmoal_handle, + &priv->wmm.tid_tbl_ptr[i].ra_list, + (pmlan_linked_list)ra_list, MNULL, + MNULL); + + if (!priv->wmm.tid_tbl_ptr[i].ra_list_curr) + priv->wmm.tid_tbl_ptr[i].ra_list_curr = ra_list; + } + + LEAVE(); +} + +/** + * @brief Initialize the WMM parameter. + * + * @param pmadapter Pointer to the mlan_adapter data structure + * + * @return N/A + */ +t_void wlan_init_wmm_param(pmlan_adapter pmadapter) +{ + /* Reuse the same structure of WmmAcParameters_t for configuration + * purpose here. the definition of acm bit is changed to ucm (user + * configuration mode) FW will take the setting of + * aifsn,ecw_max,ecw_min, tx_op_limit only when ucm is set to 1. + * othewise the default setting/behavoir in firmware will be used. + */ + pmadapter->ac_params[AC_BE].aci_aifsn.acm = 0; + pmadapter->ac_params[AC_BE].aci_aifsn.aci = AC_BE; + pmadapter->ac_params[AC_BE].aci_aifsn.aifsn = 3; + pmadapter->ac_params[AC_BE].ecw.ecw_max = 10; + pmadapter->ac_params[AC_BE].ecw.ecw_min = 4; + pmadapter->ac_params[AC_BE].tx_op_limit = 0; + + pmadapter->ac_params[AC_BK].aci_aifsn.acm = 0; + pmadapter->ac_params[AC_BK].aci_aifsn.aci = AC_BK; + pmadapter->ac_params[AC_BK].aci_aifsn.aifsn = 7; + pmadapter->ac_params[AC_BK].ecw.ecw_max = 10; + pmadapter->ac_params[AC_BK].ecw.ecw_min = 4; + pmadapter->ac_params[AC_BK].tx_op_limit = 0; + + pmadapter->ac_params[AC_VI].aci_aifsn.acm = 0; + pmadapter->ac_params[AC_VI].aci_aifsn.aci = AC_VI; + pmadapter->ac_params[AC_VI].aci_aifsn.aifsn = 2; + pmadapter->ac_params[AC_VI].ecw.ecw_max = 4; + pmadapter->ac_params[AC_VI].ecw.ecw_min = 3; + pmadapter->ac_params[AC_VI].tx_op_limit = 188; + + pmadapter->ac_params[AC_VO].aci_aifsn.acm = 0; + pmadapter->ac_params[AC_VO].aci_aifsn.aci = AC_VO; + pmadapter->ac_params[AC_VO].aci_aifsn.aifsn = 2; + pmadapter->ac_params[AC_VO].ecw.ecw_max = 3; + pmadapter->ac_params[AC_VO].ecw.ecw_min = 2; + pmadapter->ac_params[AC_VO].tx_op_limit = 102; +} + +/** + * @brief Initialize the WMM state information and the WMM data path queues. + * + * @param pmadapter Pointer to the mlan_adapter data structure + * + * @return N/A + */ +t_void wlan_wmm_init(pmlan_adapter pmadapter) +{ + int i, j; + pmlan_private priv; + + ENTER(); + + for (j = 0; j < pmadapter->priv_num; ++j) { + priv = pmadapter->priv[j]; + if (priv) { + for (i = 0; i < MAX_NUM_TID; ++i) { + priv->aggr_prio_tbl[i].amsdu = + tos_to_tid_inv[i]; + priv->aggr_prio_tbl[i].ampdu_ap = + priv->aggr_prio_tbl[i].ampdu_user = + tos_to_tid_inv[i]; + priv->ibss_ampdu[i] = + priv->aggr_prio_tbl[i].ampdu_user; + priv->wmm.pkts_queued[i] = 0; + priv->wmm.pkts_paused[i] = 0; + priv->wmm.tid_tbl_ptr[i].ra_list_curr = MNULL; + } + priv->wmm.drv_pkt_delay_max = WMM_DRV_DELAY_MAX; + + priv->aggr_prio_tbl[6].amsdu = BA_STREAM_NOT_ALLOWED; + priv->aggr_prio_tbl[7].amsdu = BA_STREAM_NOT_ALLOWED; + priv->aggr_prio_tbl[6].ampdu_ap = + priv->aggr_prio_tbl[6].ampdu_user = + BA_STREAM_NOT_ALLOWED; + priv->ibss_ampdu[6] = BA_STREAM_NOT_ALLOWED; + + priv->aggr_prio_tbl[7].ampdu_ap = + priv->aggr_prio_tbl[7].ampdu_user = + BA_STREAM_NOT_ALLOWED; + priv->ibss_ampdu[7] = BA_STREAM_NOT_ALLOWED; + + priv->add_ba_param.timeout = + MLAN_DEFAULT_BLOCK_ACK_TIMEOUT; +#ifdef STA_SUPPORT + if (priv->bss_type == MLAN_BSS_TYPE_STA) { + priv->add_ba_param.tx_win_size = + MLAN_STA_AMPDU_DEF_TXWINSIZE; + priv->add_ba_param.rx_win_size = + MLAN_STA_AMPDU_DEF_RXWINSIZE; + } +#endif +#ifdef WIFI_DIRECT_SUPPORT + if (priv->bss_type == MLAN_BSS_TYPE_WIFIDIRECT) { + priv->add_ba_param.tx_win_size = + MLAN_WFD_AMPDU_DEF_TXRXWINSIZE; + priv->add_ba_param.rx_win_size = + MLAN_WFD_AMPDU_DEF_TXRXWINSIZE; + } +#endif +#ifdef UAP_SUPPORT + if (priv->bss_type == MLAN_BSS_TYPE_UAP) { + priv->add_ba_param.tx_win_size = + MLAN_UAP_AMPDU_DEF_TXWINSIZE; + priv->add_ba_param.rx_win_size = + MLAN_UAP_AMPDU_DEF_RXWINSIZE; + } +#endif + priv->user_rxwinsize = priv->add_ba_param.rx_win_size; + priv->add_ba_param.tx_amsdu = MTRUE; + priv->add_ba_param.rx_amsdu = MTRUE; + memset(priv->adapter, priv->rx_seq, 0xff, + sizeof(priv->rx_seq)); + wlan_wmm_default_queue_priorities(priv); + } + } + + LEAVE(); +} + +/** + * @brief Setup the queue priorities and downgrade any queues as required + * by the WMM info. Setups default values if WMM is not active + * for this association. + * + * @param priv Pointer to the mlan_private driver data struct + * + * @return N/A + */ +void wlan_wmm_setup_queues(pmlan_private priv) +{ + ENTER(); + wlan_wmm_setup_queue_priorities(priv, MNULL); + wlan_wmm_setup_ac_downgrade(priv); + LEAVE(); +} + +#ifdef STA_SUPPORT +/** + * @brief Send a command to firmware to retrieve the current WMM status + * + * @param priv Pointer to the mlan_private driver data struct + * + * @return MLAN_STATUS_SUCCESS; MLAN_STATUS_FAILURE + */ +mlan_status wlan_cmd_wmm_status_change(pmlan_private priv) +{ + mlan_status ret = MLAN_STATUS_SUCCESS; + + ENTER(); + + ret = wlan_prepare_cmd(priv, HostCmd_CMD_WMM_GET_STATUS, 0, 0, 0, + MNULL); + LEAVE(); + return ret; +} +#endif + +/** + * @brief Check if wmm TX queue is empty + * + * @param pmadapter Pointer to the mlan_adapter driver data struct + * + * @return MFALSE if not empty; MTRUE if empty + */ +int wlan_wmm_lists_empty(pmlan_adapter pmadapter) +{ + int j; + pmlan_private priv; + + ENTER(); + + for (j = 0; j < pmadapter->priv_num; ++j) { + priv = pmadapter->priv[j]; + if (priv) { + if ((priv->port_ctrl_mode == MTRUE) && + (priv->port_open == MFALSE)) { + PRINTM(MINFO, + "wmm_lists_empty: PORT_CLOSED Ignore pkts from BSS%d\n", + j); + continue; + } + if (priv->tx_pause) + continue; + + if (util_scalar_read( + pmadapter->pmoal_handle, + &priv->wmm.tx_pkts_queued, + pmadapter->callbacks.moal_spin_lock, + pmadapter->callbacks.moal_spin_unlock)) { + LEAVE(); + return MFALSE; + } + } + } + + LEAVE(); + return MTRUE; +} + +/** + * @brief Get ralist node + * + * @param priv Pointer to the mlan_private driver data struct + * @param tid TID + * @param ra_addr Pointer to the route address + * + * @return ra_list or MNULL + */ +raListTbl *wlan_wmm_get_ralist_node(pmlan_private priv, t_u8 tid, t_u8 *ra_addr) +{ + raListTbl *ra_list; + ENTER(); + ra_list = + (raListTbl *)util_peek_list(priv->adapter->pmoal_handle, + &priv->wmm.tid_tbl_ptr[tid].ra_list, + MNULL, MNULL); + while (ra_list && + (ra_list != (raListTbl *)&priv->wmm.tid_tbl_ptr[tid].ra_list)) { + if (!memcmp(priv->adapter, ra_list->ra, ra_addr, + MLAN_MAC_ADDR_LENGTH)) { + LEAVE(); + return ra_list; + } + ra_list = ra_list->pnext; + } + LEAVE(); + return MNULL; +} + +/** + * @brief Check if RA list is valid or not + * + * @param priv Pointer to the mlan_private driver data struct + * @param ra_list Pointer to raListTbl + * @param ptrindex TID pointer index + * + * @return MTRUE- valid. MFALSE- invalid. + */ +int wlan_is_ralist_valid(mlan_private *priv, raListTbl *ra_list, int ptrindex) +{ + raListTbl *rlist; + + ENTER(); + + rlist = (raListTbl *)util_peek_list( + priv->adapter->pmoal_handle, + &priv->wmm.tid_tbl_ptr[ptrindex].ra_list, MNULL, MNULL); + + while (rlist && + (rlist != + (raListTbl *)&priv->wmm.tid_tbl_ptr[ptrindex].ra_list)) { + if (rlist == ra_list) { + LEAVE(); + return MTRUE; + } + + rlist = rlist->pnext; + } + LEAVE(); + return MFALSE; +} + +/** + * @brief Update an existing raList with a new RA and 11n capability + * + * @param priv Pointer to the mlan_private driver data struct + * @param old_ra Old receiver address + * @param new_ra New receiver address + * + * @return integer count of updated nodes + */ +int wlan_ralist_update(mlan_private *priv, t_u8 *old_ra, t_u8 *new_ra) +{ + t_u8 tid; + int update_count; + raListTbl *ra_list; + + ENTER(); + + update_count = 0; + + for (tid = 0; tid < MAX_NUM_TID; ++tid) { + ra_list = wlan_wmm_get_ralist_node(priv, tid, old_ra); + + if (ra_list) { + update_count++; + + if (queuing_ra_based(priv)) { + ra_list->is_11n_enabled = + wlan_is_11n_enabled(priv, new_ra); + if (ra_list->is_11n_enabled) + ra_list->max_amsdu = + get_station_max_amsdu_size( + priv, new_ra); + } else { + ra_list->is_11n_enabled = IS_11N_ENABLED(priv); + if (ra_list->is_11n_enabled) + ra_list->max_amsdu = priv->max_amsdu; + } + + ra_list->tx_pause = MFALSE; + ra_list->packet_count = 0; + ra_list->ba_packet_threshold = + wlan_get_random_ba_threshold(priv->adapter); + ra_list->amsdu_in_ampdu = MFALSE; + ra_list->ba_status = BA_STREAM_NOT_SETUP; + PRINTM(MINFO, + "ralist_update: %p, %d, " MACSTR "-->" MACSTR + "\n", + ra_list, ra_list->is_11n_enabled, + MAC2STR(ra_list->ra), MAC2STR(new_ra)); + + memcpy_ext(priv->adapter, ra_list->ra, new_ra, + MLAN_MAC_ADDR_LENGTH, MLAN_MAC_ADDR_LENGTH); + } + } + + LEAVE(); + return update_count; +} + +/** + * @brief Add packet to WMM queue + * + * @param pmadapter Pointer to the mlan_adapter driver data struct + * @param pmbuf Pointer to the mlan_buffer data struct + * + * @return N/A + */ +t_void wlan_wmm_add_buf_txqueue(pmlan_adapter pmadapter, pmlan_buffer pmbuf) +{ + pmlan_private priv = pmadapter->priv[pmbuf->bss_index]; + t_u32 tid; + raListTbl *ra_list; + t_u8 ra[MLAN_MAC_ADDR_LENGTH], tid_down; +#ifdef UAP_SUPPORT + psta_node sta_ptr = MNULL; +#endif + + ENTER(); + + pmbuf->buf_type = MLAN_BUF_TYPE_DATA; + if (!priv->media_connected) { + PRINTM_NETINTF(MWARN, priv); + PRINTM(MWARN, "Drop packet %p in disconnect state\n", pmbuf); + wlan_write_data_complete(pmadapter, pmbuf, MLAN_STATUS_FAILURE); + LEAVE(); + return; + } + tid = pmbuf->priority; + pmadapter->callbacks.moal_spin_lock(pmadapter->pmoal_handle, + priv->wmm.ra_list_spinlock); + tid_down = wlan_wmm_downgrade_tid(priv, tid); + + /* In case of infra as we have already created the list during + association we just don't have to call get_queue_raptr, we will have + only 1 raptr for a tid in case of infra */ + if (!queuing_ra_based(priv)) { + ra_list = (raListTbl *)util_peek_list( + pmadapter->pmoal_handle, + &priv->wmm.tid_tbl_ptr[tid_down].ra_list, MNULL, MNULL); + } else { + memcpy_ext(pmadapter, ra, pmbuf->pbuf + pmbuf->data_offset, + MLAN_MAC_ADDR_LENGTH, MLAN_MAC_ADDR_LENGTH); + /** put multicast/broadcast packet in the same ralist */ + if (ra[0] & 0x01) + memset(pmadapter, ra, 0xff, sizeof(ra)); +#ifdef UAP_SUPPORT + else if (priv->bss_type == MLAN_BSS_TYPE_UAP) { + sta_ptr = wlan_get_station_entry(priv, ra); + if (sta_ptr) { + if (!sta_ptr->is_wmm_enabled && + !priv->is_11ac_enabled) { + tid_down = wlan_wmm_downgrade_tid(priv, + 0xff); + } + } + } +#endif + ra_list = wlan_wmm_get_queue_raptr(priv, tid_down, ra); + } + + if (!ra_list) { + PRINTM_NETINTF(MWARN, priv); + PRINTM(MWARN, + "Drop packet %p, ra_list=%p, media_connected=%d\n", + pmbuf, ra_list, priv->media_connected); + pmadapter->callbacks.moal_spin_unlock( + pmadapter->pmoal_handle, priv->wmm.ra_list_spinlock); + wlan_write_data_complete(pmadapter, pmbuf, MLAN_STATUS_FAILURE); + LEAVE(); + return; + } + + PRINTM_NETINTF(MDATA, priv); + PRINTM(MDATA, + "Adding pkt %p (priority=%d, tid_down=%d) to ra_list %p\n", + pmbuf, pmbuf->priority, tid_down, ra_list); + util_enqueue_list_tail(pmadapter->pmoal_handle, &ra_list->buf_head, + (pmlan_linked_list)pmbuf, MNULL, MNULL); + + ra_list->total_pkts++; + ra_list->packet_count++; + + priv->wmm.pkts_queued[tid_down]++; + if (ra_list->tx_pause) { + priv->wmm.pkts_paused[tid_down]++; + } else { + util_scalar_increment(pmadapter->pmoal_handle, + &priv->wmm.tx_pkts_queued, MNULL, MNULL); + /* if highest_queued_prio < prio(tid_down), set it to + * prio(tid_down) */ + util_scalar_conditional_write( + pmadapter->pmoal_handle, &priv->wmm.highest_queued_prio, + MLAN_SCALAR_COND_LESS_THAN, tos_to_tid_inv[tid_down], + tos_to_tid_inv[tid_down], MNULL, MNULL); + } + /* Record the current time the packet was queued; used to determine + * the amount of time the packet was queued in the driver before it + * was sent to the firmware. The delay is then sent along with the + * packet to the firmware for aggregate delay calculation for stats + * and MSDU lifetime expiry. + */ + pmadapter->callbacks.moal_get_system_time( + pmadapter->pmoal_handle, &pmbuf->in_ts_sec, &pmbuf->in_ts_usec); + pmadapter->callbacks.moal_spin_unlock(pmadapter->pmoal_handle, + priv->wmm.ra_list_spinlock); + + LEAVE(); +} + +#ifdef STA_SUPPORT +/** + * @brief Process the GET_WMM_STATUS command response from firmware + * + * The GET_WMM_STATUS response may contain multiple TLVs for: + * - AC Queue status TLVs + * - Current WMM Parameter IE TLV + * - Admission Control action frame TLVs + * + * This function parses the TLVs and then calls further functions + * to process any changes in the queue prioritize or state. + * + * @param priv Pointer to the mlan_private driver data struct + * @param ptlv Pointer to the tlv block returned in the response. + * @param resp_len Length of TLV block + * + * @return MLAN_STATUS_SUCCESS + */ +mlan_status wlan_ret_wmm_get_status(pmlan_private priv, t_u8 *ptlv, + int resp_len) +{ + t_u8 *pcurrent = ptlv; + t_u32 tlv_len; + t_u8 send_wmm_event; + MrvlIEtypes_Data_t *ptlv_hdr; + MrvlIEtypes_WmmQueueStatus_t *ptlv_wmm_q_status; + IEEEtypes_WmmParameter_t *pwmm_param_ie = MNULL; + WmmAcStatus_t *pac_status; + + MrvlIETypes_ActionFrame_t *ptlv_action; + IEEEtypes_Action_WMM_AddTsRsp_t *padd_ts_rsp; + IEEEtypes_Action_WMM_DelTs_t *pdel_ts; + + ENTER(); + + send_wmm_event = MFALSE; + + PRINTM(MINFO, "WMM: WMM_GET_STATUS cmdresp received: %d\n", resp_len); + HEXDUMP("CMD_RESP: WMM_GET_STATUS", pcurrent, resp_len); + + while (resp_len >= sizeof(ptlv_hdr->header)) { + ptlv_hdr = (MrvlIEtypes_Data_t *)pcurrent; + tlv_len = wlan_le16_to_cpu(ptlv_hdr->header.len); + if ((tlv_len + sizeof(ptlv_hdr->header)) > resp_len) { + PRINTM(MERROR, + "WMM get status: Error in processing TLV buffer\n"); + resp_len = 0; + continue; + } + + switch (wlan_le16_to_cpu(ptlv_hdr->header.type)) { + case TLV_TYPE_WMMQSTATUS: + ptlv_wmm_q_status = + (MrvlIEtypes_WmmQueueStatus_t *)ptlv_hdr; + PRINTM(MEVENT, "WMM_STATUS: QSTATUS TLV: %d\n", + ptlv_wmm_q_status->queue_index); + + PRINTM(MINFO, + "CMD_RESP: WMM_GET_STATUS: QSTATUS TLV: %d, %d, %d\n", + ptlv_wmm_q_status->queue_index, + ptlv_wmm_q_status->flow_required, + ptlv_wmm_q_status->disabled); + + pac_status = + &priv->wmm.ac_status[ptlv_wmm_q_status + ->queue_index]; + pac_status->disabled = ptlv_wmm_q_status->disabled; + pac_status->flow_required = + ptlv_wmm_q_status->flow_required; + pac_status->flow_created = + ptlv_wmm_q_status->flow_created; + break; + + case TLV_TYPE_VENDOR_SPECIFIC_IE: /* WMM_IE */ + /* + * Point the regular IEEE IE 2 bytes into the NXP IE + * and setup the IEEE IE type and length byte fields + */ + + PRINTM(MEVENT, "WMM STATUS: WMM IE\n"); + + HEXDUMP("WMM: WMM TLV:", (t_u8 *)ptlv_hdr, tlv_len + 4); + + pwmm_param_ie = + (IEEEtypes_WmmParameter_t *)(pcurrent + 2); + pwmm_param_ie->vend_hdr.len = (t_u8)tlv_len; + pwmm_param_ie->vend_hdr.element_id = WMM_IE; + + PRINTM(MINFO, + "CMD_RESP: WMM_GET_STATUS: WMM Parameter Set: %d\n", + pwmm_param_ie->qos_info.para_set_count); + + memcpy_ext(priv->adapter, + (t_u8 *)&priv->curr_bss_params.bss_descriptor + .wmm_ie, + pwmm_param_ie, + (pwmm_param_ie->vend_hdr.len + 2), + sizeof(IEEEtypes_WmmParameter_t)); + send_wmm_event = MTRUE; + break; + + case TLV_TYPE_IEEE_ACTION_FRAME: + PRINTM(MEVENT, "WMM_STATUS: IEEE Action Frame\n"); + ptlv_action = (MrvlIETypes_ActionFrame_t *)pcurrent; + + ptlv_action->actionFrame.wmmAc.tspecAct.category = + wlan_le32_to_cpu(ptlv_action->actionFrame.wmmAc + .tspecAct.category); + if (ptlv_action->actionFrame.wmmAc.tspecAct.category == + IEEE_MGMT_ACTION_CATEGORY_WMM_TSPEC) { + ptlv_action->actionFrame.wmmAc.tspecAct.action = + wlan_le32_to_cpu( + ptlv_action->actionFrame.wmmAc + .tspecAct.action); + switch (ptlv_action->actionFrame.wmmAc.tspecAct + .action) { + case TSPEC_ACTION_CODE_ADDTS_RSP: + padd_ts_rsp = &ptlv_action->actionFrame + .wmmAc.addTsRsp; + wlan_send_wmmac_host_event( + priv, "ADDTS_RSP", + ptlv_action->srcAddr, + padd_ts_rsp->tspecIE.TspecBody + .TSInfo.TID, + padd_ts_rsp->tspecIE.TspecBody + .TSInfo.UserPri, + padd_ts_rsp->statusCode); + break; + + case TSPEC_ACTION_CODE_DELTS: + pdel_ts = &ptlv_action->actionFrame + .wmmAc.delTs; + wlan_send_wmmac_host_event( + priv, "DELTS_RX", + ptlv_action->srcAddr, + pdel_ts->tspecIE.TspecBody + .TSInfo.TID, + pdel_ts->tspecIE.TspecBody + .TSInfo.UserPri, + pdel_ts->reasonCode); + break; + + case TSPEC_ACTION_CODE_ADDTS_REQ: + default: + break; + } + } + break; + + default: + break; + } + + pcurrent += (tlv_len + sizeof(ptlv_hdr->header)); + resp_len -= (tlv_len + sizeof(ptlv_hdr->header)); + } + + wlan_wmm_setup_queue_priorities(priv, pwmm_param_ie); + wlan_wmm_setup_ac_downgrade(priv); + + if (send_wmm_event) { + wlan_recv_event(priv, MLAN_EVENT_ID_FW_WMM_CONFIG_CHANGE, + MNULL); + } + + LEAVE(); + return MLAN_STATUS_SUCCESS; +} + +/** + * @brief Call back from the command module to allow insertion of a WMM TLV + * + * If the BSS we are associating to supports WMM, add the required WMM + * Information IE to the association request command buffer in the form + * of a NXP extended IEEE IE. + * + * @param priv Pointer to the mlan_private driver data struct + * @param ppassoc_buf Output parameter: Pointer to the TLV output buffer, + * modified on return to point after the appended WMM TLV + * @param pwmm_ie Pointer to the WMM IE for the BSS we are joining + * @param pht_cap Pointer to the HT IE for the BSS we are joining + * + * @return Length of data appended to the association tlv buffer + */ +t_u32 wlan_wmm_process_association_req(pmlan_private priv, t_u8 **ppassoc_buf, + IEEEtypes_WmmParameter_t *pwmm_ie, + IEEEtypes_HTCap_t *pht_cap) +{ + MrvlIEtypes_WmmParamSet_t *pwmm_tlv; + t_u32 ret_len = 0; + + ENTER(); + + /* Null checks */ + if (!ppassoc_buf) { + LEAVE(); + return 0; + } + if (!(*ppassoc_buf)) { + LEAVE(); + return 0; + } + + if (!pwmm_ie) { + LEAVE(); + return 0; + } + + PRINTM(MINFO, "WMM: process assoc req: bss->wmmIe=0x%x\n", + pwmm_ie->vend_hdr.element_id); + + if ((priv->wmm_required || + (pht_cap && (pht_cap->ieee_hdr.element_id == HT_CAPABILITY) && + (priv->config_bands & BAND_GN || priv->config_bands & BAND_AN))) && + pwmm_ie->vend_hdr.element_id == WMM_IE) { + pwmm_tlv = (MrvlIEtypes_WmmParamSet_t *)*ppassoc_buf; + pwmm_tlv->header.type = (t_u16)wmm_info_ie[0]; + pwmm_tlv->header.type = wlan_cpu_to_le16(pwmm_tlv->header.type); + pwmm_tlv->header.len = (t_u16)wmm_info_ie[1]; + memcpy_ext(priv->adapter, pwmm_tlv->wmm_ie, &wmm_info_ie[2], + pwmm_tlv->header.len, pwmm_tlv->header.len); + if (pwmm_ie->qos_info.qos_uapsd) + memcpy_ext(priv->adapter, + (t_u8 *)(pwmm_tlv->wmm_ie + + pwmm_tlv->header.len - + sizeof(priv->wmm_qosinfo)), + &priv->wmm_qosinfo, + sizeof(priv->wmm_qosinfo), + sizeof(priv->wmm_qosinfo)); + + ret_len = sizeof(pwmm_tlv->header) + pwmm_tlv->header.len; + pwmm_tlv->header.len = wlan_cpu_to_le16(pwmm_tlv->header.len); + + HEXDUMP("ASSOC_CMD: WMM IE", (t_u8 *)pwmm_tlv, ret_len); + *ppassoc_buf += ret_len; + } + + LEAVE(); + return ret_len; +} +#endif /* STA_SUPPORT */ + +/** + * @brief Compute the time delay in the driver queues for a given packet. + * + * When the packet is received at the OS/Driver interface, the current + * time is set in the packet structure. The difference between the present + * time and that received time is computed in this function and limited + * based on pre-compiled limits in the driver. + * + * @param priv Ptr to the mlan_private driver data struct + * @param pmbuf Ptr to the mlan_buffer which has been previously timestamped + * + * @return Time delay of the packet in 2ms units after having limit applied + */ +t_u8 wlan_wmm_compute_driver_packet_delay(pmlan_private priv, + const pmlan_buffer pmbuf) +{ + t_u8 ret_val = 0; + t_u32 out_ts_sec, out_ts_usec; + t_s32 queue_delay; + + ENTER(); + + priv->adapter->callbacks.moal_get_system_time( + priv->adapter->pmoal_handle, &out_ts_sec, &out_ts_usec); + + queue_delay = (t_s32)(out_ts_sec - pmbuf->in_ts_sec) * 1000; + queue_delay += (t_s32)(out_ts_usec - pmbuf->in_ts_usec) / 1000; + + /* + * Queue delay is passed as a uint8 in units of 2ms (ms shifted + * by 1). Min value (other than 0) is therefore 2ms, max is 510ms. + * + * Pass max value if queue_delay is beyond the uint8 range + */ + ret_val = (t_u8)(MIN(queue_delay, priv->wmm.drv_pkt_delay_max) >> 1); + + PRINTM(MINFO, "WMM: Pkt Delay: %d ms, %d ms sent to FW\n", queue_delay, + ret_val); + + LEAVE(); + return ret_val; +} + +/** + * @brief Transmit the highest priority packet awaiting in the WMM Queues + * + * @param pmadapter Pointer to the mlan_adapter driver data struct + * + * @return N/A + */ +void wlan_wmm_process_tx(pmlan_adapter pmadapter) +{ + ENTER(); + + do { + if (wlan_dequeue_tx_packet(pmadapter)) + break; +#ifdef SDIO + if (IS_SD(pmadapter->card_type) && + (pmadapter->ireg & UP_LD_CMD_PORT_HOST_INT_STATUS)) { + wlan_send_mp_aggr_buf(pmadapter); + break; + } +#endif + +#ifdef PCIE + if (IS_PCIE(pmadapter->card_type) && + (pmadapter->ireg & + pmadapter->pcard_pcie->reg->host_intr_event_rdy)) + break; +#endif +#ifdef USB + if (IS_USB(pmadapter->card_type) && pmadapter->event_received) + break; +#endif + /* Check if busy */ + } while (!pmadapter->data_sent && !pmadapter->tx_lock_flag && + !wlan_wmm_lists_empty(pmadapter)); + + LEAVE(); + return; +} + +/** + * @brief select wmm queue + * + * @param pmpriv A pointer to mlan_private structure + * @param tid TID 0-7 + * + * @return wmm_queue priority (0-3) + */ +t_u8 wlan_wmm_select_queue(mlan_private *pmpriv, t_u8 tid) +{ + pmlan_adapter pmadapter = pmpriv->adapter; + t_u8 i; + mlan_wmm_ac_e ac_down = + pmpriv->wmm.ac_down_graded_vals[wlan_wmm_convert_tos_to_ac( + pmadapter, tid)]; + + ENTER(); + + for (i = 0; i < 4; i++) { + if (pmpriv->wmm.queue_priority[i] == ac_down) { + LEAVE(); + return i; + } + } + LEAVE(); + return 0; +} + +/** + * @brief Delete tx packets in RA list + * + * @param priv Pointer to the mlan_private driver data struct + * @param ra_list_head ra list header + * @param tid tid + * + * @return N/A + */ +static INLINE t_u8 wlan_del_tx_pkts_in_ralist(pmlan_private priv, + mlan_list_head *ra_list_head, + int tid) +{ + raListTbl *ra_list = MNULL; + pmlan_adapter pmadapter = priv->adapter; + pmlan_buffer pmbuf = MNULL; + t_u8 ret = MFALSE; + ENTER(); + ra_list = (raListTbl *)util_peek_list(priv->adapter->pmoal_handle, + ra_list_head, MNULL, MNULL); + while (ra_list && ra_list != (raListTbl *)ra_list_head) { + if (ra_list->total_pkts && + (ra_list->tx_pause || + (ra_list->total_pkts > RX_LOW_THRESHOLD))) { + pmbuf = (pmlan_buffer)util_dequeue_list( + pmadapter->pmoal_handle, &ra_list->buf_head, + MNULL, MNULL); + if (pmbuf) { + PRINTM(MDATA, + "Drop pkts: tid=%d tx_pause=%d pkts=%d " MACSTR + "\n", + tid, ra_list->tx_pause, + ra_list->total_pkts, + MAC2STR(ra_list->ra)); + wlan_write_data_complete(pmadapter, pmbuf, + MLAN_STATUS_FAILURE); + priv->wmm.pkts_queued[tid]--; + priv->num_drop_pkts++; + ra_list->total_pkts--; + if (ra_list->tx_pause) + priv->wmm.pkts_paused[tid]--; + else + util_scalar_decrement( + pmadapter->pmoal_handle, + &priv->wmm.tx_pkts_queued, + MNULL, MNULL); + ret = MTRUE; + break; + } + } + ra_list = ra_list->pnext; + } + + LEAVE(); + return ret; +} + +/** + * @brief Drop tx pkts + * + * @param priv Pointer to the mlan_private driver data struct + * + * @return N/A + */ +t_void wlan_drop_tx_pkts(pmlan_private priv) +{ + int j; + static int i; + pmlan_adapter pmadapter = priv->adapter; + pmadapter->callbacks.moal_spin_lock(pmadapter->pmoal_handle, + priv->wmm.ra_list_spinlock); + for (j = 0; j < MAX_NUM_TID; j++, i++) { + if (i == MAX_NUM_TID) + i = 0; + if (wlan_del_tx_pkts_in_ralist( + priv, &priv->wmm.tid_tbl_ptr[i].ra_list, i)) { + i++; + break; + } + } + pmadapter->callbacks.moal_spin_unlock(pmadapter->pmoal_handle, + priv->wmm.ra_list_spinlock); + return; +} + +/** + * @brief Remove peer ralist + * + * @param priv A pointer to mlan_private + * @param mac peer mac address + * + * @return N/A + */ +t_void wlan_wmm_delete_peer_ralist(pmlan_private priv, t_u8 *mac) +{ + raListTbl *ra_list; + int i; + pmlan_adapter pmadapter = priv->adapter; + t_u32 pkt_cnt = 0; + t_u32 tx_pkts_queued = 0; + + ENTER(); + pmadapter->callbacks.moal_spin_lock(pmadapter->pmoal_handle, + priv->wmm.ra_list_spinlock); + for (i = 0; i < MAX_NUM_TID; ++i) { + ra_list = wlan_wmm_get_ralist_node(priv, i, mac); + if (ra_list) { + PRINTM(MINFO, "delete sta ralist %p\n", ra_list); + priv->wmm.pkts_queued[i] -= ra_list->total_pkts; + if (ra_list->tx_pause) + priv->wmm.pkts_paused[i] -= ra_list->total_pkts; + else + pkt_cnt += ra_list->total_pkts; + wlan_wmm_del_pkts_in_ralist_node(priv, ra_list); + + util_unlink_list(pmadapter->pmoal_handle, + &priv->wmm.tid_tbl_ptr[i].ra_list, + (pmlan_linked_list)ra_list, MNULL, + MNULL); + pmadapter->callbacks.moal_mfree(pmadapter->pmoal_handle, + (t_u8 *)ra_list); + if (priv->wmm.tid_tbl_ptr[i].ra_list_curr == ra_list) + priv->wmm.tid_tbl_ptr[i].ra_list_curr = + (raListTbl *)&priv->wmm.tid_tbl_ptr[i] + .ra_list; + } + } + if (pkt_cnt) { + tx_pkts_queued = util_scalar_read(pmadapter->pmoal_handle, + &priv->wmm.tx_pkts_queued, + MNULL, MNULL); + tx_pkts_queued -= pkt_cnt; + util_scalar_write(priv->adapter->pmoal_handle, + &priv->wmm.tx_pkts_queued, tx_pkts_queued, + MNULL, MNULL); + util_scalar_write(priv->adapter->pmoal_handle, + &priv->wmm.highest_queued_prio, HIGH_PRIO_TID, + MNULL, MNULL); + } + pmadapter->callbacks.moal_spin_unlock(pmadapter->pmoal_handle, + priv->wmm.ra_list_spinlock); + LEAVE(); +} + +#ifdef STA_SUPPORT + +/** + * @brief This function prepares the command of ADDTS + * + * @param pmpriv A pointer to mlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param pdata_buf A pointer to data buffer + * @return MLAN_STATUS_SUCCESS + */ +mlan_status wlan_cmd_wmm_addts_req(pmlan_private pmpriv, + HostCmd_DS_COMMAND *cmd, t_void *pdata_buf) +{ + mlan_ds_wmm_addts *paddts = (mlan_ds_wmm_addts *)pdata_buf; + HostCmd_DS_WMM_ADDTS_REQ *pcmd_addts = &cmd->params.add_ts; + + ENTER(); + + cmd->command = wlan_cpu_to_le16(HostCmd_CMD_WMM_ADDTS_REQ); + cmd->size = wlan_cpu_to_le16(sizeof(pcmd_addts->dialog_token) + + sizeof(pcmd_addts->timeout_ms) + + sizeof(pcmd_addts->command_result) + + sizeof(pcmd_addts->ieee_status_code) + + paddts->ie_data_len + S_DS_GEN); + cmd->result = 0; + + pcmd_addts->timeout_ms = wlan_cpu_to_le32(paddts->timeout); + pcmd_addts->dialog_token = paddts->dialog_tok; + memcpy_ext(pmpriv->adapter, pcmd_addts->tspec_data, paddts->ie_data, + paddts->ie_data_len, WMM_TSPEC_SIZE); + + LEAVE(); + + return MLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the command response of ADDTS + * + * @param pmpriv A pointer to mlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @param pioctl_buf A pointer to mlan_ioctl_req structure + * + * @return MLAN_STATUS_SUCCESS + */ +mlan_status wlan_ret_wmm_addts_req(pmlan_private pmpriv, + const HostCmd_DS_COMMAND *resp, + mlan_ioctl_req *pioctl_buf) +{ + mlan_ds_wmm_cfg *pwmm = MNULL; + mlan_ds_wmm_addts *paddts = MNULL; + const HostCmd_DS_WMM_ADDTS_REQ *presp_addts = &resp->params.add_ts; + + ENTER(); + + if (pioctl_buf) { + pwmm = (mlan_ds_wmm_cfg *)pioctl_buf->pbuf; + paddts = (mlan_ds_wmm_addts *)&pwmm->param.addts; + paddts->result = wlan_le32_to_cpu(presp_addts->command_result); + paddts->dialog_tok = presp_addts->dialog_token; + paddts->status_code = (t_u32)presp_addts->ieee_status_code; + + if (paddts->result == MLAN_CMD_RESULT_SUCCESS) { + /* The tspecData field is potentially variable in size + * due to extra IEs that may have been in the ADDTS + * response action frame. Calculate the data length from + * the firmware command response. + */ + paddts->ie_data_len = + (t_u8)(resp->size - + sizeof(presp_addts->command_result) - + sizeof(presp_addts->timeout_ms) - + sizeof(presp_addts->dialog_token) - + sizeof(presp_addts->ieee_status_code) - + S_DS_GEN); + + /* Copy the TSPEC data include any extra IEs after the + * TSPEC */ + memcpy_ext(pmpriv->adapter, paddts->ie_data, + presp_addts->tspec_data, paddts->ie_data_len, + sizeof(paddts->ie_data)); + } else { + paddts->ie_data_len = 0; + } + PRINTM(MINFO, "TSPEC: ADDTS ret = %d,%d sz=%d\n", + paddts->result, paddts->status_code, + paddts->ie_data_len); + + HEXDUMP("TSPEC: ADDTS data", paddts->ie_data, + paddts->ie_data_len); + } + + LEAVE(); + return MLAN_STATUS_SUCCESS; +} + +/** + * @brief This function prepares the command of DELTS + * + * @param pmpriv A pointer to mlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param pdata_buf A pointer to data buffer + * @return MLAN_STATUS_SUCCESS + */ +mlan_status wlan_cmd_wmm_delts_req(pmlan_private pmpriv, + HostCmd_DS_COMMAND *cmd, t_void *pdata_buf) +{ + mlan_ds_wmm_delts *pdelts = (mlan_ds_wmm_delts *)pdata_buf; + HostCmd_DS_WMM_DELTS_REQ *pcmd_delts = &cmd->params.del_ts; + + ENTER(); + + cmd->command = wlan_cpu_to_le16(HostCmd_CMD_WMM_DELTS_REQ); + cmd->size = wlan_cpu_to_le16(sizeof(pcmd_delts->dialog_token) + + sizeof(pcmd_delts->command_result) + + sizeof(pcmd_delts->ieee_reason_code) + + pdelts->ie_data_len + S_DS_GEN); + cmd->result = 0; + pcmd_delts->ieee_reason_code = (t_u8)pdelts->status_code; + memcpy_ext(pmpriv->adapter, pcmd_delts->tspec_data, pdelts->ie_data, + pdelts->ie_data_len, WMM_TSPEC_SIZE); + + LEAVE(); + + return MLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the command response of DELTS + * + * @param pmpriv A pointer to mlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @param pioctl_buf A pointer to mlan_ioctl_req structure + * + * @return MLAN_STATUS_SUCCESS + */ +mlan_status wlan_ret_wmm_delts_req(pmlan_private pmpriv, + const HostCmd_DS_COMMAND *resp, + mlan_ioctl_req *pioctl_buf) +{ + mlan_ds_wmm_cfg *pwmm; + IEEEtypes_WMM_TSPEC_t *ptspec_ie; + const HostCmd_DS_WMM_DELTS_REQ *presp_delts = &resp->params.del_ts; + + ENTER(); + + if (pioctl_buf) { + pwmm = (mlan_ds_wmm_cfg *)pioctl_buf->pbuf; + pwmm->param.delts.result = + wlan_le32_to_cpu(presp_delts->command_result); + + PRINTM(MINFO, "TSPEC: DELTS result = %d\n", + presp_delts->command_result); + + if (pwmm->param.delts.result == 0) { + ptspec_ie = (IEEEtypes_WMM_TSPEC_t *) + presp_delts->tspec_data; + wlan_send_wmmac_host_event( + pmpriv, "DELTS_TX", MNULL, + ptspec_ie->TspecBody.TSInfo.TID, + ptspec_ie->TspecBody.TSInfo.UserPri, + presp_delts->ieee_reason_code); + } + } + + LEAVE(); + return MLAN_STATUS_SUCCESS; +} + +/** + * @brief This function prepares the command of WMM_QUEUE_STATS + * + * @param pmpriv A pointer to mlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param pdata_buf A pointer to data buffer + * @return MLAN_STATUS_SUCCESS + */ +mlan_status wlan_cmd_wmm_queue_stats(pmlan_private pmpriv, + HostCmd_DS_COMMAND *cmd, t_void *pdata_buf) +{ + mlan_ds_wmm_queue_stats *pqstats = (mlan_ds_wmm_queue_stats *)pdata_buf; + HostCmd_DS_WMM_QUEUE_STATS *pcmd_qstats = &cmd->params.queue_stats; + t_u8 id; + + ENTER(); + + cmd->command = wlan_cpu_to_le16(HostCmd_CMD_WMM_QUEUE_STATS); + cmd->size = + wlan_cpu_to_le16(sizeof(HostCmd_DS_WMM_QUEUE_STATS) + S_DS_GEN); + cmd->result = 0; + + pcmd_qstats->action = pqstats->action; + pcmd_qstats->select_is_userpri = 1; + pcmd_qstats->select_bin = pqstats->user_priority; + pcmd_qstats->pkt_count = wlan_cpu_to_le16(pqstats->pkt_count); + pcmd_qstats->pkt_loss = wlan_cpu_to_le16(pqstats->pkt_loss); + pcmd_qstats->avg_queue_delay = + wlan_cpu_to_le32(pqstats->avg_queue_delay); + pcmd_qstats->avg_tx_delay = wlan_cpu_to_le32(pqstats->avg_tx_delay); + pcmd_qstats->used_time = wlan_cpu_to_le16(pqstats->used_time); + pcmd_qstats->policed_time = wlan_cpu_to_le16(pqstats->policed_time); + for (id = 0; id < MLAN_WMM_STATS_PKTS_HIST_BINS; id++) { + pcmd_qstats->delay_histogram[id] = + wlan_cpu_to_le16(pqstats->delay_histogram[id]); + } + + LEAVE(); + return MLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the command response of WMM_QUEUE_STATS + * + * @param pmpriv A pointer to mlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @param pioctl_buf A pointer to mlan_ioctl_req structure + * + * @return MLAN_STATUS_SUCCESS + */ +mlan_status wlan_ret_wmm_queue_stats(pmlan_private pmpriv, + const HostCmd_DS_COMMAND *resp, + mlan_ioctl_req *pioctl_buf) +{ + mlan_ds_wmm_cfg *pwmm = MNULL; + mlan_ds_wmm_queue_stats *pqstats = MNULL; + const HostCmd_DS_WMM_QUEUE_STATS *presp_qstats = + &resp->params.queue_stats; + t_u8 id; + + ENTER(); + + if (pioctl_buf) { + pwmm = (mlan_ds_wmm_cfg *)pioctl_buf->pbuf; + pqstats = (mlan_ds_wmm_queue_stats *)&pwmm->param.q_stats; + + pqstats->action = presp_qstats->action; + pqstats->user_priority = presp_qstats->select_bin; + pqstats->pkt_count = wlan_le16_to_cpu(presp_qstats->pkt_count); + pqstats->pkt_loss = wlan_le16_to_cpu(presp_qstats->pkt_loss); + pqstats->avg_queue_delay = + wlan_le32_to_cpu(presp_qstats->avg_queue_delay); + pqstats->avg_tx_delay = + wlan_le32_to_cpu(presp_qstats->avg_tx_delay); + pqstats->used_time = wlan_le16_to_cpu(presp_qstats->used_time); + pqstats->policed_time = + wlan_le16_to_cpu(presp_qstats->policed_time); + for (id = 0; id < MLAN_WMM_STATS_PKTS_HIST_BINS; id++) { + pqstats->delay_histogram[id] = wlan_le16_to_cpu( + presp_qstats->delay_histogram[id]); + } + } + + LEAVE(); + return MLAN_STATUS_SUCCESS; +} + +/** + * @brief This function prepares the command of WMM_TS_STATUS + * + * @param pmpriv A pointer to mlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param pdata_buf A pointer to data buffer + * @return MLAN_STATUS_SUCCESS + */ +mlan_status wlan_cmd_wmm_ts_status(pmlan_private pmpriv, + HostCmd_DS_COMMAND *cmd, t_void *pdata_buf) +{ + mlan_ds_wmm_ts_status *pts_status = (mlan_ds_wmm_ts_status *)pdata_buf; + HostCmd_DS_WMM_TS_STATUS *pcmd_ts_status = &cmd->params.ts_status; + + ENTER(); + + cmd->command = wlan_cpu_to_le16(HostCmd_CMD_WMM_TS_STATUS); + cmd->size = + wlan_cpu_to_le16(sizeof(HostCmd_DS_WMM_TS_STATUS) + S_DS_GEN); + cmd->result = 0; + + memcpy_ext(pmpriv->adapter, (t_void *)pcmd_ts_status, + (t_void *)pts_status, sizeof(HostCmd_DS_WMM_TS_STATUS), + sizeof(HostCmd_DS_WMM_TS_STATUS)); + + LEAVE(); + return MLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the command response of WMM_TS_STATUS + * + * @param pmpriv A pointer to mlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @param pioctl_buf A pointer to mlan_ioctl_req structure + * + * @return MLAN_STATUS_SUCCESS + */ +mlan_status wlan_ret_wmm_ts_status(pmlan_private pmpriv, + HostCmd_DS_COMMAND *resp, + mlan_ioctl_req *pioctl_buf) +{ + mlan_ds_wmm_cfg *pwmm = MNULL; + HostCmd_DS_WMM_TS_STATUS *presp_ts_status = &resp->params.ts_status; + + ENTER(); + + if (pioctl_buf) { + pwmm = (mlan_ds_wmm_cfg *)pioctl_buf->pbuf; + presp_ts_status->medium_time = + wlan_le16_to_cpu(presp_ts_status->medium_time); + memcpy_ext(pmpriv->adapter, (t_void *)&pwmm->param.ts_status, + (t_void *)presp_ts_status, + sizeof(mlan_ds_wmm_ts_status), + sizeof(mlan_ds_wmm_ts_status)); + } + + LEAVE(); + return MLAN_STATUS_SUCCESS; +} + +/** + * @brief Set/Get WMM status + * + * @param pmadapter A pointer to mlan_adapter structure + * @param pioctl_req A pointer to ioctl request buffer + * + * @return MLAN_STATUS_SUCCESS --success + */ +static mlan_status wlan_wmm_ioctl_enable(pmlan_adapter pmadapter, + pmlan_ioctl_req pioctl_req) +{ + mlan_status ret = MLAN_STATUS_SUCCESS; + mlan_private *pmpriv = pmadapter->priv[pioctl_req->bss_index]; + mlan_ds_wmm_cfg *wmm = MNULL; + ENTER(); + wmm = (mlan_ds_wmm_cfg *)pioctl_req->pbuf; + if (pioctl_req->action == MLAN_ACT_GET) + wmm->param.wmm_enable = (t_u32)pmpriv->wmm_required; + else + pmpriv->wmm_required = (t_u8)wmm->param.wmm_enable; + pioctl_req->data_read_written = sizeof(t_u32) + MLAN_SUB_COMMAND_SIZE; + LEAVE(); + return ret; +} + +/** + * @brief Set/Get WMM QoS configuration + * + * @param pmadapter A pointer to mlan_adapter structure + * @param pioctl_req A pointer to ioctl request buffer + * + * @return MLAN_STATUS_SUCCESS --success + */ +static mlan_status wlan_wmm_ioctl_qos(pmlan_adapter pmadapter, + pmlan_ioctl_req pioctl_req) +{ + mlan_status ret = MLAN_STATUS_SUCCESS; + mlan_private *pmpriv = pmadapter->priv[pioctl_req->bss_index]; + mlan_ds_wmm_cfg *wmm = MNULL; + + ENTER(); + + wmm = (mlan_ds_wmm_cfg *)pioctl_req->pbuf; + + if (pioctl_req->action == MLAN_ACT_GET) + wmm->param.qos_cfg = pmpriv->wmm_qosinfo; + else { + pmpriv->wmm_qosinfo = wmm->param.qos_cfg; + } + + pioctl_req->data_read_written = sizeof(t_u8) + MLAN_SUB_COMMAND_SIZE; + + LEAVE(); + return ret; +} + +/** + * @brief Request for add a TSPEC + * + * @param pmadapter A pointer to mlan_adapter structure + * @param pioctl_req A pointer to ioctl request buffer + * + * @return MLAN_STATUS_PENDING --success, otherwise fail + */ +static mlan_status wlan_wmm_ioctl_addts_req(pmlan_adapter pmadapter, + pmlan_ioctl_req pioctl_req) +{ + mlan_status ret = MLAN_STATUS_SUCCESS; + pmlan_private pmpriv = pmadapter->priv[pioctl_req->bss_index]; + mlan_ds_wmm_cfg *cfg = MNULL; + + ENTER(); + cfg = (mlan_ds_wmm_cfg *)pioctl_req->pbuf; + + /* Send request to firmware */ + ret = wlan_prepare_cmd(pmpriv, HostCmd_CMD_WMM_ADDTS_REQ, 0, 0, + (t_void *)pioctl_req, + (t_void *)&cfg->param.addts); + + if (ret == MLAN_STATUS_SUCCESS) + ret = MLAN_STATUS_PENDING; + + LEAVE(); + return ret; +} + +/** + * @brief Request for delete a TSPEC + * + * @param pmadapter A pointer to mlan_adapter structure + * @param pioctl_req A pointer to ioctl request buffer + * + * @return MLAN_STATUS_PENDING --success, otherwise fail + */ +static mlan_status wlan_wmm_ioctl_delts_req(pmlan_adapter pmadapter, + pmlan_ioctl_req pioctl_req) +{ + mlan_status ret = MLAN_STATUS_SUCCESS; + pmlan_private pmpriv = pmadapter->priv[pioctl_req->bss_index]; + mlan_ds_wmm_cfg *cfg = MNULL; + + ENTER(); + cfg = (mlan_ds_wmm_cfg *)pioctl_req->pbuf; + + /* Send request to firmware */ + ret = wlan_prepare_cmd(pmpriv, HostCmd_CMD_WMM_DELTS_REQ, 0, 0, + (t_void *)pioctl_req, + (t_void *)&cfg->param.delts); + + if (ret == MLAN_STATUS_SUCCESS) + ret = MLAN_STATUS_PENDING; + + LEAVE(); + return ret; +} + +/** + * @brief To get and start/stop queue stats on a WMM AC + * + * @param pmadapter A pointer to mlan_adapter structure + * @param pioctl_req A pointer to ioctl request buffer + * + * @return MLAN_STATUS_PENDING --success, otherwise fail + */ +static mlan_status wlan_wmm_ioctl_queue_stats(pmlan_adapter pmadapter, + pmlan_ioctl_req pioctl_req) +{ + mlan_status ret = MLAN_STATUS_SUCCESS; + pmlan_private pmpriv = pmadapter->priv[pioctl_req->bss_index]; + mlan_ds_wmm_cfg *cfg = MNULL; + + ENTER(); + cfg = (mlan_ds_wmm_cfg *)pioctl_req->pbuf; + + /* Send request to firmware */ + ret = wlan_prepare_cmd(pmpriv, HostCmd_CMD_WMM_QUEUE_STATS, 0, 0, + (t_void *)pioctl_req, + (t_void *)&cfg->param.q_stats); + + if (ret == MLAN_STATUS_SUCCESS) + ret = MLAN_STATUS_PENDING; + + LEAVE(); + return ret; +} + +/** + * @brief Get the status of the WMM AC queues + * + * @param pmadapter A pointer to mlan_adapter structure + * @param pioctl_req A pointer to ioctl request buffer + * + * @return MLAN_STATUS_SUCCESS --success + */ +static mlan_status wlan_wmm_ioctl_queue_status(pmlan_adapter pmadapter, + pmlan_ioctl_req pioctl_req) +{ + mlan_status ret = MLAN_STATUS_SUCCESS; + pmlan_private pmpriv = pmadapter->priv[pioctl_req->bss_index]; + mlan_ds_wmm_cfg *cfg = MNULL; + mlan_ds_wmm_queue_status *pqstatus = MNULL; + WmmAcStatus_t *pac_status = MNULL; + mlan_wmm_ac_e ac_idx; + + ENTER(); + + cfg = (mlan_ds_wmm_cfg *)pioctl_req->pbuf; + pqstatus = (mlan_ds_wmm_queue_status *)&cfg->param.q_status; + + for (ac_idx = WMM_AC_BK; ac_idx <= WMM_AC_VO; ac_idx++) { + pac_status = &pmpriv->wmm.ac_status[ac_idx]; + + /* Firmware status */ + pqstatus->ac_status[ac_idx].flow_required = + pac_status->flow_required; + pqstatus->ac_status[ac_idx].flow_created = + pac_status->flow_created; + pqstatus->ac_status[ac_idx].disabled = pac_status->disabled; + + /* ACM bit reflected in firmware status (redundant) */ + pqstatus->ac_status[ac_idx].wmm_acm = pac_status->flow_required; + } + + LEAVE(); + return ret; +} + +/** + * @brief Get the status of the WMM Traffic Streams + * + * @param pmadapter A pointer to mlan_adapter structure + * @param pioctl_req A pointer to ioctl request buffer + * + * @return MLAN_STATUS_PENDING --success, otherwise fail + */ +static mlan_status wlan_wmm_ioctl_ts_status(pmlan_adapter pmadapter, + pmlan_ioctl_req pioctl_req) +{ + mlan_status ret = MLAN_STATUS_SUCCESS; + pmlan_private pmpriv = pmadapter->priv[pioctl_req->bss_index]; + mlan_ds_wmm_cfg *cfg = MNULL; + + ENTER(); + + cfg = (mlan_ds_wmm_cfg *)pioctl_req->pbuf; + + /* Send request to firmware */ + ret = wlan_prepare_cmd(pmpriv, HostCmd_CMD_WMM_TS_STATUS, 0, 0, + (t_void *)pioctl_req, + (t_void *)&cfg->param.ts_status); + + if (ret == MLAN_STATUS_SUCCESS) + ret = MLAN_STATUS_PENDING; + + LEAVE(); + return ret; +} +#endif /* STA_SUPPORT */ + +/** + * @brief This function prepares the command of WMM_PARAM_CONFIG + * + * @param pmpriv A pointer to mlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param cmd_action cmd action. + * @param pdata_buf A pointer to data buffer + * @return MLAN_STATUS_SUCCESS + */ +mlan_status wlan_cmd_wmm_param_config(pmlan_private pmpriv, + HostCmd_DS_COMMAND *cmd, t_u8 cmd_action, + t_void *pdata_buf) +{ + wmm_ac_parameters_t *ac_params = (wmm_ac_parameters_t *)pdata_buf; + HostCmd_DS_WMM_PARAM_CONFIG *pcmd_cfg = &cmd->params.param_config; + t_u8 i = 0; + + ENTER(); + + cmd->command = wlan_cpu_to_le16(HostCmd_CMD_WMM_PARAM_CONFIG); + cmd->size = wlan_cpu_to_le16(sizeof(HostCmd_DS_WMM_PARAM_CONFIG) + + S_DS_GEN); + cmd->result = 0; + + pcmd_cfg->action = cmd_action; + if (cmd_action == HostCmd_ACT_GEN_SET) { + memcpy_ext(pmpriv->adapter, pcmd_cfg->ac_params, ac_params, + sizeof(wmm_ac_parameters_t) * MAX_AC_QUEUES, + sizeof(wmm_ac_parameters_t) * MAX_AC_QUEUES); + for (i = 0; i < MAX_AC_QUEUES; i++) { + pcmd_cfg->ac_params[i].tx_op_limit = wlan_cpu_to_le16( + pcmd_cfg->ac_params[i].tx_op_limit); + } + } + LEAVE(); + return MLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the command response of WMM_PARAM_CONFIG + * + * @param pmpriv A pointer to mlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @param pioctl_buf A pointer to mlan_ioctl_req structure + * + * @return MLAN_STATUS_SUCCESS + */ +mlan_status wlan_ret_wmm_param_config(pmlan_private pmpriv, + const HostCmd_DS_COMMAND *resp, + mlan_ioctl_req *pioctl_buf) +{ + mlan_ds_wmm_cfg *pwmm = MNULL; + HostCmd_DS_WMM_PARAM_CONFIG *pcfg = + (HostCmd_DS_WMM_PARAM_CONFIG *)&resp->params.param_config; + t_u8 i; + + ENTER(); + + if (pioctl_buf) { + pwmm = (mlan_ds_wmm_cfg *)pioctl_buf->pbuf; + for (i = 0; i < MAX_AC_QUEUES; i++) { + pcfg->ac_params[i].tx_op_limit = wlan_le16_to_cpu( + pcfg->ac_params[i].tx_op_limit); + } + memcpy_ext(pmpriv->adapter, pwmm->param.ac_params, + pcfg->ac_params, + sizeof(wmm_ac_parameters_t) * MAX_AC_QUEUES, + sizeof(wmm_ac_parameters_t) * MAX_AC_QUEUES); + } + + LEAVE(); + return MLAN_STATUS_SUCCESS; +} + +/** + * @brief This function prepares the command of WMM_QUEUE_CONFIG + * + * @param pmpriv A pointer to mlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param pdata_buf A pointer to data buffer + * @return MLAN_STATUS_SUCCESS + */ +mlan_status wlan_cmd_wmm_queue_config(pmlan_private pmpriv, + HostCmd_DS_COMMAND *cmd, + t_void *pdata_buf) +{ + mlan_ds_wmm_queue_config *pqcfg = (mlan_ds_wmm_queue_config *)pdata_buf; + HostCmd_DS_WMM_QUEUE_CONFIG *pcmd_qcfg = &cmd->params.queue_config; + + ENTER(); + + cmd->command = wlan_cpu_to_le16(HostCmd_CMD_WMM_QUEUE_CONFIG); + cmd->size = wlan_cpu_to_le16( + sizeof(pcmd_qcfg->action) + sizeof(pcmd_qcfg->access_category) + + sizeof(pcmd_qcfg->msdu_lifetime_expiry) + S_DS_GEN); + cmd->result = 0; + + pcmd_qcfg->action = pqcfg->action; + pcmd_qcfg->access_category = pqcfg->access_category; + pcmd_qcfg->msdu_lifetime_expiry = + wlan_cpu_to_le16(pqcfg->msdu_lifetime_expiry); + + LEAVE(); + return MLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the command response of WMM_QUEUE_CONFIG + * + * @param pmpriv A pointer to mlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @param pioctl_buf A pointer to mlan_ioctl_req structure + * + * @return MLAN_STATUS_SUCCESS + */ +mlan_status wlan_ret_wmm_queue_config(pmlan_private pmpriv, + const HostCmd_DS_COMMAND *resp, + mlan_ioctl_req *pioctl_buf) +{ + mlan_ds_wmm_cfg *pwmm = MNULL; + const HostCmd_DS_WMM_QUEUE_CONFIG *presp_qcfg = + &resp->params.queue_config; + + ENTER(); + + if (pioctl_buf) { + pwmm = (mlan_ds_wmm_cfg *)pioctl_buf->pbuf; + pwmm->param.q_cfg.action = wlan_le32_to_cpu(presp_qcfg->action); + pwmm->param.q_cfg.access_category = + wlan_le32_to_cpu(presp_qcfg->access_category); + pwmm->param.q_cfg.msdu_lifetime_expiry = + wlan_le16_to_cpu(presp_qcfg->msdu_lifetime_expiry); + } + + LEAVE(); + return MLAN_STATUS_SUCCESS; +} + +/** + * @brief Set/Get a specified AC Queue's parameters + * + * @param pmadapter A pointer to mlan_adapter structure + * @param pioctl_req A pointer to ioctl request buffer + * + * @return MLAN_STATUS_PENDING --success, otherwise fail + */ +static mlan_status wlan_wmm_ioctl_queue_config(pmlan_adapter pmadapter, + pmlan_ioctl_req pioctl_req) +{ + mlan_status ret = MLAN_STATUS_SUCCESS; + pmlan_private pmpriv = pmadapter->priv[pioctl_req->bss_index]; + mlan_ds_wmm_cfg *cfg = MNULL; + + ENTER(); + cfg = (mlan_ds_wmm_cfg *)pioctl_req->pbuf; + + /* Send request to firmware */ + ret = wlan_prepare_cmd(pmpriv, HostCmd_CMD_WMM_QUEUE_CONFIG, 0, 0, + (t_void *)pioctl_req, + (t_void *)&cfg->param.q_cfg); + + if (ret == MLAN_STATUS_SUCCESS) + ret = MLAN_STATUS_PENDING; + + LEAVE(); + return ret; +} + +/** + * @brief WMM configuration handler + * + * @param pmadapter A pointer to mlan_adapter structure + * @param pioctl_req A pointer to ioctl request buffer + * + * @return MLAN_STATUS_SUCCESS --success, otherwise fail + */ +mlan_status wlan_wmm_cfg_ioctl(pmlan_adapter pmadapter, + pmlan_ioctl_req pioctl_req) +{ + mlan_status status = MLAN_STATUS_SUCCESS; + mlan_ds_wmm_cfg *wmm = MNULL; + + ENTER(); + + if (pioctl_req->buf_len < sizeof(mlan_ds_wmm_cfg)) { + PRINTM(MWARN, "MLAN bss IOCTL length is too short.\n"); + pioctl_req->data_read_written = 0; + pioctl_req->buf_len_needed = sizeof(mlan_ds_wmm_cfg); + pioctl_req->status_code = MLAN_ERROR_INVALID_PARAMETER; + LEAVE(); + return MLAN_STATUS_RESOURCE; + } + wmm = (mlan_ds_wmm_cfg *)pioctl_req->pbuf; + switch (wmm->sub_command) { +#ifdef STA_SUPPORT + case MLAN_OID_WMM_CFG_ENABLE: + status = wlan_wmm_ioctl_enable(pmadapter, pioctl_req); + break; + case MLAN_OID_WMM_CFG_QOS: + status = wlan_wmm_ioctl_qos(pmadapter, pioctl_req); + break; + case MLAN_OID_WMM_CFG_ADDTS: + status = wlan_wmm_ioctl_addts_req(pmadapter, pioctl_req); + break; + case MLAN_OID_WMM_CFG_DELTS: + status = wlan_wmm_ioctl_delts_req(pmadapter, pioctl_req); + break; + case MLAN_OID_WMM_CFG_QUEUE_STATS: + status = wlan_wmm_ioctl_queue_stats(pmadapter, pioctl_req); + break; + case MLAN_OID_WMM_CFG_QUEUE_STATUS: + status = wlan_wmm_ioctl_queue_status(pmadapter, pioctl_req); + break; + case MLAN_OID_WMM_CFG_TS_STATUS: + status = wlan_wmm_ioctl_ts_status(pmadapter, pioctl_req); + break; +#endif + case MLAN_OID_WMM_CFG_QUEUE_CONFIG: + status = wlan_wmm_ioctl_queue_config(pmadapter, pioctl_req); + break; + default: + pioctl_req->status_code = MLAN_ERROR_IOCTL_INVALID; + status = MLAN_STATUS_FAILURE; + break; + } + LEAVE(); + return status; +} + +/** + * @brief Get ralist info + * + * @param priv A pointer to mlan_private structure + * @param buf A pointer to ralist_info structure + * @return number of ralist entry + * + */ +int wlan_get_ralist_info(mlan_private *priv, ralist_info *buf) +{ + ralist_info *plist = buf; + mlan_list_head *ra_list_head = MNULL; + raListTbl *ra_list; + int i; + int count = 0; + for (i = 0; i < MAX_NUM_TID; i++) { + ra_list_head = &priv->wmm.tid_tbl_ptr[i].ra_list; + ra_list = + (raListTbl *)util_peek_list(priv->adapter->pmoal_handle, + ra_list_head, MNULL, MNULL); + while (ra_list && ra_list != (raListTbl *)ra_list_head) { + if (ra_list->total_pkts) { + plist->total_pkts = ra_list->total_pkts; + plist->tid = i; + plist->tx_pause = ra_list->tx_pause; + memcpy_ext(priv->adapter, plist->ra, + ra_list->ra, MLAN_MAC_ADDR_LENGTH, + MLAN_MAC_ADDR_LENGTH); + plist++; + count++; + if (count >= MLAN_MAX_RALIST_NUM) + break; + } + ra_list = ra_list->pnext; + } + } + LEAVE(); + return count; +} + +/** + * @brief dump ralist info + * + * @param priv A pointer to mlan_private structure + * + * @return N/A + * + */ +void wlan_dump_ralist(mlan_private *priv) +{ + mlan_list_head *ra_list_head = MNULL; + raListTbl *ra_list; + mlan_adapter *pmadapter = priv->adapter; + int i; + t_u32 tx_pkts_queued; + + tx_pkts_queued = + util_scalar_read(pmadapter->pmoal_handle, + &priv->wmm.tx_pkts_queued, MNULL, MNULL); + PRINTM(MERROR, "bss_index = %d, tx_pkts_queued = %d\n", priv->bss_index, + tx_pkts_queued); + if (!tx_pkts_queued) + return; + for (i = 0; i < MAX_NUM_TID; i++) { + ra_list_head = &priv->wmm.tid_tbl_ptr[i].ra_list; + ra_list = + (raListTbl *)util_peek_list(priv->adapter->pmoal_handle, + ra_list_head, MNULL, MNULL); + while (ra_list && ra_list != (raListTbl *)ra_list_head) { + if (ra_list->total_pkts) { + PRINTM(MERROR, + "ralist ra: %02x:%02x:%02x:%02x:%02x:%02x tid=%d pkts=%d pause=%d\n", + ra_list->ra[0], ra_list->ra[1], + ra_list->ra[2], ra_list->ra[3], + ra_list->ra[4], ra_list->ra[5], i, + ra_list->total_pkts, ra_list->tx_pause); + } + ra_list = ra_list->pnext; + } + } + return; +} + +/** + * @brief get tid down + * + * @param priv A pointer to mlan_private structure + * @param tid tid + * + * @return tid_down + * + */ +int wlan_get_wmm_tid_down(mlan_private *priv, int tid) +{ + return wlan_wmm_downgrade_tid(priv, tid); +} |