/* * Copyright (C) 2016 Spreadtrum Communications Inc. * * Authors : * star.liu * yifei.li * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "qos.h" #include "msg.h" #include "sprdwl.h" unsigned int g_qos_enable; #if 0 /*initial array of dscp map to priority *map array will be changed by qos remap */ #define DSCP_MAX_VALUE 64 const unsigned char default_dscp2priomap[DSCP_MAX_VALUE] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 0, 0, 0, 0, 6, 0, 6, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0 }; #endif #ifdef WMMAC_WFA_CERTIFICATION bool g_wmmac_available[NUM_AC] = {false, false, false, false}; u32 g_wmmac_admittedtime[NUM_AC] = {0}; u32 g_wmmac_usedtime[NUM_AC] = {0}; struct wmm_ac_ts_t g_sta_ts_info[NUM_TID]; unsigned int wmmac_ratio = 10; #endif struct qos_map_set g_11u_qos_map; void init_default_qos_map(void) { u8 index; for (index = 0; index < QOS_MAP_MAX_DSCP_EXCEPTION; index++) { g_11u_qos_map.qos_exceptions[index].dscp = 0xFF; g_11u_qos_map.qos_exceptions[index].up = prio_0; } index = 0; g_11u_qos_map.qos_ranges[index].low = 0x0; /*IP-PL0*/ g_11u_qos_map.qos_ranges[index].high = 0x0; g_11u_qos_map.qos_ranges[index].up = prio_0; index++; g_11u_qos_map.qos_ranges[index].low = 0x3; /*IP-PL3*/ g_11u_qos_map.qos_ranges[index].high = 0x3; g_11u_qos_map.qos_ranges[index].up = prio_0; index++; g_11u_qos_map.qos_ranges[index].low = 0x1; /*IP-PL1*/ g_11u_qos_map.qos_ranges[index].high = 0x2; /*IP-PL2*/ g_11u_qos_map.qos_ranges[index].up = prio_1; index++; g_11u_qos_map.qos_ranges[index].low = 0x4; /*IP-PL4*/ g_11u_qos_map.qos_ranges[index].high = 0x5; /*IP-PL5*/ g_11u_qos_map.qos_ranges[index].up = prio_4; index++; g_11u_qos_map.qos_ranges[index].low = 0x6; /*IP-PL6*/ g_11u_qos_map.qos_ranges[index].high = 0x7; /*IP-PL7*/ g_11u_qos_map.qos_ranges[index].up = prio_6; } unsigned int pkt_get_prio(void *skb, int data_offset, unsigned char *tos) { struct ether_header *eh; struct ethervlan_header *evh; unsigned char *pktdata; unsigned int priority = prio_6; pktdata = ((struct sk_buff *)(skb))->data + data_offset; eh = (struct ether_header *)pktdata; if (eh->ether_type == cpu_to_be16(ETHER_TYPE_8021Q)) { unsigned short vlan_tag; int vlan_prio; evh = (struct ethervlan_header *)eh; vlan_tag = be16_to_cpu(evh->vlan_tag); vlan_prio = (int)(vlan_tag >> VLAN_PRI_SHIFT) & VLAN_PRI_MASK; priority = vlan_prio; } else { unsigned char *ip_body = pktdata + sizeof(struct ether_header); unsigned char tos_tc = IP_TOS46(ip_body) & 0xE0; *tos = IP_TOS46(ip_body); switch (tos_tc) { case 0x00: case 0x60: priority = prio_0;/*BE*/ break; case 0x20: case 0x40: priority = prio_1;/*BK*/ break; case 0x80: case 0xA0: priority = prio_4;/*VI*/ break; default: priority = prio_6;/*VO*/ break; } } PKT_SET_PRIO(skb, priority); return priority; } static const u8 up_to_ac[] = { 0, /*SPRDWL_AC_BE*/ 1, /*SPRDWL_AC_BK*/ 1, /*SPRDWL_AC_BK*/ 0, /*SPRDWL_AC_BE*/ 4, /*SPRDWL_AC_VI*/ 4, /*SPRDWL_AC_VI*/ 6, /*SPRDWL_AC_VO*/ 6 /*SPRDWL_AC_VO*/ }; #if 0 /* up range from low to high with up value */ static bool qos_up_table_set(u8 *dscp2up_table, u8 up, struct dscp_range *dscp_range) { int i; if (up > 7 || dscp_range->low > dscp_range->high || dscp_range->low >= DSCP_MAX_VALUE || dscp_range->high >= DSCP_MAX_VALUE) { return false; } for (i = dscp_range->low; i <= dscp_range->high; i++) dscp2up_table[i] = up_to_ac[up]; return true; } /* set user priority table */ void qos_set_dscp2up_table(unsigned char *dscp2up_table, struct qos_capab_info *qos_map_ie) { unsigned char len; struct dscp_range dscp_range; int i; struct dscp_range *range; unsigned char except_len; u8 *except_ptr; u8 *range_ptr; if (dscp2up_table == NULL || qos_map_ie == NULL) return; /* length of QoS Map IE must be 16+n*2, n is number of exceptions */ if (qos_map_ie != NULL && qos_map_ie->id == DOT11_MNG_QOS_MAP_ID && qos_map_ie->len >= QOS_MAP_FIXED_LENGTH && (qos_map_ie->len % 2) == 0) { except_ptr = (u8 *)qos_map_ie->qos_info; len = qos_map_ie->len; except_len = len - QOS_MAP_FIXED_LENGTH; range_ptr = except_ptr + except_len; /* fill in ranges */ for (i = 0; i < QOS_MAP_FIXED_LENGTH; i += 2) { range = (struct dscp_range *)(&range_ptr[i]); if (range->low == 255 && range->high == 255) continue; if (!qos_up_table_set(dscp2up_table, i / 2, range)) { /* reset the table on failure */ memcpy(dscp2up_table, default_dscp2priomap, DSCP_MAX_VALUE); return; } } /* update exceptions */ for (i = 0; i < except_len; i += 2) { struct dscp_exception *exception = (struct dscp_exception *)(&except_ptr[i]); unsigned char dscp = exception->dscp; unsigned char up = exception->up; dscp_range.high = dscp; dscp_range.low = dscp; /* exceptions with invalid dscp/up are ignored */ qos_up_table_set(dscp2up_table, up, &dscp_range); } } wl_hex_dump(L_DBG, "qos up table: ", DUMP_PREFIX_OFFSET, 16, 1, dscp2up_table, DSCP_MAX_VALUE, 0); } /*Todo*/ int fq_table[5][4] = { {0, 0, 0, 0}, {30, 0, 0, 0}, {30, 20, 0, 0}, {40, 25, 20, 0}, {40, 30, 20, 10} }; /*Todo*/ int wfq_table[5][4] = { {0, 0, 0, 0}, {10, 0, 0, 0}, {20, 10, 0, 0}, {30, 20, 10, 0}, {40, 30, 20, 10} }; /*Todo*/ int fd_special_table[2][2] = { {30, 10}, {30, 10} }; /*time slot ratio based on WFA spec*/ int fd_ratio_table[3] = {7, /*vo: 87%, vi:13%*/ 9, /*vi:90%, be:10%*/ 5};/*be:81%, bk:19%*/ #endif void qos_enable(int flag) { g_qos_enable = flag; } void qos_init(struct tx_t *tx_t_list) { int i, j; /*tx_t_list->index = SPRDWL_AC_VO;*/ for (i = 0; i < SPRDWL_AC_MAX; i++) { for (j = 0; j < MAX_LUT_NUM; j++) { INIT_LIST_HEAD(&tx_t_list->q_list[i].p_list[j].head_list); spin_lock_init(&tx_t_list->q_list[i].p_list[j].p_lock); atomic_set(&tx_t_list->q_list[i].p_list[j].l_num, 0); } } #if 0 if (tx_t_list->dscp2up_table == NULL) { tx_t_list->dscp2up_table = kzalloc(DSCP_MAX_VALUE, GFP_KERNEL); if (tx_t_list->dscp2up_table == NULL) wl_err("%s malloc dscp2up_table fail\n", __func__); else memcpy(tx_t_list->dscp2up_table, default_dscp2priomap, DSCP_MAX_VALUE); } #endif } #if 0 void qos_deinit(struct tx_t *qos) { if (qos->dscp2up_table != NULL) { kfree(qos->dscp2up_table); qos->dscp2up_table = NULL; } } struct qos_capab_info *qos_parse_capab_info(void *buf, int buflen, uint key) { struct qos_capab_info *capab_info; int totlen; capab_info = (struct qos_capab_info *)buf; if (capab_info == NULL) return NULL; totlen = buflen; /* find tagged parameter */ while (totlen >= TLV_HDR_LEN) { int len = capab_info->len; /* validate remaining totlen */ if ((capab_info->id == key) && (totlen >= (int)(len + TLV_HDR_LEN))) return capab_info; capab_info = (struct qos_capab_info *) ((u8 *)capab_info + (len + TLV_HDR_LEN)); totlen -= (len + TLV_HDR_LEN); } return NULL; } #endif unsigned int qos_match_q(void *skb, int data_offset) { int priority; struct ether_header *eh; qos_head_type_t data_type = SPRDWL_AC_BE; unsigned char tos = 0; if (0 == g_qos_enable) return SPRDWL_AC_BE; /* vo vi bk be*/ eh = (struct ether_header *)(((struct sk_buff *)(skb))->data + data_offset); if (cpu_to_be16(ETHER_TYPE_IP) != eh->ether_type && cpu_to_be16(ETHER_TYPE_IPV6) != eh->ether_type) { goto OUT; } priority = pkt_get_prio(skb, data_offset, &tos); switch (priority) { case prio_1: data_type = SPRDWL_AC_BK; break; case prio_4: data_type = SPRDWL_AC_VI; break; case prio_6: data_type = SPRDWL_AC_VO; break; default: data_type = SPRDWL_AC_BE; break; } OUT: /*return data_type as qos queue index*/ return data_type; } unsigned int tid_map_to_qosindex(unsigned char tid) { qos_head_type_t qos_index = SPRDWL_AC_BE; switch (tid) { case prio_1: qos_index = SPRDWL_AC_BK; break; case prio_4: qos_index = SPRDWL_AC_VI; break; case prio_6: qos_index = SPRDWL_AC_VO; break; default: qos_index = SPRDWL_AC_BE; break; } /*return data_type as qos queue index*/ return qos_index; } unsigned int get_tid_qosindex(void *skb, int data_offset, unsigned char *tid, unsigned char *tos) { int priority; struct ether_header *eh; if (0 == g_qos_enable) return SPRDWL_AC_BE; /* vo vi bk be*/ eh = (struct ether_header *)(((struct sk_buff *)(skb))->data + data_offset); /*if (cpu_to_be16(ETHER_TYPE_IP) != eh->ether_type && cpu_to_be16(ETHER_TYPE_IPV6) != eh->ether_type) { goto OUT; }*/ priority = pkt_get_prio(skb, data_offset, tos); *tid = priority; /*return data_type as qos queue index*/ return tid_map_to_qosindex(*tid); } #if 0 void qos_wfq(struct tx_t *qos) { int t, i, j, weight, q[4] = {0}, list_num[4] = {0, 0, 0, 0}; for (i = 0, t = 0, weight = 0; i < 4; i++) { for (j = 0; j < MAX_LUT_NUM; j++) list_num[i] += get_list_num(&qos->q_list[i].p_list[j].head_list); if (list_num[i] > 0) { q[t] = i; t++; } } if (0 == t) return; for (i = 0; i < t; i++) weight += wfq_table[t][i]; for (i = 0; i < t; i++) qos->going[q[i]] = wfq_table[t][i] * list_num[i] / weight; } void qos_fq(struct tx_t *qos) { int i, j, t, k, q[4] = {0}, list_num[4] = {0, 0, 0, 0}; for (i = 0, t = 0; i < 4; i++) { for (j = 0; j < MAX_LUT_NUM; j++) list_num[i] += get_list_num(&qos->q_list[i].p_list[j].head_list); if (list_num[i] > 0) { q[t] = i; t++; } } if (0 == t) return; /* vi & bk*/ if ((2 == t) && (1 == q[0]) && (2 == q[1])) { qos->going[SPRDWL_AC_VI] = fd_special_table[0][0]; qos->going[SPRDWL_AC_BE] = fd_special_table[0][1]; if (list_num[SPRDWL_AC_VI] < qos->going[1]) qos->going[SPRDWL_AC_VI] = list_num[SPRDWL_AC_VI]; if (list_num[SPRDWL_AC_BE] < qos->going[2]) qos->going[SPRDWL_AC_BE] = list_num[SPRDWL_AC_BE]; return; } /*bk & be*/ if ((2 == t) && (2 == q[0]) && (3 == q[1])) { qos->going[2] = fd_special_table[1][0]; qos->going[3] = fd_special_table[1][1]; if (list_num[SPRDWL_AC_BE] < qos->going[2]) qos->going[SPRDWL_AC_BE] = list_num[SPRDWL_AC_BE]; if (list_num[SPRDWL_AC_BK] < qos->going[3]) qos->going[SPRDWL_AC_BK] = list_num[SPRDWL_AC_BK]; return; } for (i = 0; i < t; i++) { k = 0; qos->going[q[i]] = fq_table[t][i]; if (list_num[q[i]] < qos->going[q[i]]) { k = list_num[q[i]]; qos->going[q[i]] = k; } } } /*get time slot ratio between higher priority stream and lower*/ int qos_fq_ratio(struct tx_t *qos) { int i, j, t, q[4] = {0}; for (i = 0, t = 0; i < 4; i++) { for (j = 0; j < MAX_LUT_NUM; j++) { if (!list_empty(&qos->q_list[i].p_list[j].head_list)) { q[t] = i; t++; break; } } } if (0 == t) return t; /*vi & vo, two streams coexist based on WFA spec*/ if ((2 == t) && (0 == q[0]) && (1 == q[1])) { qos->ratio = fd_ratio_table[0]; qos->ac_index = SPRDWL_AC_VO; return t; } /* vi & be*/ if ((2 == t) && (1 == q[0]) && (2 == q[1])) { qos->ratio = fd_ratio_table[1]; qos->ac_index = SPRDWL_AC_VI; return t; } /*be & bk*/ if ((2 == t) && (2 == q[0]) && (3 == q[1])) { qos->ratio = fd_ratio_table[2]; qos->ac_index = SPRDWL_AC_BE; return t; } /*ac_index indicate which two qos streams coexist*/ qos->ac_index = SPRDWL_AC_MAX; qos->ratio = 0; return t; } void qos_sched(struct tx_t *qos, struct qos_list **q, int *num) { int round, j; if (0 == g_qos_enable) { *q = &qos->q_list[SPRDWL_AC_BE]; for (j = 0; j < MAX_LUT_NUM; j++) *num += get_list_num(&qos->q_list[SPRDWL_AC_BE].p_list[j].head_list); return; } for (round = 0; round < 4; round++) { if ((SPRDWL_AC_VO == qos->index) && (0 == qos->going[SPRDWL_AC_VO])) /*qos_fq(qos);*/ qos_wfq(qos); if (qos->going[qos->index] > 0) break; qos->index = INCR_RING_BUFF_INDX(qos->index, 4); } *q = &qos->q_list[qos->index]; *num = qos->going[qos->index]; } #endif int get_list_num(struct list_head *list) { int num = 0; struct list_head *pos; struct list_head *n_list; if (list_empty(list)) return 0; list_for_each_safe(pos, n_list, list) num++; return num; } #ifdef WMMAC_WFA_CERTIFICATION /*init wmmac params, include timer and ac params*/ void wmm_ac_init(struct sprdwl_priv *priv) { u8 ac; for (ac = 0; ac < NUM_AC; ac++) { g_wmmac_usedtime[ac] = 0; g_wmmac_available[ac] = false; g_wmmac_admittedtime[ac] = 0; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) timer_setup(&priv->wmmac.wmmac_edcaf_timer, update_wmmac_edcaftime_timeout, 0); timer_setup(&priv->wmmac.wmmac_vo_timer, update_wmmac_vo_timeout, 0); timer_setup(&priv->wmmac.wmmac_vi_timer, update_wmmac_vi_timeout, 0); #else setup_timer(&priv->wmmac.wmmac_edcaf_timer, update_wmmac_edcaftime_timeout, (unsigned long)priv); setup_timer(&priv->wmmac.wmmac_vo_timer, update_wmmac_vo_timeout, (unsigned long)priv); setup_timer(&priv->wmmac.wmmac_vi_timer, update_wmmac_vi_timeout, (unsigned long)priv); #endif memset(&priv->wmmac.ac[0], 0, 4*sizeof(struct wmm_ac_params)); } void reset_wmmac_parameters(struct sprdwl_priv *priv) { u8 ac; for (ac = 0; ac < NUM_AC; ac++) { g_wmmac_usedtime[ac] = 0; g_wmmac_available[ac] = false; g_wmmac_admittedtime[ac] = 0; } if (timer_pending(&priv->wmmac.wmmac_edcaf_timer)) del_timer_sync(&priv->wmmac.wmmac_edcaf_timer); if (timer_pending(&priv->wmmac.wmmac_vo_timer)) del_timer_sync(&priv->wmmac.wmmac_vo_timer); if (timer_pending(&priv->wmmac.wmmac_vi_timer)) del_timer_sync(&priv->wmmac.wmmac_vi_timer); memset(&priv->wmmac.ac[0], 0, 4*sizeof(struct wmm_ac_params)); } void reset_wmmac_ts_info(void) { u8 tsid; for (tsid = 0; tsid < NUM_TID; tsid++) remove_wmmac_ts_info(tsid); } unsigned int priority_map_to_qos_index(int priority) { qos_head_type_t qos_index = SPRDWL_AC_BE; switch (up_to_ac[priority]) { case prio_1: qos_index = SPRDWL_AC_BK; break; case prio_4: qos_index = SPRDWL_AC_VI; break; case prio_6: qos_index = SPRDWL_AC_VO; break; default: qos_index = SPRDWL_AC_BE; break; } /*return data_type as qos queue index*/ return qos_index; } unsigned int map_priority_to_edca_ac(int priority) { int ac; switch (priority) { case 01: case 02: ac = AC_BK; break; case 04: case 05: ac = AC_VI; break; case 06: case 07: ac = AC_VO; break; case 00: case 03: default: ac = AC_BE; break; } /*return data_type as qos queue index*/ return ac; } unsigned int map_edca_ac_to_priority(u8 ac) { unsigned int priority; switch (ac) { case AC_BK: priority = prio_1; break; case AC_VI: priority = prio_4; break; case AC_VO: priority = prio_6; break; case AC_BE: default: priority = prio_0; break; } return priority; } void update_wmmac_ts_info(u8 tsid, u8 up, u8 ac, bool status, u16 admitted_time) { g_sta_ts_info[tsid].exist = status; g_sta_ts_info[tsid].ac = ac; g_sta_ts_info[tsid].up = up; g_sta_ts_info[tsid].admitted_time = admitted_time; } u16 get_wmmac_admitted_time(u8 tsid) { u16 value = 0; if (g_sta_ts_info[tsid].exist == true) value = g_sta_ts_info[tsid].admitted_time; return value; } void remove_wmmac_ts_info(u8 tsid) { memset(&(g_sta_ts_info[tsid]), 0, sizeof(struct wmm_ac_ts_t)); } void update_admitted_time(struct sprdwl_priv *priv, u8 tsid, u16 medium_time, bool increase) { u8 ac = g_sta_ts_info[tsid].ac; if (true == increase) { /*mediumtime is in unit of 32 us, admittedtime is in unit of us*/ g_wmmac_admittedtime[ac] += (medium_time<<5); mod_timer(&priv->wmmac.wmmac_edcaf_timer, jiffies + WMMAC_EDCA_TIMEOUT_MS * HZ / 1000); /*replace the usedtime logic method with timer counter, just for simplify for the WFA certification*/ if (ac == AC_VO) mod_timer(&priv->wmmac.wmmac_vo_timer, jiffies + usecs_to_jiffies(g_wmmac_admittedtime[ac] * WMMAC_TIME_RATIO)); else if (ac == AC_VI) mod_timer(&priv->wmmac.wmmac_vi_timer, jiffies + usecs_to_jiffies(g_wmmac_admittedtime[ac] * WMMAC_TIME_RATIO)); } else { if (g_wmmac_admittedtime[ac] > (medium_time<<5)) g_wmmac_admittedtime[ac] -= (medium_time<<5); else { g_wmmac_admittedtime[ac] = 0; if (timer_pending(&priv->wmmac.wmmac_edcaf_timer)) del_timer_sync(&priv->wmmac.wmmac_edcaf_timer); if (timer_pending(&priv->wmmac.wmmac_vo_timer)) del_timer_sync(&priv->wmmac.wmmac_vo_timer); if (timer_pending(&priv->wmmac.wmmac_vi_timer)) del_timer_sync(&priv->wmmac.wmmac_vi_timer); } } g_wmmac_available[ac] = (g_wmmac_usedtime[ac] < g_wmmac_admittedtime[ac]); } #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) void update_wmmac_edcaftime_timeout(struct timer_list *t) { struct sprdwl_priv *priv = from_timer(priv, t, wmmac.wmmac_edcaf_timer); #else void update_wmmac_edcaftime_timeout(unsigned long data) { struct sprdwl_priv *priv = (struct sprdwl_priv *)data; #endif /*restart edcaf timer per second*/ mod_timer(&priv->wmmac.wmmac_edcaf_timer, jiffies + WMMAC_EDCA_TIMEOUT_MS * HZ / 1000); if (g_wmmac_admittedtime[AC_VO] > 0) { g_wmmac_usedtime[AC_VO] = 0; g_wmmac_available[AC_VO] = true; mod_timer(&priv->wmmac.wmmac_vo_timer, jiffies + usecs_to_jiffies(g_wmmac_admittedtime[AC_VO] * WMMAC_TIME_RATIO)); } if (g_wmmac_admittedtime[AC_VI] > 0) { g_wmmac_usedtime[AC_VI] = 0; g_wmmac_available[AC_VI] = true; mod_timer(&priv->wmmac.wmmac_vi_timer, jiffies + usecs_to_jiffies(g_wmmac_admittedtime[AC_VI] * WMMAC_TIME_RATIO)); } } #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) void update_wmmac_vo_timeout(struct timer_list *t) #else void update_wmmac_vo_timeout(unsigned long data) #endif { g_wmmac_usedtime[AC_VO] = g_wmmac_admittedtime[AC_VO]; g_wmmac_available[AC_VO] = false; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) void update_wmmac_vi_timeout(struct timer_list *t) #else void update_wmmac_vi_timeout(unsigned long data) #endif { g_wmmac_usedtime[AC_VI] = g_wmmac_admittedtime[AC_VI]; g_wmmac_available[AC_VI] = false; } /*change priority according to the g_wmmac_available value */ unsigned int change_priority_if(struct sprdwl_priv *priv, unsigned char *tid, unsigned char *tos, u16 len) { unsigned int qos_index, ac; int match_index = 0; unsigned char priority = *tos; priority >>= 2; for (match_index = 0; match_index < QOS_MAP_MAX_DSCP_EXCEPTION; match_index++) { if (priority == g_11u_qos_map.qos_exceptions[match_index].dscp) { *tid = g_11u_qos_map.qos_exceptions[match_index].up; break; } } if (match_index >= QOS_MAP_MAX_DSCP_EXCEPTION) { for (match_index = 0; match_index < 8; match_index++) { if ((priority >= g_11u_qos_map.qos_ranges[match_index].low) && (priority <= g_11u_qos_map.qos_ranges[match_index].high)) { *tid = g_11u_qos_map.qos_ranges[match_index].up; break; } } } if (1 == g_qos_enable) { ac = map_priority_to_edca_ac(*tid); while (ac != 0) { if (!!(priv->wmmac.ac[ac].aci_aifsn & WMM_AC_ACM)) { /*current ac is available, use it directly*/ if (true == g_wmmac_available[ac]) { /* use wmmac_ratio to adjust ac used time */ /* it is rough calc method: (data_len * 8) * ratio / data_rate, here , use 54Mbps as common usage */ g_wmmac_usedtime[ac] += (len + 4) * 8 * wmmac_ratio / 10 / 54; g_wmmac_available[ac] = (g_wmmac_usedtime[ac] < g_wmmac_admittedtime[ac]); break; } if ((g_wmmac_available[ac] == false) && (g_wmmac_usedtime[ac] != 0)) return SPRDWL_AC_MAX; /*current ac is not available, maybe usedtime > admitted time*/ /*downgrade to lower ac, then try again*/ ac--; } else { break; } } *tid = map_edca_ac_to_priority(ac); } switch (*tid) { case prio_1: qos_index = SPRDWL_AC_BK; break; case prio_4: qos_index = SPRDWL_AC_VI; break; case prio_6: qos_index = SPRDWL_AC_VO; break; default: qos_index = SPRDWL_AC_BE; break; } /*return data_type as qos queue index*/ return qos_index; } const u8 *get_wmm_ie(u8 *res, u16 ie_len, u8 ie, uint oui, uint oui_type) { const u8 *end, *pos; pos = res; end = pos + ie_len; while (pos + 1 < end) { if (pos + 2 + pos[1] > end) break; /*try to find VENDOR_SPECIFIC ie, which wmm ie located*/ if (pos[0] == ie) { /*match the OUI_MICROSOFT 0x0050f2 ie, and WMM ie*/ if ((((pos[2] << 16) | (pos[3] << 8) | pos[4]) == oui) && (pos[5] == WMM_OUI_TYPE)) { pos += 2; return pos; } break; } pos += 2 + pos[1]; } return NULL; } #endif