2025-05-10 21:58:58 +08:00

285 lines
8.6 KiB
C
Executable File

/******************************************************************************
*
* Copyright(c) 2019 Realtek Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* 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.
*
*****************************************************************************/
#define _PHL_RX_AGG_C_
#include "phl_headers.h"
/**
* phl_tid_ampdu_rx_free() - Free the tid ampdu rx entry specified by @r.
*
* The caller has to take care of potential race condition:
* - if it is in @rtw_phl_stainfo_t.tid_rx, should be called with
* @rtw_phl_stainfo_t.tid_rx_lock held
* - if it is not in @rtw_phl_stainfo_t.tid_rx, it can be called freely since
* other part of the code can't see it
*
* @phl_tid_ampdu_rx.removed can be set to make sure that the reorder_time will
* not be set again. This is usful when canceling the timer synchronously
* before releasing it.
*
* TODO: On macos, _os_kmem_free() should not be called with lock held since it
* may block.
*/
void phl_tid_ampdu_rx_free(struct phl_tid_ampdu_rx *r)
{
/* ref wil_tid_ampdu_rx_free() and ieee80211_free_tid_rx() */
u16 buf_size;
void *drv_priv;
int i;
struct rtw_phl_rx_pkt *pkt = NULL;
struct phl_hci_trx_ops *hci_trx_ops = NULL;
if (!r)
return;
buf_size = r->buf_size;
drv_priv = r->drv_priv;
hci_trx_ops = r->phl_info->hci_trx_ops;
for (i = 0; i < r->buf_size; i++) {
pkt = r->reorder_buf[i];
if (NULL != pkt)
hci_trx_ops->recycle_rx_pkt(r->phl_info, pkt);
}
_os_kmem_free(drv_priv, r->reorder_time,
buf_size * sizeof(u32));
_os_kmem_free(drv_priv, r->reorder_buf,
buf_size * sizeof(struct rtw_phl_rx_pkt *));
_os_kmem_free(drv_priv, r, sizeof(*r));
}
static inline void
_phl_cancel_rx_reorder_timer(struct phl_info_t *phl_info,
struct rtw_phl_stainfo_t *sta)
{
void *drv = phl_to_drvpriv(phl_info);
#ifdef PHL_PLATFORM_WINDOWS
/* Cancel the reorder_timer of the stainfo synchronously.
* Note that on Windows, _os_cancel_timer() does not guarantee that
* after the cancel, no timer callback will ever be called. Therefore
* @comp_sync is used for waiting this residual timer callback on
* Windows.*/
_os_event_reset(drv, &sta->comp_sync);
_os_cancel_timer(drv, &sta->reorder_timer);
_os_event_wait(drv, &sta->comp_sync, PHL_REORDER_TIMER_SYNC_TO_MS);
#else /*PHL_PLATFORM_LINUX && PHL_PLATFORM_AP*/
_os_cancel_timer(drv, &sta->reorder_timer);/*or _os_cancel_timer_async*/
#endif
}
void phl_free_rx_reorder(struct phl_info_t *phl_info,
struct rtw_phl_stainfo_t *sta)
{
void *drv = phl_to_drvpriv(phl_info);
u8 i = 0;
/* Mark the enrties as removed to make sure that reorder_timer of the
* stainfo will not be set again after the timer is canceled. */
_os_spinlock(drv, &sta->tid_rx_lock, _bh, NULL);
for (i = 0; i < ARRAY_SIZE(sta->tid_rx); i++)
if (sta->tid_rx[i])
sta->tid_rx[i]->removed = true;
_os_spinunlock(drv, &sta->tid_rx_lock, _bh, NULL);
_phl_cancel_rx_reorder_timer(phl_info, sta);
/* Free the tid ampdu rx entry */
_os_spinlock(drv, &sta->tid_rx_lock, _bh, NULL);
for (i = 0; i < ARRAY_SIZE(sta->tid_rx); i++) {
/* ref wil_disconnect_cid() */
if (!sta->tid_rx[i])
continue;
phl_tid_ampdu_rx_free(sta->tid_rx[i]);
sta->tid_rx[i] = NULL;
}
_os_spinunlock(drv, &sta->tid_rx_lock, _bh, NULL);
}
void rtw_phl_stop_rx_ba_session(void *phl, struct rtw_phl_stainfo_t *sta,
u16 tid)
{
/* ref wmi_evt_delba() and ___ieee80211_stop_rx_ba_session() */
struct phl_info_t *phl_info = (struct phl_info_t *)phl;
void *drv_priv = phl_to_drvpriv(phl_info);
struct phl_tid_ampdu_rx *r;
if (NULL == sta) {
PHL_TRACE(COMP_PHL_RECV, _PHL_WARNING_,
"rtw_phl_stop_rx_ba_session: station info is NULL!\n");
return;
}
PHL_INFO("Stop rx BA session for sta=0x%p, tid=%u\n", sta, tid);
rtw_hal_stop_ba_session(phl_info->hal, sta, tid);
if (tid >= ARRAY_SIZE(sta->tid_rx))
return;
_os_spinlock(drv_priv, &sta->tid_rx_lock, _bh, NULL);
if (!sta->tid_rx[tid]) {
PHL_INFO("No active session found for the specified sta tid pair\n");
_os_spinunlock(drv_priv, &sta->tid_rx_lock, _bh, NULL);
return;
}
r = sta->tid_rx[tid];
sta->tid_rx[tid] = NULL;
r->removed = true;
/* Note that it is safe to free the tid ampdu rx here. If reorder_timer
* callback is invoked, the tid_rx_lock is held and it does not do
* anything to the entry if it is NULL. */
phl_tid_ampdu_rx_free(r);
_os_spinunlock(drv_priv, &sta->tid_rx_lock, _bh, NULL);
PHL_INFO("Rx BA session for sta=0x%p, tid=%u freed\n", sta, tid);
}
struct phl_tid_ampdu_rx *phl_tid_ampdu_rx_alloc(struct phl_info_t *phl_info,
struct rtw_phl_stainfo_t *sta,
u16 timeout, u16 ssn, u16 tid,
u16 buf_size)
{
/* ref wil_tid_ampdu_rx_alloc() */
void *drv_priv = phl_to_drvpriv(phl_info);
struct phl_tid_ampdu_rx *r;
/* allocate r */
r = _os_kmem_alloc(drv_priv, sizeof(*r));
if (!r)
return NULL;
_os_mem_set(drv_priv, r, 0, sizeof(*r));
/* allocate reorder_buf */
r->reorder_buf =
_os_kmem_alloc(drv_priv,
buf_size * sizeof(struct rtw_phl_rx_pkt *));
if (!r->reorder_buf) {
_os_kmem_free(drv_priv, r, sizeof(*r));
return NULL;
}
_os_mem_set(drv_priv, r->reorder_buf, 0,
buf_size * sizeof(struct rtw_phl_rx_pkt *));
/* allocate reorder_time */
r->reorder_time =
_os_kmem_alloc(drv_priv, buf_size * sizeof(u32));
if (!r->reorder_time) {
_os_kmem_free(drv_priv, r->reorder_buf,
buf_size * sizeof(struct rtw_phl_rx_pkt *));
_os_kmem_free(drv_priv, r, sizeof(*r));
return NULL;
}
_os_mem_set(drv_priv, r->reorder_time, 0,
buf_size * sizeof(u32));
/* init other fields */
r->sta = sta;
r->ssn = ssn;
r->head_seq_num = ssn;
r->buf_size = buf_size;
r->stored_mpdu_num = 0;
r->tid = tid;
r->started = false;
r->drv_priv = drv_priv;
r->phl_info = phl_info;
return r;
}
enum rtw_phl_status
rtw_phl_start_rx_ba_session(void *phl, struct rtw_phl_stainfo_t *sta,
u8 dialog_token, u16 timeout, u16 start_seq_num,
u16 ba_policy, u16 tid, u16 buf_size)
{
/* ref wil_addba_rx_request() and ___ieee80211_start_rx_ba_session() */
struct phl_info_t *phl_info = (struct phl_info_t *)phl;
enum rtw_hal_status hal_sts = RTW_HAL_STATUS_FAILURE;
void *drv_priv = phl_to_drvpriv(phl_info);
struct phl_tid_ampdu_rx *r;
hal_sts = rtw_hal_start_ba_session(phl_info->hal, sta, dialog_token,
timeout, start_seq_num, ba_policy,
tid, buf_size);
/* TODO: sta status */
/* TODO: check sta capability */
PHL_INFO("Start rx BA session for sta=0x%p, tid=%u, buf_size=%u, timeout=%u\n",
sta, tid, buf_size, timeout);
/* apply policies */
if (ba_policy) {
PHL_ERR("BACK requested unsupported ba_policy == 1\n");
return RTW_PHL_STATUS_FAILURE;
}
/* apply buf_size */
if (buf_size == 0) {
PHL_INFO("Suggest BACK wsize %d\n", PHL_MAX_AGG_WSIZE);
buf_size = PHL_MAX_AGG_WSIZE;
}
/* allocate tid ampdu rx */
r = phl_tid_ampdu_rx_alloc(phl_info, sta, timeout, start_seq_num, tid,
buf_size);
if (!r) {
PHL_ERR("Failed to alloc tid ampdu rx\n");
return RTW_PHL_STATUS_RESOURCE;
}
/* apply */
_os_spinlock(drv_priv, &sta->tid_rx_lock, _bh, NULL);
if (sta->tid_rx[tid])
phl_tid_ampdu_rx_free(sta->tid_rx[tid]);
sta->tid_rx[tid] = r;
_os_spinunlock(drv_priv, &sta->tid_rx_lock, _bh, NULL);
return RTW_PHL_STATUS_SUCCESS;
}
void phl_notify_reorder_sleep(void *phl, struct rtw_phl_stainfo_t *sta)
{
struct phl_info_t *phl_info = (struct phl_info_t *)phl;
void *drv = phl_to_drvpriv(phl_info);
struct phl_tid_ampdu_rx *r;
u16 tid = 0;
if (NULL == sta) {
PHL_TRACE(COMP_PHL_RECV, _PHL_WARNING_,
"rtw_phl_stop_rx_ba_session: station info is NULL!\n");
return;
}
_os_spinlock(drv, &sta->tid_rx_lock, _bh, NULL);
for (tid = 0; tid < ARRAY_SIZE(sta->tid_rx); tid++) {
if (!sta->tid_rx[tid])
continue;
PHL_INFO("Notify rx BA session sleep for sta=0x%p, tid=%u\n",
sta, tid);
r = sta->tid_rx[tid];
r->sleep = true;
PHL_INFO("Rx BA session for sta=0x%p, tid=%u sleep\n", sta, tid);
}
_os_spinunlock(drv, &sta->tid_rx_lock, _bh, NULL);
}