blob: ce635ebf3368b55eaac530814712fcd0396b2214 [file] [log] [blame]
/******************************************************************************
*
* Copyright(c) 2018 Intel Corporation
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name Intel Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*****************************************************************************/
#include "fw/api/rx.h"
#include <linux/module.h>
#include <linux/types.h>
#include "fw/dbg.h"
#include "xvt.h"
/*
* Returns true if sn2 - buffer_size < sn1 < sn2.
* To be used only in order to compare reorder buffer head with NSSN.
* We fully trust NSSN unless it is behind us due to reorder timeout.
* Reorder timeout can only bring us up to buffer_size SNs ahead of NSSN.
*/
static bool iwl_xvt_is_sn_less(uint16_t sn1, uint16_t sn2, uint16_t buffer_size) {
return ieee80211_sn_less(sn1, sn2) && !ieee80211_sn_less(sn1, sn2 - buffer_size);
}
static void iwl_xvt_release_frames(struct iwl_xvt* xvt, struct iwl_xvt_reorder_buffer* reorder_buf,
uint16_t nssn) {
uint16_t ssn = reorder_buf->head_sn;
iwl_assert_lock_held(&reorder_buf->lock);
IWL_DEBUG_HT(xvt, "reorder: release nssn=%d\n", nssn);
/* ignore nssn smaller than head sn - this can happen due to timeout */
if (iwl_xvt_is_sn_less(nssn, ssn, reorder_buf->buf_size)) {
return;
}
while (iwl_xvt_is_sn_less(ssn, nssn, reorder_buf->buf_size)) {
int index = ssn % reorder_buf->buf_size;
uint16_t frames_count = reorder_buf->entries[index];
ssn = ieee80211_sn_inc(ssn);
/*
* Reset frame count. Will have more than one frame for A-MSDU.
* entries=0 is valid as well since nssn indicates frames were
* received.
*/
IWL_DEBUG_HT(xvt, "reorder: deliver index=0x%x\n", index);
reorder_buf->entries[index] = 0;
reorder_buf->num_stored -= frames_count;
reorder_buf->stats.released += frames_count;
}
reorder_buf->head_sn = nssn;
/* don't mess with reorder timer for now */
}
void iwl_xvt_rx_frame_release(struct iwl_xvt* xvt, struct iwl_rx_packet* pkt) {
struct iwl_frame_release* release = (void*)pkt->data;
struct iwl_xvt_reorder_buffer* buffer;
int baid = release->baid;
IWL_DEBUG_HT(xvt, "Frame release notification for BAID %u, NSSN %d\n", baid,
le16_to_cpu(release->nssn));
if (WARN_ON_ONCE(baid >= IWL_MAX_BAID)) {
return;
}
buffer = &xvt->reorder_bufs[baid];
if (buffer->sta_id == IWL_XVT_INVALID_STA) {
return;
}
spin_lock_bh(&buffer->lock);
iwl_xvt_release_frames(xvt, buffer, le16_to_cpu(release->nssn));
spin_unlock_bh(&buffer->lock);
}
void iwl_xvt_destroy_reorder_buffer(struct iwl_xvt* xvt, struct iwl_xvt_reorder_buffer* buf) {
if (buf->sta_id == IWL_XVT_INVALID_STA) {
return;
}
spin_lock_bh(&buf->lock);
iwl_xvt_release_frames(xvt, buf, ieee80211_sn_add(buf->head_sn, buf->buf_size));
buf->sta_id = IWL_XVT_INVALID_STA;
spin_unlock_bh(&buf->lock);
}
static bool iwl_xvt_init_reorder_buffer(struct iwl_xvt_reorder_buffer* buf, uint8_t sta_id,
uint8_t tid, uint16_t ssn, uint8_t buf_size) {
int j;
if (WARN_ON(buf_size > ARRAY_SIZE(buf->entries))) {
return false;
}
buf->num_stored = 0;
buf->head_sn = ssn;
buf->buf_size = buf_size;
spin_lock_init(&buf->lock);
buf->queue = 0;
buf->sta_id = sta_id;
buf->tid = tid;
for (j = 0; j < buf->buf_size; j++) {
buf->entries[j] = 0;
}
memset(&buf->stats, 0, sizeof(buf->stats));
/* currently there's no need to mess with reorder timer */
return true;
}
bool iwl_xvt_reorder(struct iwl_xvt* xvt, struct iwl_rx_packet* pkt) {
struct iwl_rx_mpdu_desc* desc = (void*)pkt->data;
struct ieee80211_hdr* hdr;
uint32_t reorder = le32_to_cpu(desc->reorder_data);
struct iwl_xvt_reorder_buffer* buffer;
uint16_t tail;
bool last_subframe = desc->amsdu_info & IWL_RX_MPDU_AMSDU_LAST_SUBFRAME;
uint8_t sub_frame_idx = desc->amsdu_info & IWL_RX_MPDU_AMSDU_SUBFRAME_IDX_MASK;
bool amsdu = desc->mac_flags2 & IWL_RX_MPDU_MFLG2_AMSDU;
uint16_t nssn, sn, min_sn;
int index;
uint8_t baid;
uint8_t sta_id = desc->sta_id_flags & IWL_RX_MPDU_SIF_STA_ID_MASK;
uint8_t tid;
baid = (reorder & IWL_RX_MPDU_REORDER_BAID_MASK) >> IWL_RX_MPDU_REORDER_BAID_SHIFT;
if (baid >= IWL_MAX_BAID) {
return false;
}
if (xvt->trans->cfg->device_family >= IWL_DEVICE_FAMILY_22560) {
hdr = (void*)(pkt->data + sizeof(struct iwl_rx_mpdu_desc));
} else {
hdr = (void*)(pkt->data + IWL_RX_DESC_SIZE_V1);
}
/* not a data packet */
if (!ieee80211_is_data_qos(hdr->frame_control) || is_multicast_ether_addr(hdr->addr1)) {
return false;
}
if (unlikely(!ieee80211_is_data_present(hdr->frame_control))) {
return false;
}
nssn = reorder & IWL_RX_MPDU_REORDER_NSSN_MASK;
sn = (reorder & IWL_RX_MPDU_REORDER_SN_MASK) >> IWL_RX_MPDU_REORDER_SN_SHIFT;
min_sn = ieee80211_sn_less(sn, nssn) ? sn : nssn;
tid = *ieee80211_get_qos_ctl(hdr) & IEEE80211_QOS_CTL_TID_MASK;
/* Check if buffer needs to be initialized */
buffer = &xvt->reorder_bufs[baid];
if (buffer->sta_id == IWL_XVT_INVALID_STA) {
/* don't initialize until first valid packet comes through */
if (reorder & IWL_RX_MPDU_REORDER_BA_OLD_SN) {
return false;
}
if (!iwl_xvt_init_reorder_buffer(buffer, sta_id, tid, min_sn, IEEE80211_MAX_AMPDU_BUF_HT)) {
return false;
}
}
/* verify sta_id and tid match the reorder buffer params */
if (buffer->sta_id != sta_id || buffer->tid != tid) {
/* TODO: add add_ba/del_ba notifications */
WARN(1, "sta_id/tid doesn't match saved baid params\n");
return false;
}
spin_lock_bh(&buffer->lock);
/*
* If there was a significant jump in the nssn - adjust.
* If the SN is smaller than the NSSN it might need to first go into
* the reorder buffer, in which case we just release up to it and the
* rest of the function will take care of storing it and releasing up to
* the nssn
*/
if (!iwl_xvt_is_sn_less(nssn, buffer->head_sn + buffer->buf_size, buffer->buf_size) ||
!ieee80211_sn_less(sn, buffer->head_sn + buffer->buf_size)) {
iwl_xvt_release_frames(xvt, buffer, min_sn);
}
/* drop any outdated packets */
if (ieee80211_sn_less(sn, buffer->head_sn)) {
goto drop;
}
/* release immediately if allowed by nssn and no stored frames */
if (!buffer->num_stored && ieee80211_sn_less(sn, nssn)) {
if (iwl_xvt_is_sn_less(buffer->head_sn, nssn, buffer->buf_size) && (!amsdu || last_subframe)) {
buffer->head_sn = nssn;
}
/* No need to update AMSDU last SN - we are moving the head */
spin_unlock_bh(&buffer->lock);
buffer->stats.released++;
buffer->stats.skipped++;
return false;
}
index = sn % buffer->buf_size;
/*
* Check if we already stored this frame
* As AMSDU is either received or not as whole, logic is simple:
* If we have frames in that position in the buffer and the last frame
* originated from AMSDU had a different SN then it is a retransmission.
* If it is the same SN then if the subframe index is incrementing it
* is the same AMSDU - otherwise it is a retransmission.
*/
tail = buffer->entries[index];
if (tail && !amsdu) {
goto drop;
} else if (tail && (sn != buffer->last_amsdu || buffer->last_sub_index >= sub_frame_idx)) {
goto drop;
}
/* put in reorder buffer */
buffer->entries[index]++;
buffer->num_stored++;
if (amsdu) {
buffer->last_amsdu = sn;
buffer->last_sub_index = sub_frame_idx;
}
buffer->stats.reordered++;
/*
* We cannot trust NSSN for AMSDU sub-frames that are not the last.
* The reason is that NSSN advances on the first sub-frame, and may
* cause the reorder buffer to advance before all the sub-frames arrive.
* Example: reorder buffer contains SN 0 & 2, and we receive AMSDU with
* SN 1. NSSN for first sub frame will be 3 with the result of driver
* releasing SN 0,1, 2. When sub-frame 1 arrives - reorder buffer is
* already ahead and it will be dropped.
* If the last sub-frame is not on this queue - we will get frame
* release notification with up to date NSSN.
*/
if (!amsdu || last_subframe) {
iwl_xvt_release_frames(xvt, buffer, nssn);
}
spin_unlock_bh(&buffer->lock);
return true;
drop:
buffer->stats.dropped++;
spin_unlock_bh(&buffer->lock);
return true;
}