/* * HT handling * * Copyright 2003, Jouni Malinen * Copyright 2002-2005, Instant802 Networks, Inc. * Copyright 2005-2006, Devicescape Software, Inc. * Copyright 2006-2007 Jiri Benc * Copyright 2007, Michael Wu * Copyright 2007-2010, Intel Corporation * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include "atbm_hal.h" #if ATBM_DRIVER_PROCESS_BA /** * DOC: TX A-MPDU aggregation * * Aggregation on the TX side requires setting the hardware flag * %IEEE80211_HW_AMPDU_AGGREGATION. The driver will then be handed * packets with a flag indicating A-MPDU aggregation. The driver * or device is responsible for actually aggregating the frames, * as well as deciding how many and which to aggregate. * * When TX aggregation is started by some subsystem (usually the rate * control algorithm would be appropriate) by calling the * ieee80211_start_tx_ba_session() function, the driver will be * notified via its @ampdu_action function, with the * %IEEE80211_AMPDU_TX_START action. * * In response to that, the driver is later required to call the * ieee80211_start_tx_ba_cb_irqsafe() function, which will really * start the aggregation session after the peer has also responded. * If the peer responds negatively, the session will be stopped * again right away. Note that it is possible for the aggregation * session to be stopped before the driver has indicated that it * is done setting it up, in which case it must not indicate the * setup completion. * * Also note that, since we also need to wait for a response from * the peer, the driver is notified of the completion of the * handshake by the %IEEE80211_AMPDU_TX_OPERATIONAL action to the * @ampdu_action callback. * * Similarly, when the aggregation session is stopped by the peer * or something calling ieee80211_stop_tx_ba_session(), the driver's * @ampdu_action function will be called with the action * %IEEE80211_AMPDU_TX_STOP. In this case, the call must not fail, * and the driver must later call ieee80211_stop_tx_ba_cb_irqsafe(). */ int atbm_ampdu_action(struct atbmwifi_common *hw_priv, struct atbmwifi_vif *priv, enum atbmwifi_ieee80211_ampdu_mlme_action action, struct atbmwifi_sta_priv *sta_priv, atbm_uint16 tid, atbm_uint16 *ssn, atbm_uint8 buf_size, atbm_uint8 hw_token) { /* Aggregation is implemented fully in firmware, * including block ack negotiation. * In case of AMPDU aggregation in RX direction * re-ordering of packets takes place on host. mac80211 * needs the ADDBA Request to setup reodering.mac80211 also * sends ADDBA Response which is discarded in the driver as * FW generates the ADDBA Response on its own.*/ int ret; if(!hw_priv->driver_setup_ba){ return 0; } wifi_printk(WIFI_ALWAYS, "AMPDU[%d]:action[%d],tid[%d],ssn[%d],buff_size[%d],token[%d],"MACSTR"\n", priv->if_id,action,tid,ssn ? *ssn:0,buf_size,hw_token,MAC2STR(sta_priv->mac)); switch (action) { case ATBM_IEEE80211_AMPDU_RX_START: case ATBM_IEEE80211_AMPDU_RX_STOP: { struct wsm_rx_ba_session ba_session; ba_session.mode = action == ATBM_IEEE80211_AMPDU_RX_START ? WSM_RX_BA_SESSION_MODE__ENABLE : WSM_RX_BA_SESSION_MODE__DISABLE; ba_session.win_size = buf_size; ba_session.tid = tid; ba_session.rxtx = 1; atbm_memcpy(ba_session.TA,sta_priv->mac,ATBM_ETH_ALEN); ba_session.ssn = ssn ? *ssn:0; ba_session.timeout = 0; ba_session.hw_token = hw_token; ba_session.resv2 = 0; /* Just return OK to mac80211 */ ret = wsm_req_rx_ba_session(hw_priv,&ba_session,priv->if_id); break; } case ATBM_IEEE80211_AMPDU_TX_OPERATIONAL: case ATBM_IEEE80211_AMPDU_TX_STOP: { struct wsm_rx_ba_session ba_session; ba_session.mode = action == ATBM_IEEE80211_AMPDU_TX_OPERATIONAL ? WSM_RX_BA_SESSION_MODE__ENABLE : WSM_RX_BA_SESSION_MODE__DISABLE; ba_session.win_size = buf_size; ba_session.tid = tid; ba_session.rxtx = 0; atbm_memcpy(ba_session.TA,sta_priv->mac,ATBM_ETH_ALEN); ba_session.ssn = ssn ? *ssn:0; ba_session.timeout = 0; ba_session.hw_token = hw_token; ba_session.resv2 = 0; /* Just return OK to mac80211 */ ret = wsm_req_rx_ba_session(hw_priv,&ba_session,priv->if_id); break; } case ATBM_IEEE80211_AMPDU_TX_START: ret = 0; break; default: ret = -ATBM_ENOTSUPP; } return ret; } static int ieee80211_tx_ba_alloc_hw_token(struct atbmwifi_common *hw_priv) { int token = -1; atbm_spin_lock_bh(&hw_priv->aggr_lock); if(!hw_priv->driver_setup_ba){ token = 0; goto exit; } token = atbm_find_first_zero_bit(hw_priv->tx_aggr_map,hw_priv->max_hw_support_tx_aggs); if(token < 0 || token >= hw_priv->max_hw_support_tx_aggs){ token = -1; goto exit; } atbm_set_bit(token,hw_priv->tx_aggr_map); exit: atbm_spin_unlock_bh(&hw_priv->aggr_lock); return token; } static void ieee80211_tx_ba_free_hw_token(struct atbmwifi_common *hw_priv,int token) { atbm_spin_lock_bh(&hw_priv->aggr_lock); if(!hw_priv->driver_setup_ba){ goto exit; } if(token > hw_priv->max_hw_support_tx_aggs || token < 0){ goto exit; } atbm_clear_bit(token,hw_priv->tx_aggr_map); exit: atbm_spin_unlock_bh(&hw_priv->aggr_lock); } static int ieee80211_tx_ba_session_limit(struct atbmwifi_sta_priv *sta_priv, int tid) { if(!atbm_TimeAfter(sta_priv->ampdu_mlme.timestamp[tid], 300*HZ)){ sta_priv->ampdu_mlme.trylimit[tid] = 0; sta_priv->ampdu_mlme.timestamp[tid] = atbm_GetOsTimeMs(); } return (++sta_priv->ampdu_mlme.trylimit[tid] <= HT_AGG_TX_BURST); } void ieee80211_assign_tid_tx(struct atbmwifi_sta_priv *sta_priv, int tid, struct tid_ampdu_tx *tid_tx) { sta_priv->ampdu_mlme.tid_tx[tid] = tid_tx; } int ___ieee80211_stop_tx_ba_session(struct atbmwifi_sta_priv *sta_priv, atbm_uint16 tid, enum ieee80211_back_parties initiator, ATBM_BOOL tx) { struct atbmwifi_vif *priv = sta_priv->priv; struct atbmwifi_common *hw_priv = _atbmwifi_vifpriv_to_hwpriv(priv); struct tid_ampdu_tx *tid_tx; int ret; atbm_spin_lock_bh(&sta_priv->lock); tid_tx = sta_priv->ampdu_mlme.tid_tx[tid]; if (!tid_tx) { atbm_spin_unlock_bh(&sta_priv->lock); return -ATBM_ENOENT; } /* if we're already stopping ignore any new requests to stop */ if (atbm_test_bit(HT_AGG_STATE_STOPPING, &tid_tx->state)) { atbm_spin_unlock_bh(&sta_priv->lock); return -ATBM_EALREADY; } if (atbm_test_bit(HT_AGG_STATE_WANT_START, &tid_tx->state)) { /* not even started yet! */ ieee80211_tx_ba_free_hw_token(hw_priv,tid_tx->hw_token); ieee80211_assign_tid_tx(sta_priv, tid, NULL); atbm_spin_unlock_bh(&sta_priv->lock); atbm_kfree(tid_tx); return 0; } atbm_set_bit(HT_AGG_STATE_STOPPING, &tid_tx->state); atbm_spin_unlock_bh(&sta_priv->lock); #ifdef CONFIG_MAC80211_ATBM_HT_DEBUG atbm_printk_agg("Tx BA session stop requested for %pM tid %u\n", sta->sta.addr, tid); #endif /* CONFIG_MAC80211_ATBM_HT_DEBUG */ //wifi_printk(WIFI_ALWAYS, "%s:%d\n", __FUNCTION__,__LINE__); atbmwifi_eloop_cancel_timeout(sta_addba_resp_timer_expired, sta_priv, tid); /* * After this packets are no longer handed right through * to the driver but are put onto tid_tx->pending instead, * with locking to ensure proper access. */ atbm_clear_bit(HT_AGG_STATE_OPERATIONAL, &tid_tx->state); /* * There might be a few packets being processed right now (on * another CPU) that have already gotten past the aggregation * check when it was still OPERATIONAL and consequently have * IEEE80211_TX_CTL_AMPDU set. In that case, this code might * call into the driver at the same time or even before the * TX paths calls into it, which could confuse the driver. * * Wait for all currently running TX paths to finish before * telling the driver. New packets will not go through since * the aggregation session is no longer OPERATIONAL. */ tid_tx->stop_initiator = initiator; tid_tx->tx_stop = tx; ret = atbm_ampdu_action(hw_priv, priv, ATBM_IEEE80211_AMPDU_TX_STOP, sta_priv, tid, NULL, 0, tid_tx->hw_token); /* HW shall not deny going back to legacy */ if (ATBM_WARN_ON(ret)) { /* * We may have pending packets get stuck in this case... * Not bothering with a workaround for now. */ } ieee80211_tx_ba_free_hw_token(hw_priv,tid_tx->hw_token); if (tid_tx->stop_initiator == WLAN_BACK_INITIATOR && tid_tx->tx_stop && hw_priv->driver_setup_ba) ieee80211_send_delba(priv, sta_priv->mac, tid, WLAN_BACK_INITIATOR, ATBM_WLAN_REASON_QSTA_NOT_USE); /* * When we get here, the TX path will not be lockless any more wrt. * aggregation, since the OPERATIONAL bit has long been cleared. * Thus it will block on getting the lock, if it occurs. So if we * stop the queue now, we will not get any more packets, and any * that might be being processed will wait for us here, thereby * guaranteeing that no packets go to the tid_tx pending queue any * more. */ atbm_spin_lock_bh(&sta_priv->lock); ieee80211_assign_tid_tx(sta_priv, tid, NULL); atbm_spin_unlock_bh(&sta_priv->lock); /* future packets must not find the tid_tx struct any more */ atbmwifi_tx_aggr_queued_nolock(priv, sta_priv, tid_tx, tid); atbm_kfree(tid_tx); return ret; } /* * After sending add Block Ack request we activated a timer until * add Block Ack response will arrive from the recipient. * If this timer expires sta_addba_resp_timer_expired will be executed. */ void sta_addba_resp_timer_expired(atbm_void *data1,atbm_void *data2) { /* not an elegant detour, but there is no choice as the timer passes * only one argument, and both sta_info and TID are needed, so init * flow in sta_info_create gives the TID as data, while the timer_to_id * array gives the sta through container_of */ int tid = (int)data2; struct atbmwifi_sta_priv *sta_priv = (struct atbmwifi_sta_priv *)data1; struct tid_ampdu_tx *tid_tx; /* check if the TID waits for addBA response */ tid_tx = sta_priv->ampdu_mlme.tid_tx[tid]; if (!tid_tx || atbm_test_bit(HT_AGG_STATE_RESPONSE_RECEIVED, &tid_tx->state)) { rcu_read_unlock(); wifi_printk(WIFI_BASETUP, "timer expired on tid %d but we are not " "(or no longer) expecting addBA response there\n", tid); return; } wifi_printk(WIFI_BASETUP, "addBA response timer expired on tid %d\n", tid); ieee80211_stop_tx_ba_session(sta_priv, tid); } static inline int ieee80211_ac_from_tid(int tid) { return ieee802_1d_to_ac[tid & 7]; } void ieee80211_tx_ba_session_handle_start(struct atbmwifi_sta_priv *sta_priv, int tid) { struct atbmwifi_vif *priv = sta_priv->priv; struct atbmwifi_common *hw_priv = _atbmwifi_vifpriv_to_hwpriv(priv); struct tid_ampdu_tx *tid_tx; atbm_uint16 start_seq_num; int ret; tid_tx = sta_priv->ampdu_mlme.tid_tx[tid]; //wifi_printk(WIFI_ALWAYS, "%s:%d\n", __FUNCTION__,__LINE__); /* * Start queuing up packets for this aggregation session. * We're going to release them once the driver is OK with * that. */ atbm_clear_bit(HT_AGG_STATE_WANT_START, &tid_tx->state); /* * Make sure no packets are being processed. This ensures that * we have a valid starting sequence number and that in-flight * packets have been flushed out and no packets for this TID * will go into the driver during the ampdu_action call. */ //synchronize_net(); start_seq_num = sta_priv->tid_seq[tid] >> 4; ret = atbm_ampdu_action(hw_priv, priv, ATBM_IEEE80211_AMPDU_TX_START, sta_priv, tid, &start_seq_num, 0, tid_tx->hw_token); if (ret) { wifi_printk(WIFI_DBG_ERROR, "BA request denied - HW unavailable for" " tid %d\n", tid); atbmwifi_wpa_event_queue(sta_priv, tid, ATBM_NULL, WPA_EVENT_TX_AGGR_QUEUE_PACKET, ATBM_FALSE); atbm_kfree(tid_tx); return; } /* activate the timer for the recipient's addBA response */ atbmwifi_eloop_register_timeout(0,ADDBA_RESP_INTERVAL,sta_addba_resp_timer_expired,sta_priv, tid); if (ATBM_WARN_ON(atbm_set_bit(HT_AGG_STATE_DRV_READY, &tid_tx->state))){ wifi_printk(WIFI_DBG_ERROR, "BA request already send%d\n", tid); return; } wifi_printk(WIFI_BASETUP, "activated addBA response timer on tid %d\n", tid); atbm_spin_lock_bh(&sta_priv->lock); sta_priv->ampdu_mlme.addba_req_num[tid]++; atbm_spin_unlock_bh(&sta_priv->lock); /* send AddBA request */ atbmwifi_ieee80211_send_addba_request(priv, sta_priv->mac, tid, tid_tx->dialog_token, start_seq_num, hw_priv->max_tx_aggregation_subframes, tid_tx->timeout); } int ieee80211_start_tx_ba_session(struct atbmwifi_sta_priv *sta_priv, atbm_uint16 tid, atbm_uint16 timeout) { struct tid_ampdu_tx *tid_tx; struct atbmwifi_vif *priv = sta_priv->priv; struct atbmwifi_common *hw_priv = _atbmwifi_vifpriv_to_hwpriv(priv); int ret = 0; int token = -1; if(tid >= STA_TID_NUM) return -ATBM_EINVAL; wifi_printk(WIFI_BASETUP, "Open BA session requested for "MACSTR" tid %u\n", MAC2STR(sta_priv->mac), tid); /* * The aggregation code is not prepared to handle * anything but STA/AP due to the BSSID handling. * IBSS could work in the code but isn't supported * by drivers or the standard. */ if (priv->iftype != ATBM_NL80211_IFTYPE_STATION && priv->iftype != ATBM_NL80211_IFTYPE_AP) return -ATBM_EINVAL; if (sta_priv->flags & WLAN_STA_BLOCK_BA) { wifi_printk(WIFI_DBG_MSG, "BA sessions blocked. " "Denying BA session request\n"); return -ATBM_EINVAL; } token = ieee80211_tx_ba_alloc_hw_token(hw_priv); if(token == -1){ wifi_printk(WIFI_DBG_ERROR, "BA sessions alloc HW token fail. \n"); return -ATBM_EINVAL; } atbm_spin_lock_bh(&sta_priv->lock); /* we have tried too many times, receiver does not want A-MPDU */ if (ieee80211_tx_ba_session_limit(sta_priv,tid) == 0) { wifi_printk(WIFI_DBG_ERROR, "BA sessions failure exceed limit. \n"); ret = -ATBM_EBUSY; goto err_unlock_sta; } tid_tx = sta_priv->ampdu_mlme.tid_tx[tid]; /* check if the TID is not in aggregation flow already */ if (tid_tx || sta_priv->ampdu_mlme.tid_start_tx[tid]) { wifi_printk(WIFI_BASETUP,"BA request denied - session is not " "idle on tid %u %p %p\n", tid,tid_tx,sta_priv->ampdu_mlme.tid_start_tx[tid]); ret = -ATBM_EAGAIN; goto err_unlock_sta; } /* prepare A-MPDU MLME for Tx aggregation */ atbm_spin_unlock_bh(&sta_priv->lock); //aden add tid_tx = atbm_kzalloc(sizeof(struct tid_ampdu_tx), GFP_ATOMIC); atbm_spin_lock_bh(&sta_priv->lock); //aden add if (!tid_tx) { ret = -ATBM_ENOMEM; goto err_unlock_sta; } atbm_skb_queue_head_init(&tid_tx->pending); atbm_set_bit(HT_AGG_STATE_WANT_START, &tid_tx->state); tid_tx->timeout = timeout; /* assign a dialog token */ sta_priv->ampdu_mlme.dialog_token_allocator++; tid_tx->dialog_token = sta_priv->ampdu_mlme.dialog_token_allocator; tid_tx->hw_token = token; /* * Finally, assign it to the start array; the work item will * collect it and move it to the normal array. */ sta_priv->ampdu_mlme.tid_start_tx[tid] = tid_tx; atbm_queue_work(hw_priv, sta_priv->ampdu_mlme.work); /* this flow continues off the work */ err_unlock_sta: if(ret != 0){ ieee80211_tx_ba_free_hw_token(hw_priv,token); } atbm_spin_unlock_bh(&sta_priv->lock); return ret; } //EXPORT_SYMBOL(ieee80211_start_tx_ba_session); static void ieee80211_agg_tx_operational(struct atbmwifi_common *hw_priv, struct atbmwifi_sta_priv *sta_priv, atbm_uint16 tid) { struct tid_ampdu_tx *tid_tx; tid_tx = sta_priv->ampdu_mlme.tid_tx[tid]; wifi_printk(WIFI_BASETUP, "Aggregation is on for tid %d\n", tid); //wifi_printk(WIFI_ALWAYS, "%s:%d\n", __FUNCTION__,__LINE__); atbm_ampdu_action(hw_priv, sta_priv->priv, ATBM_IEEE80211_AMPDU_TX_OPERATIONAL, sta_priv, tid, ATBM_NULL, tid_tx->buf_size, tid_tx->hw_token); /* * Now mark as operational. This will be visible * in the TX path, and lets it go lock-free in * the common case. */ atbm_set_bit(HT_AGG_STATE_OPERATIONAL, &tid_tx->state); /* * synchronize with TX path, while splicing the TX path * should block so it won't put more packets onto pending. */ atbmwifi_wpa_event_queue(sta_priv, tid, ATBM_NULL, WPA_EVENT_TX_AGGR_QUEUE_PACKET, ATBM_FALSE); } int __ieee80211_stop_tx_ba_session(struct atbmwifi_sta_priv *sta_priv, atbm_uint16 tid, enum ieee80211_back_parties initiator, ATBM_BOOL tx) { int ret; atbm_os_mutexLock(&sta_priv->ampdu_mlme.mtx, 0); ret = ___ieee80211_stop_tx_ba_session(sta_priv, tid, initiator, tx); atbm_os_mutexUnLock(&sta_priv->ampdu_mlme.mtx); return ret; } int ieee80211_stop_tx_ba_session(struct atbmwifi_sta_priv *sta_priv, atbm_uint16 tid) { struct atbmwifi_vif *priv = sta_priv->priv; struct atbmwifi_common *hw_priv = _atbmwifi_vifpriv_to_hwpriv(priv); struct tid_ampdu_tx *tid_tx; int ret = 0; if (tid >= STA_TID_NUM) return -ATBM_EINVAL; atbm_spin_lock_bh(&sta_priv->lock); tid_tx = sta_priv->ampdu_mlme.tid_tx[tid]; if (!tid_tx) { ret = -ATBM_ENOENT; goto unlock; } if (atbm_test_bit(HT_AGG_STATE_STOPPING, &tid_tx->state)) { /* already in progress stopping it */ ret = 0; goto unlock; } atbm_set_bit(HT_AGG_STATE_WANT_STOP, &tid_tx->state); atbm_queue_work(hw_priv, sta_priv->ampdu_mlme.work); unlock: atbm_spin_unlock_bh(&sta_priv->lock); return ret; } void ieee80211_process_addba_resp(struct atbmwifi_common *hw_priv, struct atbmwifi_sta_priv *sta_priv, struct atbmwifi_ieee80211_mgmt *mgmt, atbm_size_t len) { struct tid_ampdu_tx *tid_tx; atbm_uint16 capab, tid; atbm_uint8 buf_size; capab = atbm_le16_to_cpu(mgmt->u.action.u.addba_resp.capab); tid = (capab & ATBM_IEEE80211_ADDBA_PARAM_TID_MASK) >> 2; buf_size = (capab & ATBM_IEEE80211_ADDBA_PARAM_BUF_SIZE_MASK) >> 6; atbm_os_mutexLock(&sta_priv->ampdu_mlme.mtx, 0); tid_tx = sta_priv->ampdu_mlme.tid_tx[tid]; if (!tid_tx) goto out; if (mgmt->u.action.u.addba_resp.dialog_token != tid_tx->dialog_token) { wifi_printk(WIFI_DBG_ERROR, "wrong addBA response token, tid %d\n", tid); goto out; } atbmwifi_eloop_cancel_timeout(sta_addba_resp_timer_expired, sta_priv, tid); wifi_printk(WIFI_BASETUP, "switched off addBA timer for tid %d\n", tid); /* * addba_resp_timer may have fired before we got here, and * caused WANT_STOP to be set. If the stop then was already * processed further, STOPPING might be set. */ if (atbm_test_bit(HT_AGG_STATE_WANT_STOP, &tid_tx->state) || atbm_test_bit(HT_AGG_STATE_STOPPING, &tid_tx->state)) { wifi_printk(WIFI_DBG_ERROR, "got addBA resp for tid %d but we already gave up\n", tid); goto out; } //wifi_printk(WIFI_ALWAYS, "%s:%d\n", __FUNCTION__,__LINE__); /* * IEEE 802.11-2007 7.3.1.14: * In an ADDBA Response frame, when the Status Code field * is set to 0, the Buffer Size subfield is set to a value * of at least 1. */ if (atbm_le16_to_cpu(mgmt->u.action.u.addba_resp.status) == ATBM_WLAN_STATUS_SUCCESS && buf_size) { if (atbm_set_bit(HT_AGG_STATE_RESPONSE_RECEIVED, &tid_tx->state)) { /* ignore duplicate response */ goto out; } tid_tx->buf_size = buf_size; if (atbm_test_bit(HT_AGG_STATE_DRV_READY, &tid_tx->state)) ieee80211_agg_tx_operational(hw_priv, sta_priv, tid); sta_priv->ampdu_mlme.addba_req_num[tid] = 0; } else { ___ieee80211_stop_tx_ba_session(sta_priv, tid, WLAN_BACK_INITIATOR, ATBM_TRUE); } out: atbm_os_mutexUnLock(&sta_priv->ampdu_mlme.mtx); } static int ieee80211_rx_ba_alloc_hw_token(struct atbmwifi_common *hw_priv) { int token = -1; atbm_spin_lock_bh(&hw_priv->aggr_lock); if(!hw_priv->driver_setup_ba){ token = 0; goto exit; } token = atbm_find_first_zero_bit(hw_priv->rx_aggr_map,hw_priv->max_hw_support_rx_aggs); if(token < 0 || token >= hw_priv->max_hw_support_rx_aggs){ token = -1; goto exit; } atbm_set_bit(token,hw_priv->rx_aggr_map); exit: atbm_spin_unlock_bh(&hw_priv->aggr_lock); return token; } static void ieee80211_rx_ba_free_hw_token(struct atbmwifi_common *hw_priv,int token) { atbm_spin_lock_bh(&hw_priv->aggr_lock); if(!hw_priv->driver_setup_ba){ goto exit; } if(token > hw_priv->max_hw_support_rx_aggs || token < 0){ goto exit; } atbm_clear_bit(token,hw_priv->rx_aggr_map); exit: atbm_spin_unlock_bh(&hw_priv->aggr_lock); } static void ieee80211_free_tid_rx(struct tid_ampdu_rx *tid_rx) { atbm_kfree(tid_rx); } void ___ieee80211_stop_rx_ba_session(struct atbmwifi_sta_priv *sta_priv, atbm_uint16 tid, atbm_uint16 initiator, atbm_uint16 reason, ATBM_BOOL tx) { struct atbmwifi_vif *priv = sta_priv->priv; struct atbmwifi_common *hw_priv = _atbmwifi_vifpriv_to_hwpriv(priv); struct atbm_ba_params ba_params; if(sta_priv->rx_token[tid] == -1){ return; } ba_params.tid = tid; ba_params.action = ATBM_BA__ACTION_RX_DELBA; atbm_updata_ba_tid_params(priv,&ba_params); wifi_printk(WIFI_BASETUP,"Rx BA session stop requested for %pM tid %u\n", sta_priv->mac, tid); if (atbm_ampdu_action(hw_priv, priv, ATBM_IEEE80211_AMPDU_RX_STOP, sta_priv, tid, NULL, 0, (atbm_uint8)sta_priv->rx_token[tid])) wifi_printk(WIFI_DBG_ERROR, "HW problem - can not stop rx " "aggregation for tid %d\n", tid); /* check if this is a self generated aggregation halt */ if (initiator == WLAN_BACK_RECIPIENT && tx && hw_priv->driver_setup_ba) ieee80211_send_delba(priv, sta_priv->mac, tid, 0, reason); atbmwifi_eloop_cancel_timeout(sta_rx_agg_session_timer_expired, &sta_priv->timer_to_tid[tid], ATBM_NULL); ieee80211_rx_ba_free_hw_token(hw_priv,sta_priv->rx_token[tid]); sta_priv->rx_token[tid] = -1; } void __ieee80211_stop_rx_ba_session(struct atbmwifi_sta_priv *sta_priv, atbm_uint16 tid, atbm_uint16 initiator, atbm_uint16 reason, ATBM_BOOL tx) { atbm_os_mutexLock(&sta_priv->ampdu_mlme.mtx, 0); ___ieee80211_stop_rx_ba_session(sta_priv, tid, initiator, reason, tx); atbm_os_mutexUnLock(&sta_priv->ampdu_mlme.mtx); } /* * After accepting the AddBA Request we activated a timer, * resetting it after each frame that arrives from the originator. */ void sta_rx_agg_session_timer_expired(atbm_void *data1,atbm_void *data2) { /* not an elegant detour, but there is no choice as the timer passes * only one argument, and various sta_info are needed here, so init * flow in sta_info_create gives the TID as data, while the timer_to_id * array gives the sta through container_of */ atbm_uint8 *ptid = (atbm_uint8 *)data1; atbm_uint8 *timer_to_id = ptid - *ptid; struct atbmwifi_sta_priv *sta_priv = atbm_container_of(timer_to_id, struct atbmwifi_sta_priv, timer_to_tid[0]); struct atbmwifi_common *hw_priv = _atbmwifi_vifpriv_to_hwpriv(sta_priv->priv); wifi_printk(WIFI_BASETUP, "rx session timer expired on tid %d\n", (atbm_uint16)*ptid); atbm_set_bit(*ptid, sta_priv->ampdu_mlme.tid_rx_timer_expired); atbm_queue_work(hw_priv, sta_priv->ampdu_mlme.work); } void ieee80211_process_addba_request(struct atbmwifi_common *hw_priv, struct atbmwifi_sta_priv *sta_priv, struct atbmwifi_ieee80211_mgmt *mgmt, int len) { struct tid_ampdu_rx *tid_agg_rx; atbm_uint16 capab, tid, timeout, ba_policy, buf_size, start_seq_num; atbm_uint16 status; int tx = hw_priv->driver_setup_ba; struct atbm_ba_params ba_params; atbm_uint8 dialog_token; int ret = -ATBM_EOPNOTSUPP; int hw_token = -1; /* extract session parameters from addba request frame */ dialog_token = mgmt->u.action.u.addba_req.dialog_token; timeout = atbm_le16_to_cpu(mgmt->u.action.u.addba_req.timeout); start_seq_num = atbm_le16_to_cpu(mgmt->u.action.u.addba_req.start_seq_num) >> 4; capab = atbm_le16_to_cpu(mgmt->u.action.u.addba_req.capab); ba_policy = (capab & ATBM_IEEE80211_ADDBA_PARAM_POLICY_MASK) >> 1; tid = (capab & ATBM_IEEE80211_ADDBA_PARAM_TID_MASK) >> 2; buf_size = (capab & ATBM_IEEE80211_ADDBA_PARAM_BUF_SIZE_MASK) >> 6; status = ATBM_WLAN_STATUS_REQUEST_DECLINED; if (sta_priv->flags & WLAN_STA_BLOCK_BA) { wifi_printk(WIFI_BASETUP, "Suspend in progress. " "Denying ADDBA request\n"); goto end_no_lock; } /* sanity check for incoming parameters: * check if configuration can support the BA policy * and if buffer size does not exceeds max value */ /* XXX: check own ht delayed BA capability?? */ if ((ba_policy != 1) && (!(sta_priv->rate.ht_cap.cap & ATBM_IEEE80211_HT_CAP_DELAY_BA))) { status = ATBM_WLAN_STATUS_INVALID_QOS_PARAM; wifi_printk(WIFI_DBG_ERROR, "AddBA Req with bad params from " "%pM on tid %u. policy %d, buffer size %d\n", mgmt->sa, tid, ba_policy, buf_size); goto end_no_lock; } /* determine default buffer size */ if (buf_size == 0) buf_size = 64;//IEEE80211_MAX_AMPDU_BUF; /* make sure the size doesn't exceed the maximum supported by the hw */ if (buf_size > hw_priv->max_rx_aggregation_subframes) buf_size = hw_priv->max_rx_aggregation_subframes; /* examine state machine */ atbm_os_mutexLock(&sta_priv->ampdu_mlme.mtx, 0); if (sta_priv->rx_token[tid] != -1) { wifi_printk(WIFI_BASETUP, "unexpected AddBA Req from " "%pM on tid %u\n", mgmt->sa, tid); /* delete existing Rx BA session on the same tid */ ___ieee80211_stop_rx_ba_session(sta_priv, tid, WLAN_BACK_RECIPIENT, ATBM_WLAN_STATUS_UNSPECIFIED_QOS, ATBM_FALSE); } /* prepare A-MPDU MLME for Rx aggregation */ hw_token = ieee80211_rx_ba_alloc_hw_token(hw_priv); if (hw_token == -1){ wifi_printk(WIFI_DBG_ERROR, "aggr token not enough\n"); goto end; } ret = atbm_ampdu_action(hw_priv, sta_priv->priv, ATBM_IEEE80211_AMPDU_RX_START, sta_priv, tid, &start_seq_num, 0, hw_token); wifi_printk(WIFI_BASETUP, "Rx A-MPDU request on tid %d result %d\n", tid, ret); if (ret) { ieee80211_rx_ba_free_hw_token(hw_priv, hw_token); goto end; } ba_params.link_id = sta_priv->link_id; ba_params.action = ATBM_BA__ACTION_RX_ADDBR; ba_params.timeout = timeout; capab = atbm_le16_to_cpu(mgmt->u.action.u.addba_req.capab); ba_params.tid = (capab & ATBM_IEEE80211_ADDBA_PARAM_TID_MASK) >> 2; // ba_params.win_size = (capab & IEEE80211_ADDBA_PARAM_BUF_SIZE_MASK) >> 6; ba_params.win_size = BUFF_STORED_LEN; ba_params.ssn = start_seq_num; ba_params.sta_priv = sta_priv; atbm_updata_ba_tid_params(sta_priv->priv,&ba_params); sta_priv->rx_token[tid] = hw_token; status = ATBM_WLAN_STATUS_SUCCESS; /* activate it for RX */ if (timeout) atbmwifi_eloop_register_timeout(0, timeout, sta_rx_agg_session_timer_expired, &sta_priv->timer_to_tid[tid], ATBM_NULL); end: atbm_os_mutexUnLock(&sta_priv->ampdu_mlme.mtx); end_no_lock: if(tx) ieee80211_send_addba_resp(sta_priv->priv, sta_priv->mac, tid, dialog_token, status, 1, buf_size, timeout); return; } void ieee80211_process_delba(struct atbmwifi_common *hw_priv, struct atbmwifi_sta_priv *sta_priv, struct atbmwifi_ieee80211_mgmt *mgmt, int len) { atbm_uint16 tid, params; atbm_uint16 initiator; params = atbm_le16_to_cpu(mgmt->u.action.u.delba.params); tid = (params & ATBM_IEEE80211_DELBA_PARAM_TID_MASK) >> 12; initiator = (params & ATBM_IEEE80211_DELBA_PARAM_INITIATOR_MASK) >> 11; wifi_printk(WIFI_BASETUP, "delba from %pM (%s) tid %d reason code %d\n", mgmt->sa, initiator ? "initiator" : "recipient", tid, atbm_le16_to_cpu(mgmt->u.action.u.delba.reason_code)); if (initiator == WLAN_BACK_INITIATOR) __ieee80211_stop_rx_ba_session(sta_priv, tid, WLAN_BACK_INITIATOR, 0, ATBM_TRUE); __ieee80211_stop_tx_ba_session(sta_priv, tid, WLAN_BACK_RECIPIENT, ATBM_TRUE); } void ieee80211_ba_session_work(struct atbm_work_struct *work) { struct atbmwifi_sta_priv *sta_priv = (struct atbmwifi_sta_priv *)work; #if ATBM_DRIVER_PROCESS_BA struct tid_ampdu_tx *tid_tx; #endif int tid; /* * When this flag is set, new sessions should be * blocked, and existing sessions will be torn * down by the code that set the flag, so this * need not run. */ if (sta_priv->flags & WLAN_STA_BLOCK_BA) return; atbm_os_mutexLock(&sta_priv->ampdu_mlme.mtx, 0); for (tid = 0; tid < STA_TID_NUM; tid++) { if (atbm_clear_bit(tid, sta_priv->ampdu_mlme.tid_rx_timer_expired)) ___ieee80211_stop_rx_ba_session( sta_priv, tid, WLAN_BACK_RECIPIENT, ATBM_WLAN_REASON_QSTA_TIMEOUT, ATBM_TRUE); if (atbm_clear_bit(tid, sta_priv->ampdu_mlme.tid_rx_stop_requested)) ___ieee80211_stop_rx_ba_session( sta_priv, tid, WLAN_BACK_RECIPIENT, ATBM_WLAN_REASON_UNSPECIFIED, ATBM_TRUE); tid_tx = sta_priv->ampdu_mlme.tid_start_tx[tid]; if (tid_tx) { /* * Assign it over to the normal tid_tx array * where it "goes live". */ atbm_spin_lock_bh(&sta_priv->lock); sta_priv->ampdu_mlme.tid_start_tx[tid] = NULL; /* could there be a race? */ if (sta_priv->ampdu_mlme.tid_tx[tid]) atbm_kfree(tid_tx); else ieee80211_assign_tid_tx(sta_priv, tid, tid_tx); atbm_spin_unlock_bh(&sta_priv->lock); ieee80211_tx_ba_session_handle_start(sta_priv, tid); continue; } tid_tx = sta_priv->ampdu_mlme.tid_tx[tid]; if (tid_tx && atbm_clear_bit(HT_AGG_STATE_WANT_STOP, &tid_tx->state)){ ___ieee80211_stop_tx_ba_session(sta_priv, tid, WLAN_BACK_INITIATOR, ATBM_TRUE); } } atbm_os_mutexUnLock(&sta_priv->ampdu_mlme.mtx); } void ieee80211_sta_tear_down_BA_sessions(struct atbmwifi_sta_priv *sta_priv, ATBM_BOOL tx) { int i; struct atbmwifi_common *hw_priv = sta_priv->priv->hw_priv; atbm_cancel_work(hw_priv, sta_priv->ampdu_mlme.work); for (i = 0; i < STA_TID_NUM; i++) { __ieee80211_stop_tx_ba_session(sta_priv, i, WLAN_BACK_INITIATOR, tx); __ieee80211_stop_rx_ba_session(sta_priv, i, WLAN_BACK_RECIPIENT, ATBM_WLAN_REASON_QSTA_LEAVE_QBSS, tx); } } ATBM_BOOL ieee80211_tx_prep_agg(struct atbmwifi_sta_priv *sta_priv, struct atbm_buff *skb, struct atbmwifi_ieee80211_tx_info * tx_info, struct tid_ampdu_tx *tid_tx, int tid) { ATBM_BOOL queued = ATBM_FALSE; if (atbm_test_bit(HT_AGG_STATE_OPERATIONAL, &tid_tx->state)) { tx_info->flags |= ATBM_IEEE80211_TX_CTL_AMPDU; } else if (atbm_test_bit(HT_AGG_STATE_WANT_START, &tid_tx->state)) { /* * nothing -- this aggregation session is being started * but that might still fail with the driver */ } else { atbm_spin_lock(&sta_priv->lock); /* * Need to re-check now, because we may get here * * 1) in the window during which the setup is actually * already done, but not marked yet because not all * packets are spliced over to the driver pending * queue yet -- if this happened we acquire the lock * either before or after the splice happens, but * need to recheck which of these cases happened. * * 2) during session teardown, if the OPERATIONAL bit * was cleared due to the teardown but the pointer * hasn't been assigned NULL yet (or we loaded it * before it was assigned) -- in this case it may * now be NULL which means we should just let the * packet pass through because splicing the frames * back is already done. */ tid_tx = sta_priv->ampdu_mlme.tid_tx[tid]; if (!tid_tx) { /* do nothing, let packet pass through */ } else if (atbm_test_bit(HT_AGG_STATE_OPERATIONAL, &tid_tx->state)) { tx_info->flags |= ATBM_IEEE80211_TX_CTL_AMPDU; } else { queued = ATBM_TRUE; atbm_skb_queue_tail(&tid_tx->pending, skb); } atbm_spin_unlock(&sta_priv->lock); } return queued; } atbm_void atbmwifi_tx_aggr_queued(struct atbmwifi_vif *priv, struct atbmwifi_sta_priv *sta_priv, int tid){ struct atbmwifi_common * hw_priv = priv->hw_priv; struct atbm_buff *skb; struct tid_ampdu_tx *tid_tx; struct atbmwifi_ieee80211_tx_info * tx_info; tid_tx = sta_priv->ampdu_mlme.tid_tx[tid]; atbm_os_mutexLock(&sta_priv->ampdu_mlme.mtx, 0); while ((skb = atbm_skb_dequeue(&tid_tx->pending)) != ATBM_NULL){ tx_info = ATBM_IEEE80211_SKB_TXCB(skb); if(ieee80211_tx_prep_agg(sta_priv, skb, tx_info, tid_tx, tid)) break; ieee80211_tx_h_sequence(sta_priv, skb, tid); atbmwifi_tx(hw_priv,skb,priv); } atbm_os_mutexUnLock(&sta_priv->ampdu_mlme.mtx); } atbm_void atbmwifi_tx_aggr_queued_nolock(struct atbmwifi_vif *priv, struct atbmwifi_sta_priv *sta_priv, struct tid_ampdu_tx *tid_tx, int tid){ struct atbmwifi_common * hw_priv = priv->hw_priv; struct atbm_buff *skb; sta_priv->flags |= WLAN_STA_BLOCK_BA; if(tid_tx){ while ((skb = atbm_skb_dequeue(&tid_tx->pending)) != ATBM_NULL){ ieee80211_tx_h_sequence(sta_priv, skb, tid); atbmwifi_tx(hw_priv,skb,priv); } } sta_priv->flags &= ~WLAN_STA_BLOCK_BA; } #endif