blob: cd0ce0ff3348cc5f0de167689dbd9ccf9e8bda48 [file] [log] [blame]
/****************************************************************************
*
* Copyright (c) 2012 - 2019 Samsung Electronics Co., Ltd. All rights reserved
*
****************************************************************************/
#include "debug.h"
#include "dev.h"
#include "ba.h"
#include "mgt.h"
#include "sap.h"
/* Timeout (in milliseconds) for frames in MPDU reorder buffer
*
* When a frame is out of order, the frame is stored in Reorder buffer.
* Frames can be released from the buffer, if subsequent frames arrive such that
* frames can be ordered or when this timeout occurs
*/
static uint ba_mpdu_reorder_age_timeout = 100; /* 100 milliseconds */
module_param(ba_mpdu_reorder_age_timeout, uint, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(ba_mpdu_reorder_age_timeout, "Timeout (in ms) before a BA frame in Reorder buffer is passed to upper layers");
static bool ba_out_of_range_delba_enable = 1;
module_param(ba_out_of_range_delba_enable, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(ba_out_of_range_delba_enable, "Option to trigger DELBA on out of range frame (1: enable (default), 0: disable)");
#define BA_WINDOW_BOUNDARY 2048
#define SN_TO_INDEX(__ba_session_rx, __sn) (((__sn - __ba_session_rx->start_sn) & 0xFFF) % __ba_session_rx->buffer_size)
#define ADVANCE_EXPECTED_SN(__ba_session_rx) \
{ \
__ba_session_rx->expected_sn++; \
__ba_session_rx->expected_sn &= 0xFFF; \
}
#define FREE_BUFFER_SLOT(__ba_session_rx, __index) \
{ \
__ba_session_rx->occupied_slots--; \
__ba_session_rx->buffer[__index].active = false; \
}
#define IS_SN_LESS(sn1, sn2) ((((sn1) - (sn2)) & 0xFFF) > BA_WINDOW_BOUNDARY)
void slsi_rx_ba_init(struct slsi_dev *sdev)
{
slsi_spinlock_create(&sdev->rx_ba_buffer_pool_lock);
}
static struct slsi_ba_session_rx *slsi_rx_ba_alloc_buffer(struct net_device *dev)
{
struct netdev_vif *ndev_vif = netdev_priv(dev);
struct slsi_dev *sdev = ndev_vif->sdev;
struct slsi_ba_session_rx *buffer = NULL;
int i;
SLSI_NET_DBG3(dev, SLSI_RX_BA, "RX BA buffer pool status: %d,%d,%d,%d,%d,%d,%d,%d\n",
sdev->rx_ba_buffer_pool[0].used, sdev->rx_ba_buffer_pool[1].used, sdev->rx_ba_buffer_pool[2].used,
sdev->rx_ba_buffer_pool[3].used, sdev->rx_ba_buffer_pool[4].used, sdev->rx_ba_buffer_pool[5].used,
sdev->rx_ba_buffer_pool[6].used, sdev->rx_ba_buffer_pool[7].used);
slsi_spinlock_lock(&sdev->rx_ba_buffer_pool_lock);
for (i = 0; i < SLSI_MAX_RX_BA_SESSIONS; i++) {
if (!sdev->rx_ba_buffer_pool[i].used) {
sdev->rx_ba_buffer_pool[i].used = true;
buffer = &sdev->rx_ba_buffer_pool[i];
break;
}
}
slsi_spinlock_unlock(&sdev->rx_ba_buffer_pool_lock);
if (!buffer)
SLSI_NET_ERR(dev, "No free RX BA buffer\n");
return buffer;
}
static void slsi_rx_ba_free_buffer(struct net_device *dev, struct slsi_peer *peer, int tid)
{
struct netdev_vif *ndev_vif = netdev_priv(dev);
struct slsi_dev *sdev = ndev_vif->sdev;
slsi_spinlock_lock(&sdev->rx_ba_buffer_pool_lock);
if (peer && peer->ba_session_rx[tid]) {
peer->ba_session_rx[tid]->used = false;
peer->ba_session_rx[tid] = NULL;
}
slsi_spinlock_unlock(&sdev->rx_ba_buffer_pool_lock);
SLSI_NET_DBG3(dev, SLSI_RX_BA, "RX BA buffer pool status: %d,%d,%d,%d,%d,%d,%d,%d\n",
sdev->rx_ba_buffer_pool[0].used, sdev->rx_ba_buffer_pool[1].used, sdev->rx_ba_buffer_pool[2].used,
sdev->rx_ba_buffer_pool[3].used, sdev->rx_ba_buffer_pool[4].used, sdev->rx_ba_buffer_pool[5].used,
sdev->rx_ba_buffer_pool[6].used, sdev->rx_ba_buffer_pool[7].used);
}
/* This code - slsi_ba_process_complete()
* must be called with the ba_lock spinlock held.
*/
void slsi_ba_process_complete(struct net_device *dev, bool ctx_napi)
{
struct netdev_vif *ndev_vif = netdev_priv(dev);
struct sk_buff *skb;
while ((skb = skb_dequeue(&ndev_vif->ba_complete)) != NULL) {
local_bh_disable();
slsi_spinlock_unlock(&ndev_vif->ba_lock);
slsi_rx_data_deliver_skb(ndev_vif->sdev, dev, skb, ctx_napi);
slsi_spinlock_lock(&ndev_vif->ba_lock);
local_bh_enable();
}
}
static void slsi_ba_signal_process_complete(struct net_device *dev)
{
struct netdev_vif *ndev_vif = netdev_priv(dev);
atomic_set(&ndev_vif->ba_flush, 1);
#ifndef CONFIG_SCSC_WLAN_RX_NAPI
slsi_skb_schedule_work(&ndev_vif->rx_data);
#endif
}
static void ba_add_frame_to_ba_complete(struct net_device *dev, struct slsi_ba_frame_desc *frame_desc)
{
struct netdev_vif *ndev_vif = netdev_priv(dev);
skb_queue_tail(&ndev_vif->ba_complete, frame_desc->signal);
}
static void ba_update_expected_sn(struct net_device *dev,
struct slsi_ba_session_rx *ba_session_rx, u16 sn)
{
u32 i, j;
u16 gap;
gap = (sn - ba_session_rx->expected_sn) & 0xFFF;
SLSI_NET_DBG3(dev, SLSI_RX_BA, "Proccess the frames up to new expected_sn = %d gap = %d\n", sn, gap);
for (j = 0; j < gap && j < ba_session_rx->buffer_size; j++) {
i = SN_TO_INDEX(ba_session_rx, ba_session_rx->expected_sn);
SLSI_NET_DBG3(dev, SLSI_RX_BA, "Proccess the slot index = %d\n", i);
if (ba_session_rx->buffer[i].active) {
ba_add_frame_to_ba_complete(dev, &ba_session_rx->buffer[i]);
SLSI_NET_DBG3(dev, SLSI_RX_BA, "Proccess the frame at index = %d expected_sn = %d\n", i, ba_session_rx->expected_sn);
FREE_BUFFER_SLOT(ba_session_rx, i);
} else {
SLSI_NET_DBG3(dev, SLSI_RX_BA, "Empty slot at index = %d\n", i);
}
ADVANCE_EXPECTED_SN(ba_session_rx);
}
ba_session_rx->expected_sn = sn;
}
static void ba_complete_ready_sequence(struct net_device *dev,
struct slsi_ba_session_rx *ba_session_rx)
{
int i;
i = SN_TO_INDEX(ba_session_rx, ba_session_rx->expected_sn);
while (ba_session_rx->buffer[i].active) {
ba_add_frame_to_ba_complete(dev, &ba_session_rx->buffer[i]);
SLSI_NET_DBG4(dev, SLSI_RX_BA, "Completed stored frame (expected_sn=%d) at i = %d\n",
ba_session_rx->expected_sn, i);
FREE_BUFFER_SLOT(ba_session_rx, i);
ADVANCE_EXPECTED_SN(ba_session_rx);
i = SN_TO_INDEX(ba_session_rx, ba_session_rx->expected_sn);
}
}
static void ba_scroll_window(struct net_device *dev,
struct slsi_ba_session_rx *ba_session_rx, u16 sn)
{
if (((sn - ba_session_rx->expected_sn) & 0xFFF) <= BA_WINDOW_BOUNDARY) {
ba_update_expected_sn(dev, ba_session_rx, sn);
ba_complete_ready_sequence(dev, ba_session_rx);
}
}
static void ba_delete_ba_on_old_frame(struct net_device *dev, struct slsi_peer *peer,
struct slsi_ba_session_rx *ba_session_rx, u16 sn)
{
struct netdev_vif *ndev_vif = netdev_priv(dev);
struct sap_drv_ma_to_mlme_delba_req *delba_req;
struct sk_buff *skb = NULL;
SLSI_NET_DBG4(dev, SLSI_RX_BA, "sn=%d\n", sn);
/* construct a message for MLME */
skb = alloc_skb(sizeof(struct sap_drv_ma_to_mlme_delba_req), GFP_ATOMIC);
if (WARN_ON(!skb))
return;
ba_session_rx->closing = true;
slsi_skb_cb_init(skb)->sig_length = sizeof(struct sap_drv_ma_to_mlme_delba_req);
slsi_skb_cb_get(skb)->data_length = sizeof(struct sap_drv_ma_to_mlme_delba_req);
delba_req = (struct sap_drv_ma_to_mlme_delba_req *)skb_put(skb, sizeof(struct sap_drv_ma_to_mlme_delba_req));
delba_req->header.id = cpu_to_le16(SAP_DRV_MA_TO_MLME_DELBA_REQ);
delba_req->header.receiver_pid = 0;
delba_req->header.sender_pid = 0;
delba_req->header.fw_reference = 0;
delba_req->vif = ndev_vif->ifnum;
memcpy(delba_req->peer_qsta_address, peer->address, ETH_ALEN);
delba_req->sequence_number = sn;
delba_req->user_priority = ba_session_rx->tid;
delba_req->reason = FAPI_REASONCODE_OUT_OF_RANGE_SEQUENCE_NUMBER;
delba_req->direction = FAPI_DIRECTION_RECEIVE;
/* queue the message for MLME SAP */
slsi_rx_enqueue_netdev_mlme(ndev_vif->sdev, skb, fapi_get_vif(skb));
}
static int ba_consume_frame_or_get_buffer_index(struct net_device *dev, struct slsi_peer *peer,
struct slsi_ba_session_rx *ba_session_rx, u16 sn, struct slsi_ba_frame_desc *frame_desc, bool *stop_timer)
{
int i;
u16 sn_temp;
#ifdef CONFIG_SCSC_WLAN_STA_ENHANCED_ARP_DETECT
struct netdev_vif *ndev_vif = netdev_priv(dev);
#endif
*stop_timer = false;
if (((sn - ba_session_rx->expected_sn) & 0xFFF) <= BA_WINDOW_BOUNDARY) {
/* Once we are in BA window, set the flag for BA trigger */
if (!ba_session_rx->trigger_ba_after_ssn)
ba_session_rx->trigger_ba_after_ssn = true;
sn_temp = ba_session_rx->expected_sn + ba_session_rx->buffer_size;
SLSI_NET_DBG4(dev, SLSI_RX_BA, "New frame: sn=%d\n", sn);
if (!(((sn - sn_temp) & 0xFFF) > BA_WINDOW_BOUNDARY)) {
u16 new_expected_sn;
SLSI_NET_DBG2(dev, SLSI_RX_BA, "Frame is out of window\n");
sn_temp = (sn - ba_session_rx->buffer_size) & 0xFFF;
if (ba_session_rx->timer_on)
*stop_timer = true;
new_expected_sn = (sn_temp + 1) & 0xFFF;
ba_update_expected_sn(dev, ba_session_rx, new_expected_sn);
}
i = -1;
if (sn == ba_session_rx->expected_sn) {
SLSI_NET_DBG4(dev, SLSI_RX_BA, "sn = ba_session_rx->expected_sn = %d\n", sn);
if (ba_session_rx->timer_on)
*stop_timer = true;
ADVANCE_EXPECTED_SN(ba_session_rx);
ba_add_frame_to_ba_complete(dev, frame_desc);
} else {
i = SN_TO_INDEX(ba_session_rx, sn);
SLSI_NET_DBG4(dev, SLSI_RX_BA, "sn (%d) != ba_session_rx->expected_sn(%d), i = %d\n", sn, ba_session_rx->expected_sn, i);
if (ba_session_rx->buffer[i].active) {
SLSI_NET_DBG3(dev, SLSI_RX_BA, "free frame at i = %d\n", i);
i = -1;
#ifdef CONFIG_SCSC_SMAPPER
hip4_smapper_free_mapped_skb(frame_desc->signal);
#endif
kfree_skb(frame_desc->signal);
}
}
if (!IS_SN_LESS(sn, ba_session_rx->highest_received_sn))
ba_session_rx->highest_received_sn = sn;
} else {
i = -1;
if (!ba_session_rx->trigger_ba_after_ssn) {
SLSI_NET_DBG3(dev, SLSI_RX_BA, "frame before ssn, pass it up: sn=%d\n", sn);
ba_add_frame_to_ba_complete(dev, frame_desc);
} else {
if (slsi_is_tdls_peer(dev, peer)) {
/* Don't drop old frames in TDLS AMPDU-reordering for interoperability with third party devices.
* When the TDLS link is established the peer sends few packets with AP's sequence number.
* BA reorder logic updates the expected sequence number. After that peer sends packets with
* starting sequence number negotiated in BA (0). But those frames are getting dropped here.
* Because of this TCP traffic does not work and TDLS link is getting disconnected.
*/
SLSI_NET_DBG1(dev, SLSI_RX_BA, "tdls: forward old frame: sn=%d, expected_sn=%d\n", sn, ba_session_rx->expected_sn);
ba_add_frame_to_ba_complete(dev, frame_desc);
} else {
/* this frame is deemed as old. But it may so happen that the reorder process did not wait long
* enough for this frame and moved to new window. So check here that the current frame still lies in
* originators transmit window by comparing it with highest sequence number received from originator.
*
* If it lies in the window pass the frame to next process else pass the frame and initiate DELBA procedure.
*/
if (IS_SN_LESS(ba_session_rx->highest_received_sn, ((sn + ba_session_rx->buffer_size) & 0xFFF))) {
SLSI_NET_DBG3(dev, SLSI_RX_BA, "old frame, but still in window: sn=%d, highest_received_sn=%d\n", sn, ba_session_rx->highest_received_sn);
ba_add_frame_to_ba_complete(dev, frame_desc);
} else {
if (!ba_out_of_range_delba_enable) {
SLSI_NET_WARN(dev, "old frame, drop: sn=%d, expected_sn=%d\n", sn, ba_session_rx->expected_sn);
#ifdef CONFIG_SCSC_WLAN_STA_ENHANCED_ARP_DETECT
if (ndev_vif->enhanced_arp_detect_enabled)
slsi_fill_enhanced_arp_out_of_order_drop_counter(ndev_vif, frame_desc->signal);
#endif
#ifdef CONFIG_SCSC_SMAPPER
hip4_smapper_free_mapped_skb(frame_desc->signal);
#endif
kfree_skb(frame_desc->signal);
} else {
SLSI_NET_DBG3(dev, SLSI_RX_BA, "old frame, accept but as countermeasure initiate delete BA: sn=%d, expected_sn=%d\n", sn, ba_session_rx->expected_sn);
ba_add_frame_to_ba_complete(dev, frame_desc);
if (!ba_session_rx->closing)
ba_delete_ba_on_old_frame(dev, peer, ba_session_rx, sn);
}
}
}
}
}
return i;
}
#if KERNEL_VERSION(4, 15, 0) <= LINUX_VERSION_CODE
static void slsi_ba_aging_timeout_handler(struct timer_list *t)
#else
static void slsi_ba_aging_timeout_handler(unsigned long data)
#endif
{
#if KERNEL_VERSION(4, 15, 0) <= LINUX_VERSION_CODE
struct slsi_ba_session_rx *ba_session_rx = from_timer(ba_session_rx, t, ba_age_timer);
#else
struct slsi_ba_session_rx *ba_session_rx = (struct slsi_ba_session_rx *)data;
#endif
u8 i, j;
u8 gap = 1;
u16 temp_sn;
struct net_device *dev = ba_session_rx->dev;
struct netdev_vif *ndev_vif = netdev_priv(dev);
SLSI_NET_DBG3(dev, SLSI_RX_BA, "\n");
slsi_spinlock_lock(&ndev_vif->ba_lock);
ba_session_rx->timer_on = false;
if (ba_session_rx->active && ba_session_rx->occupied_slots) {
/* expected sequence has not arrived so start searching from next
* sequence number until a frame is available and determine the gap.
* Release all the frames upto next hole from the reorder buffer.
*/
temp_sn = (ba_session_rx->expected_sn + 1) & 0xFFF;
for (j = 0; j < ba_session_rx->buffer_size; j++) {
i = SN_TO_INDEX(ba_session_rx, temp_sn);
if (ba_session_rx->buffer[i].active) {
while (gap--)
ADVANCE_EXPECTED_SN(ba_session_rx);
SLSI_NET_DBG3(dev, SLSI_RX_BA, "Completed stored frame (expected_sn=%d) at i = %d\n", ba_session_rx->expected_sn, i);
ba_add_frame_to_ba_complete(dev, &ba_session_rx->buffer[i]);
FREE_BUFFER_SLOT(ba_session_rx, i);
ADVANCE_EXPECTED_SN(ba_session_rx);
ba_complete_ready_sequence(dev, ba_session_rx);
break;
}
/* advance temp sequence number and frame gap */
temp_sn = (temp_sn + 1) & 0xFFF;
gap++;
}
/* Check for next hole in the buffer, if hole exists create the timer for next missing frame */
/* do not rearm the timer if BA session is going down */
if (ba_session_rx->active && ba_session_rx->occupied_slots) {
SLSI_NET_DBG3(dev, SLSI_RX_BA, "Timer start\n");
mod_timer(&ba_session_rx->ba_age_timer, jiffies + msecs_to_jiffies(ba_mpdu_reorder_age_timeout));
ba_session_rx->timer_on = true;
}
/* Process the data now marked as completed */
#ifdef CONFIG_SCSC_WLAN_RX_NAPI
slsi_ba_process_complete(dev, false);
#else
slsi_ba_signal_process_complete(dev);
#endif
}
slsi_spinlock_unlock(&ndev_vif->ba_lock);
}
int slsi_ba_process_frame(struct net_device *dev, struct slsi_peer *peer,
struct sk_buff *skb, u16 sequence_number, u16 tid)
{
struct netdev_vif *ndev_vif = netdev_priv(dev);
int i;
struct slsi_ba_session_rx *ba_session_rx = peer->ba_session_rx[tid];
struct slsi_ba_frame_desc frame_desc;
bool stop_timer = false;
SLSI_NET_DBG4(dev, SLSI_RX_BA, "Got frame(sn=%d)\n", sequence_number);
if (WARN_ON(tid > FAPI_PRIORITY_QOS_UP7)) {
SLSI_NET_ERR(dev, "tid=%d\n", tid);
return -EINVAL;
}
if (!ba_session_rx)
return -EINVAL;
slsi_spinlock_lock(&ndev_vif->ba_lock);
if (!ba_session_rx->active) {
SLSI_NET_ERR(dev, "No BA session exists\n");
slsi_spinlock_unlock(&ndev_vif->ba_lock);
return -EINVAL;
}
frame_desc.signal = skb;
frame_desc.sn = sequence_number;
frame_desc.active = true;
i = ba_consume_frame_or_get_buffer_index(dev, peer, ba_session_rx, sequence_number, &frame_desc, &stop_timer);
if (i >= 0) {
SLSI_NET_DBG4(dev, SLSI_RX_BA, "Store frame(sn=%d) at i = %d\n", sequence_number, i);
ba_session_rx->buffer[i] = frame_desc;
ba_session_rx->occupied_slots++;
} else {
SLSI_NET_DBG4(dev, SLSI_RX_BA, "Frame consumed - sn = %d\n", sequence_number);
}
ba_complete_ready_sequence(dev, ba_session_rx);
/* Timer decision:
*
* If the timer is not running (timer_on=false)
* Start the timer if there are holes (occupied_slots!=0)
*
* If the timer is running (timer_on=true)
* Stop the timer if
* There are no holes (occupied_slots=0)
* Restart the timer if
* stop_timer=true and there are holes (occupied_slots!=0)
* Leave the timer running (do nothing) if
* stop_timer=false and there are holes (occupied_slots!=0)
*/
if (!ba_session_rx->timer_on) {
if (ba_session_rx->occupied_slots) {
stop_timer = false;
SLSI_NET_DBG3(dev, SLSI_RX_BA, "Timer start\n");
mod_timer(&ba_session_rx->ba_age_timer, jiffies + msecs_to_jiffies(ba_mpdu_reorder_age_timeout));
ba_session_rx->timer_on = true;
}
} else if (!ba_session_rx->occupied_slots) {
stop_timer = true;
} else if (stop_timer) {
stop_timer = false;
SLSI_NET_DBG3(dev, SLSI_RX_BA, "Timer restart\n");
mod_timer(&ba_session_rx->ba_age_timer, jiffies + msecs_to_jiffies(ba_mpdu_reorder_age_timeout));
ba_session_rx->timer_on = true;
}
if (stop_timer) {
ba_session_rx->timer_on = false;
slsi_spinlock_unlock(&ndev_vif->ba_lock);
SLSI_NET_DBG3(dev, SLSI_RX_BA, "Timer stop\n");
del_timer_sync(&ba_session_rx->ba_age_timer);
} else {
slsi_spinlock_unlock(&ndev_vif->ba_lock);
}
slsi_ba_signal_process_complete(dev);
return 0;
}
bool slsi_ba_check(struct slsi_peer *peer, u16 tid)
{
if (tid > FAPI_PRIORITY_QOS_UP7)
return false;
if (!peer->ba_session_rx[tid])
return false;
return peer->ba_session_rx[tid]->active;
}
static void __slsi_rx_ba_stop(struct net_device *dev, struct slsi_ba_session_rx *ba_session_rx)
{
u8 i, j;
SLSI_NET_DBG1(dev, SLSI_RX_BA, "Stopping BA session: tid = %d\n", ba_session_rx->tid);
if (WARN_ON(!ba_session_rx->active)) {
SLSI_NET_ERR(dev, "No BA session exists\n");
return;
}
for (i = SN_TO_INDEX(ba_session_rx, ba_session_rx->expected_sn), j = 0;
j < ba_session_rx->buffer_size; i++, j++) {
i %= ba_session_rx->buffer_size;
if (ba_session_rx->buffer[i].active) {
ba_add_frame_to_ba_complete(dev, &ba_session_rx->buffer[i]);
SLSI_NET_DBG3(dev, SLSI_RX_BA, "Completed stored frame at i = %d\n", i);
FREE_BUFFER_SLOT(ba_session_rx, i);
}
}
#ifdef CONFIG_SCSC_WLAN_RX_NAPI
slsi_ba_process_complete(dev, false);
#else
slsi_ba_signal_process_complete(dev);
#endif
ba_session_rx->active = false;
}
static void slsi_rx_ba_stop_lock_held(struct net_device *dev, struct slsi_ba_session_rx *ba_session_rx)
{
struct netdev_vif *ndev_vif = netdev_priv(dev);
__slsi_rx_ba_stop(dev, ba_session_rx);
ba_session_rx->timer_on = false;
slsi_spinlock_unlock(&ndev_vif->ba_lock);
del_timer_sync(&ba_session_rx->ba_age_timer);
slsi_spinlock_lock(&ndev_vif->ba_lock);
}
static void slsi_rx_ba_stop_lock_unheld(struct net_device *dev, struct slsi_ba_session_rx *ba_session_rx)
{
struct netdev_vif *ndev_vif = netdev_priv(dev);
slsi_spinlock_lock(&ndev_vif->ba_lock);
__slsi_rx_ba_stop(dev, ba_session_rx);
ba_session_rx->timer_on = false;
slsi_spinlock_unlock(&ndev_vif->ba_lock);
del_timer_sync(&ba_session_rx->ba_age_timer);
}
void slsi_rx_ba_stop_all(struct net_device *dev, struct slsi_peer *peer)
{
int i;
for (i = 0; i < NUM_BA_SESSIONS_PER_PEER; i++)
if (peer->ba_session_rx[i] && peer->ba_session_rx[i]->active) {
slsi_rx_ba_stop_lock_unheld(dev, peer->ba_session_rx[i]);
slsi_rx_ba_free_buffer(dev, peer, i);
}
}
static int slsi_rx_ba_start(struct net_device *dev,
struct slsi_ba_session_rx *ba_session_rx,
u16 tid, u16 buffer_size, u16 start_sn)
{
struct netdev_vif *ndev_vif = netdev_priv(dev);
SLSI_NET_DBG1(dev, SLSI_RX_BA, "Request to start a new BA session tid=%d buffer_size=%d start_sn=%d\n",
tid, buffer_size, start_sn);
if (WARN_ON((!buffer_size) || (buffer_size > SLSI_BA_BUFFER_SIZE_MAX))) {
SLSI_NET_ERR(dev, "Invalid window size: buffer_size=%d\n", buffer_size);
return -EINVAL;
}
slsi_spinlock_lock(&ndev_vif->ba_lock);
if (ba_session_rx->active) {
SLSI_NET_DBG1(dev, SLSI_RX_BA, "BA session already exists\n");
if ((ba_session_rx->buffer_size == buffer_size) &&
(ba_session_rx->expected_sn == start_sn)) {
SLSI_NET_DBG1(dev, SLSI_RX_BA,
"BA session tid=%d already exists. The parameters match so keep the existing session\n",
tid);
slsi_spinlock_unlock(&ndev_vif->ba_lock);
return 0;
}
SLSI_NET_DBG1(dev, SLSI_RX_BA, "Parameters don't match so stop the existing BA session: tid=%d\n", tid);
slsi_rx_ba_stop_lock_held(dev, ba_session_rx);
}
ba_session_rx->dev = dev;
ba_session_rx->buffer_size = buffer_size;
ba_session_rx->start_sn = start_sn;
ba_session_rx->expected_sn = start_sn;
ba_session_rx->highest_received_sn = 0;
ba_session_rx->closing = false;
ba_session_rx->trigger_ba_after_ssn = false;
ba_session_rx->tid = tid;
ba_session_rx->timer_on = false;
#if KERNEL_VERSION(4, 15, 0) <= LINUX_VERSION_CODE
timer_setup(&ba_session_rx->ba_age_timer, slsi_ba_aging_timeout_handler, 0);
#else
ba_session_rx->ba_age_timer.function = slsi_ba_aging_timeout_handler;
ba_session_rx->ba_age_timer.data = (unsigned long)ba_session_rx;
init_timer(&ba_session_rx->ba_age_timer);
#endif
ba_session_rx->active = true;
SLSI_NET_DBG1(dev, SLSI_RX_BA, "Started a new BA session tid=%d buffer_size=%d start_sn=%d\n",
tid, buffer_size, start_sn);
slsi_spinlock_unlock(&ndev_vif->ba_lock);
slsi_ba_signal_process_complete(dev);
return 0;
}
void slsi_ba_update_window(struct net_device *dev,
struct slsi_ba_session_rx *ba_session_rx, u16 sequence_number)
{
struct netdev_vif *ndev_vif = netdev_priv(dev);
slsi_spinlock_lock(&ndev_vif->ba_lock);
if (WARN_ON(!ba_session_rx->active)) {
SLSI_NET_ERR(dev, "No BA session exists\n");
slsi_spinlock_unlock(&ndev_vif->ba_lock);
return;
}
ba_scroll_window(dev, ba_session_rx, sequence_number);
#ifdef CONFIG_SCSC_WLAN_RX_NAPI
slsi_ba_process_complete(dev, false);
#else
slsi_ba_signal_process_complete(dev);
#endif
slsi_spinlock_unlock(&ndev_vif->ba_lock);
}
void slsi_handle_blockack(struct net_device *dev, struct slsi_peer *peer,
u16 vif, u8 *peer_qsta_address, u16 parameter_set, u16 sequence_number,
u16 reason_code, u16 direction)
{
struct slsi_ba_session_rx *ba_session_rx;
u16 user_priority = (parameter_set >> 2) & 0x000F;
u16 buffer_size = (parameter_set >> 6) & 0x03FF;
SLSI_UNUSED_PARAMETER(vif);
SLSI_UNUSED_PARAMETER(peer_qsta_address);
if (WARN_ON(user_priority > FAPI_PRIORITY_QOS_UP7)) {
SLSI_NET_ERR(dev, "Invalid user_priority=%d\n", user_priority);
return;
}
switch (direction) {
case FAPI_DIRECTION_TRANSMIT:
break;
case FAPI_DIRECTION_RECEIVE:
ba_session_rx = peer->ba_session_rx[user_priority];
switch (reason_code) {
case FAPI_REASONCODE_START:
if (!peer->ba_session_rx[user_priority])
peer->ba_session_rx[user_priority] = slsi_rx_ba_alloc_buffer(dev);
if (peer->ba_session_rx[user_priority])
if (slsi_rx_ba_start(dev, peer->ba_session_rx[user_priority], user_priority, buffer_size, sequence_number) != 0)
slsi_rx_ba_free_buffer(dev, peer, user_priority);
break;
case FAPI_REASONCODE_END:
if (ba_session_rx) {
slsi_rx_ba_stop_lock_unheld(dev, ba_session_rx);
slsi_rx_ba_free_buffer(dev, peer, user_priority);
}
break;
case FAPI_REASONCODE_UNSPECIFIED_REASON:
if (ba_session_rx)
slsi_ba_update_window(dev, ba_session_rx, sequence_number);
break;
default:
SLSI_NET_ERR(dev, "Invalid value: reason_code=%d\n", reason_code);
break;
}
break;
default:
SLSI_NET_ERR(dev, "Invalid value: direction=%d\n", direction);
break;
}
}