2014-08-06 15:31:51 +08:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2014 Qualcomm Atheros, Inc.
|
|
|
|
*
|
|
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
|
|
* copyright notice and this permission notice appear in all copies.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
2014-02-27 22:20:44 +08:00
|
|
|
#include "wil6210.h"
|
|
|
|
#include "txrx.h"
|
|
|
|
|
|
|
|
#define SEQ_MODULO 0x1000
|
|
|
|
#define SEQ_MASK 0xfff
|
|
|
|
|
|
|
|
static inline int seq_less(u16 sq1, u16 sq2)
|
|
|
|
{
|
|
|
|
return ((sq1 - sq2) & SEQ_MASK) > (SEQ_MODULO >> 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline u16 seq_inc(u16 sq)
|
|
|
|
{
|
|
|
|
return (sq + 1) & SEQ_MASK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline u16 seq_sub(u16 sq1, u16 sq2)
|
|
|
|
{
|
|
|
|
return (sq1 - sq2) & SEQ_MASK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int reorder_index(struct wil_tid_ampdu_rx *r, u16 seq)
|
|
|
|
{
|
|
|
|
return seq_sub(seq, r->ssn) % r->buf_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void wil_release_reorder_frame(struct wil6210_priv *wil,
|
|
|
|
struct wil_tid_ampdu_rx *r,
|
|
|
|
int index)
|
|
|
|
{
|
|
|
|
struct net_device *ndev = wil_to_ndev(wil);
|
|
|
|
struct sk_buff *skb = r->reorder_buf[index];
|
|
|
|
|
|
|
|
if (!skb)
|
|
|
|
goto no_frame;
|
|
|
|
|
|
|
|
/* release the frame from the reorder ring buffer */
|
|
|
|
r->stored_mpdu_num--;
|
|
|
|
r->reorder_buf[index] = NULL;
|
|
|
|
wil_netif_rx_any(skb, ndev);
|
|
|
|
|
|
|
|
no_frame:
|
|
|
|
r->head_seq_num = seq_inc(r->head_seq_num);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void wil_release_reorder_frames(struct wil6210_priv *wil,
|
|
|
|
struct wil_tid_ampdu_rx *r,
|
|
|
|
u16 hseq)
|
|
|
|
{
|
|
|
|
int index;
|
|
|
|
|
2014-05-27 19:45:50 +08:00
|
|
|
/* note: this function is never called with
|
|
|
|
* hseq preceding r->head_seq_num, i.e it is always true
|
|
|
|
* !seq_less(hseq, r->head_seq_num)
|
|
|
|
* and thus on loop exit it should be
|
|
|
|
* r->head_seq_num == hseq
|
|
|
|
*/
|
|
|
|
while (seq_less(r->head_seq_num, hseq) && r->stored_mpdu_num) {
|
2014-02-27 22:20:44 +08:00
|
|
|
index = reorder_index(r, r->head_seq_num);
|
|
|
|
wil_release_reorder_frame(wil, r, index);
|
|
|
|
}
|
2014-05-27 19:45:50 +08:00
|
|
|
r->head_seq_num = hseq;
|
2014-02-27 22:20:44 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void wil_reorder_release(struct wil6210_priv *wil,
|
|
|
|
struct wil_tid_ampdu_rx *r)
|
|
|
|
{
|
|
|
|
int index = reorder_index(r, r->head_seq_num);
|
|
|
|
|
|
|
|
while (r->reorder_buf[index]) {
|
|
|
|
wil_release_reorder_frame(wil, r, index);
|
|
|
|
index = reorder_index(r, r->head_seq_num);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void wil_rx_reorder(struct wil6210_priv *wil, struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
struct net_device *ndev = wil_to_ndev(wil);
|
|
|
|
struct vring_rx_desc *d = wil_skb_rxdesc(skb);
|
|
|
|
int tid = wil_rxdesc_tid(d);
|
|
|
|
int cid = wil_rxdesc_cid(d);
|
|
|
|
int mid = wil_rxdesc_mid(d);
|
|
|
|
u16 seq = wil_rxdesc_seq(d);
|
2014-12-23 15:47:21 +08:00
|
|
|
int mcast = wil_rxdesc_mcast(d);
|
2014-02-27 22:20:44 +08:00
|
|
|
struct wil_sta_info *sta = &wil->sta[cid];
|
2014-09-10 21:34:42 +08:00
|
|
|
struct wil_tid_ampdu_rx *r;
|
2014-02-27 22:20:44 +08:00
|
|
|
u16 hseq;
|
|
|
|
int index;
|
2014-09-10 21:34:42 +08:00
|
|
|
unsigned long flags;
|
2014-02-27 22:20:44 +08:00
|
|
|
|
2014-12-23 15:47:21 +08:00
|
|
|
wil_dbg_txrx(wil, "MID %d CID %d TID %d Seq 0x%03x mcast %01x\n",
|
|
|
|
mid, cid, tid, seq, mcast);
|
|
|
|
|
|
|
|
if (unlikely(mcast)) {
|
|
|
|
wil_netif_rx_any(skb, ndev);
|
|
|
|
return;
|
|
|
|
}
|
2014-02-27 22:20:44 +08:00
|
|
|
|
2014-09-10 21:34:42 +08:00
|
|
|
spin_lock_irqsave(&sta->tid_rx_lock, flags);
|
|
|
|
|
|
|
|
r = sta->tid_rx[tid];
|
2014-02-27 22:20:44 +08:00
|
|
|
if (!r) {
|
2014-09-10 21:34:42 +08:00
|
|
|
spin_unlock_irqrestore(&sta->tid_rx_lock, flags);
|
2014-02-27 22:20:44 +08:00
|
|
|
wil_netif_rx_any(skb, ndev);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
hseq = r->head_seq_num;
|
|
|
|
|
2014-04-08 16:36:18 +08:00
|
|
|
/** Due to the race between WMI events, where BACK establishment
|
|
|
|
* reported, and data Rx, few packets may be pass up before reorder
|
|
|
|
* buffer get allocated. Catch up by pretending SSN is what we
|
|
|
|
* see in the 1-st Rx packet
|
|
|
|
*/
|
|
|
|
if (r->first_time) {
|
|
|
|
r->first_time = false;
|
|
|
|
if (seq != r->head_seq_num) {
|
|
|
|
wil_err(wil, "Error: 1-st frame with wrong sequence"
|
|
|
|
" %d, should be %d. Fixing...\n", seq,
|
|
|
|
r->head_seq_num);
|
|
|
|
r->head_seq_num = seq;
|
|
|
|
r->ssn = seq;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-27 22:20:44 +08:00
|
|
|
/* frame with out of date sequence number */
|
|
|
|
if (seq_less(seq, r->head_seq_num)) {
|
2014-06-17 00:37:07 +08:00
|
|
|
r->ssn_last_drop = seq;
|
2014-02-27 22:20:44 +08:00
|
|
|
dev_kfree_skb(skb);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If frame the sequence number exceeds our buffering window
|
|
|
|
* size release some previous frames to make room for this one.
|
|
|
|
*/
|
|
|
|
if (!seq_less(seq, r->head_seq_num + r->buf_size)) {
|
|
|
|
hseq = seq_inc(seq_sub(seq, r->buf_size));
|
|
|
|
/* release stored frames up to new head to stack */
|
|
|
|
wil_release_reorder_frames(wil, r, hseq);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Now the new frame is always in the range of the reordering buffer */
|
|
|
|
|
|
|
|
index = reorder_index(r, seq);
|
|
|
|
|
|
|
|
/* check if we already stored this frame */
|
|
|
|
if (r->reorder_buf[index]) {
|
|
|
|
dev_kfree_skb(skb);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the current MPDU is in the right order and nothing else
|
|
|
|
* is stored we can process it directly, no need to buffer it.
|
|
|
|
* If it is first but there's something stored, we may be able
|
|
|
|
* to release frames after this one.
|
|
|
|
*/
|
|
|
|
if (seq == r->head_seq_num && r->stored_mpdu_num == 0) {
|
|
|
|
r->head_seq_num = seq_inc(r->head_seq_num);
|
|
|
|
wil_netif_rx_any(skb, ndev);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* put the frame in the reordering buffer */
|
|
|
|
r->reorder_buf[index] = skb;
|
|
|
|
r->reorder_time[index] = jiffies;
|
|
|
|
r->stored_mpdu_num++;
|
|
|
|
wil_reorder_release(wil, r);
|
|
|
|
|
|
|
|
out:
|
2014-09-10 21:34:42 +08:00
|
|
|
spin_unlock_irqrestore(&sta->tid_rx_lock, flags);
|
2014-02-27 22:20:44 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
struct wil_tid_ampdu_rx *wil_tid_ampdu_rx_alloc(struct wil6210_priv *wil,
|
|
|
|
int size, u16 ssn)
|
|
|
|
{
|
|
|
|
struct wil_tid_ampdu_rx *r = kzalloc(sizeof(*r), GFP_KERNEL);
|
2014-09-10 21:34:34 +08:00
|
|
|
|
2014-02-27 22:20:44 +08:00
|
|
|
if (!r)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
r->reorder_buf =
|
|
|
|
kcalloc(size, sizeof(struct sk_buff *), GFP_KERNEL);
|
|
|
|
r->reorder_time =
|
|
|
|
kcalloc(size, sizeof(unsigned long), GFP_KERNEL);
|
|
|
|
if (!r->reorder_buf || !r->reorder_time) {
|
|
|
|
kfree(r->reorder_buf);
|
|
|
|
kfree(r->reorder_time);
|
|
|
|
kfree(r);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
r->ssn = ssn;
|
|
|
|
r->head_seq_num = ssn;
|
|
|
|
r->buf_size = size;
|
|
|
|
r->stored_mpdu_num = 0;
|
2014-04-08 16:36:18 +08:00
|
|
|
r->first_time = true;
|
2014-02-27 22:20:44 +08:00
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
void wil_tid_ampdu_rx_free(struct wil6210_priv *wil,
|
|
|
|
struct wil_tid_ampdu_rx *r)
|
|
|
|
{
|
|
|
|
if (!r)
|
|
|
|
return;
|
|
|
|
wil_release_reorder_frames(wil, r, r->head_seq_num + r->buf_size);
|
|
|
|
kfree(r->reorder_buf);
|
|
|
|
kfree(r->reorder_time);
|
|
|
|
kfree(r);
|
|
|
|
}
|
wil6210: ADDBA/DELBA flows
Introduce BACK establishment procedures; decision logic is not implemented
yet; debugfs entry 'addba' used to manually trigger addba/delba for ringid 0.
debugfs usage:
to establish BACK with agg_wsize 16:
echo 16 > /sys/kernel/debug/ieee80211/phy0/wil6210/addba
to delete BACK:
echo 0 > /sys/kernel/debug/ieee80211/phy0/wil6210/addba
to change agg_wsize, one need to delete BACK and establish it anew
ADDBA flow for:
- originator
Tx side (initiator) sends WMI_VRING_BA_EN_CMDID providing
agg_wsize and timeout parameters.
Eventually, it gets event confirming BACK agreement - WMI_BA_STATUS_EVENTID
with negotiated parameters. On this event, update Tx vring data
(struct vring_tx_data) and display BACK parameters on debugfs
- recipient
Rx side (recipient) firmware informs driver about ADDBA with
WMI_RCP_ADDBA_REQ_EVENTID, driver process it in service work
queue wq_service. It adjusts parameters and sends response
with WMI_RCP_ADDBA_RESP_CMDID, and final confirmation provided
by firmware with WMI_ADDBA_RESP_SENT_EVENTID. In case of success,
driver updates Rx BACK reorder buffer.
policy for BACK parameters:
- aggregation size (agg_wsize * MPDUsize)) to not exceed 64Kbytes
DELBA flow for:
- originator
driver decides to terminate BACK, it sends WMI_VRING_BA_DIS_CMDID
and updates struct vring_tx_data associated with vring; ignore
WMI_DELBA_EVENTID.
- recipient
firmware informs driver with WMI_DELBA_EVENTID,
driver deletes correspondent reorder buffer
ADDBA request processing requires sending WMI command, therefore
it is processed in work queue context. Same work queue used as for
connect, it get renamed to wq_service
Signed-off-by: Vladimir Kondratiev <qca_vkondrat@qca.qualcomm.com>
Signed-off-by: Kalle Valo <kvalo@codeaurora.org>
2014-12-23 15:47:03 +08:00
|
|
|
|
|
|
|
/* ADDBA processing */
|
|
|
|
static u16 wil_agg_size(struct wil6210_priv *wil, u16 req_agg_wsize)
|
|
|
|
{
|
|
|
|
u16 max_agg_size = min_t(u16, WIL_MAX_AGG_WSIZE, WIL_MAX_AMPDU_SIZE /
|
|
|
|
(mtu_max + WIL_MAX_MPDU_OVERHEAD));
|
|
|
|
|
|
|
|
if (!req_agg_wsize)
|
|
|
|
return max_agg_size;
|
|
|
|
|
|
|
|
return min(max_agg_size, req_agg_wsize);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Block Ack - Rx side (recipient */
|
|
|
|
int wil_addba_rx_request(struct wil6210_priv *wil, u8 cidxtid,
|
|
|
|
u8 dialog_token, __le16 ba_param_set,
|
|
|
|
__le16 ba_timeout, __le16 ba_seq_ctrl)
|
|
|
|
{
|
|
|
|
struct wil_back_rx *req = kzalloc(sizeof(*req), GFP_KERNEL);
|
|
|
|
|
|
|
|
if (!req)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
req->cidxtid = cidxtid;
|
|
|
|
req->dialog_token = dialog_token;
|
|
|
|
req->ba_param_set = le16_to_cpu(ba_param_set);
|
|
|
|
req->ba_timeout = le16_to_cpu(ba_timeout);
|
|
|
|
req->ba_seq_ctrl = le16_to_cpu(ba_seq_ctrl);
|
|
|
|
|
|
|
|
mutex_lock(&wil->back_rx_mutex);
|
|
|
|
list_add_tail(&req->list, &wil->back_rx_pending);
|
|
|
|
mutex_unlock(&wil->back_rx_mutex);
|
|
|
|
|
|
|
|
queue_work(wil->wq_service, &wil->back_rx_worker);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void wil_back_rx_handle(struct wil6210_priv *wil,
|
|
|
|
struct wil_back_rx *req)
|
|
|
|
{
|
|
|
|
struct wil_sta_info *sta;
|
|
|
|
u8 cid, tid;
|
|
|
|
u16 agg_wsize = 0;
|
|
|
|
/* bit 0: A-MSDU supported
|
|
|
|
* bit 1: policy (should be 0 for us)
|
|
|
|
* bits 2..5: TID
|
|
|
|
* bits 6..15: buffer size
|
|
|
|
*/
|
|
|
|
u16 req_agg_wsize = WIL_GET_BITS(req->ba_param_set, 6, 15);
|
|
|
|
bool agg_amsdu = !!(req->ba_param_set & BIT(0));
|
|
|
|
int ba_policy = req->ba_param_set & BIT(1);
|
|
|
|
u16 agg_timeout = req->ba_timeout;
|
|
|
|
u16 status = WLAN_STATUS_SUCCESS;
|
|
|
|
unsigned long flags;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
parse_cidxtid(req->cidxtid, &cid, &tid);
|
|
|
|
|
|
|
|
/* sanity checks */
|
|
|
|
if (cid >= WIL6210_MAX_CID) {
|
|
|
|
wil_err(wil, "BACK: invalid CID %d\n", cid);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
sta = &wil->sta[cid];
|
|
|
|
if (sta->status != wil_sta_connected) {
|
|
|
|
wil_err(wil, "BACK: CID %d not connected\n", cid);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
wil_dbg_wmi(wil,
|
|
|
|
"ADDBA request for CID %d %pM TID %d size %d timeout %d AMSDU%s policy %d token %d\n",
|
|
|
|
cid, sta->addr, tid, req_agg_wsize, req->ba_timeout,
|
|
|
|
agg_amsdu ? "+" : "-", !!ba_policy, req->dialog_token);
|
|
|
|
|
|
|
|
/* apply policies */
|
|
|
|
if (ba_policy) {
|
|
|
|
wil_err(wil, "BACK requested unsupported ba_policy == 1\n");
|
|
|
|
status = WLAN_STATUS_INVALID_QOS_PARAM;
|
|
|
|
}
|
|
|
|
if (status == WLAN_STATUS_SUCCESS)
|
|
|
|
agg_wsize = wil_agg_size(wil, req_agg_wsize);
|
|
|
|
|
|
|
|
rc = wmi_addba_rx_resp(wil, cid, tid, req->dialog_token, status,
|
|
|
|
agg_amsdu, agg_wsize, agg_timeout);
|
|
|
|
if (rc || (status != WLAN_STATUS_SUCCESS))
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* apply */
|
|
|
|
spin_lock_irqsave(&sta->tid_rx_lock, flags);
|
|
|
|
|
|
|
|
wil_tid_ampdu_rx_free(wil, sta->tid_rx[tid]);
|
|
|
|
sta->tid_rx[tid] = wil_tid_ampdu_rx_alloc(wil, agg_wsize,
|
|
|
|
req->ba_seq_ctrl >> 4);
|
|
|
|
|
|
|
|
spin_unlock_irqrestore(&sta->tid_rx_lock, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
void wil_back_rx_flush(struct wil6210_priv *wil)
|
|
|
|
{
|
|
|
|
struct wil_back_rx *evt, *t;
|
|
|
|
|
|
|
|
wil_dbg_misc(wil, "%s()\n", __func__);
|
|
|
|
|
|
|
|
mutex_lock(&wil->back_rx_mutex);
|
|
|
|
|
|
|
|
list_for_each_entry_safe(evt, t, &wil->back_rx_pending, list) {
|
|
|
|
list_del(&evt->list);
|
|
|
|
kfree(evt);
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex_unlock(&wil->back_rx_mutex);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Retrieve next ADDBA request from the pending list */
|
|
|
|
static struct list_head *next_back_rx(struct wil6210_priv *wil)
|
|
|
|
{
|
|
|
|
struct list_head *ret = NULL;
|
|
|
|
|
|
|
|
mutex_lock(&wil->back_rx_mutex);
|
|
|
|
|
|
|
|
if (!list_empty(&wil->back_rx_pending)) {
|
|
|
|
ret = wil->back_rx_pending.next;
|
|
|
|
list_del(ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex_unlock(&wil->back_rx_mutex);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void wil_back_rx_worker(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct wil6210_priv *wil = container_of(work, struct wil6210_priv,
|
|
|
|
back_rx_worker);
|
|
|
|
struct wil_back_rx *evt;
|
|
|
|
struct list_head *lh;
|
|
|
|
|
|
|
|
while ((lh = next_back_rx(wil)) != NULL) {
|
|
|
|
evt = list_entry(lh, struct wil_back_rx, list);
|
|
|
|
|
|
|
|
wil_back_rx_handle(wil, evt);
|
|
|
|
kfree(evt);
|
|
|
|
}
|
|
|
|
}
|
2014-12-23 15:47:04 +08:00
|
|
|
|
|
|
|
/* BACK - Tx (originator) side */
|
|
|
|
static void wil_back_tx_handle(struct wil6210_priv *wil,
|
|
|
|
struct wil_back_tx *req)
|
|
|
|
{
|
|
|
|
struct vring_tx_data *txdata = &wil->vring_tx_data[req->ringid];
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (txdata->addba_in_progress) {
|
|
|
|
wil_dbg_misc(wil, "ADDBA for vring[%d] already in progress\n",
|
|
|
|
req->ringid);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (txdata->agg_wsize) {
|
|
|
|
wil_dbg_misc(wil,
|
|
|
|
"ADDBA for vring[%d] already established wsize %d\n",
|
|
|
|
req->ringid, txdata->agg_wsize);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
txdata->addba_in_progress = true;
|
|
|
|
rc = wmi_addba(wil, req->ringid, req->agg_wsize, req->agg_timeout);
|
|
|
|
if (rc)
|
|
|
|
txdata->addba_in_progress = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct list_head *next_back_tx(struct wil6210_priv *wil)
|
|
|
|
{
|
|
|
|
struct list_head *ret = NULL;
|
|
|
|
|
|
|
|
mutex_lock(&wil->back_tx_mutex);
|
|
|
|
|
|
|
|
if (!list_empty(&wil->back_tx_pending)) {
|
|
|
|
ret = wil->back_tx_pending.next;
|
|
|
|
list_del(ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex_unlock(&wil->back_tx_mutex);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void wil_back_tx_worker(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct wil6210_priv *wil = container_of(work, struct wil6210_priv,
|
|
|
|
back_tx_worker);
|
|
|
|
struct wil_back_tx *evt;
|
|
|
|
struct list_head *lh;
|
|
|
|
|
|
|
|
while ((lh = next_back_tx(wil)) != NULL) {
|
|
|
|
evt = list_entry(lh, struct wil_back_tx, list);
|
|
|
|
|
|
|
|
wil_back_tx_handle(wil, evt);
|
|
|
|
kfree(evt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void wil_back_tx_flush(struct wil6210_priv *wil)
|
|
|
|
{
|
|
|
|
struct wil_back_tx *evt, *t;
|
|
|
|
|
|
|
|
wil_dbg_misc(wil, "%s()\n", __func__);
|
|
|
|
|
|
|
|
mutex_lock(&wil->back_tx_mutex);
|
|
|
|
|
|
|
|
list_for_each_entry_safe(evt, t, &wil->back_tx_pending, list) {
|
|
|
|
list_del(&evt->list);
|
|
|
|
kfree(evt);
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex_unlock(&wil->back_tx_mutex);
|
|
|
|
}
|
|
|
|
|
2014-12-23 15:47:05 +08:00
|
|
|
int wil_addba_tx_request(struct wil6210_priv *wil, u8 ringid, u16 wsize)
|
2014-12-23 15:47:04 +08:00
|
|
|
{
|
|
|
|
struct wil_back_tx *req = kzalloc(sizeof(*req), GFP_KERNEL);
|
|
|
|
|
|
|
|
if (!req)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
req->ringid = ringid;
|
2014-12-23 15:47:05 +08:00
|
|
|
req->agg_wsize = wil_agg_size(wil, wsize);
|
2014-12-23 15:47:04 +08:00
|
|
|
req->agg_timeout = 0;
|
|
|
|
|
|
|
|
mutex_lock(&wil->back_tx_mutex);
|
|
|
|
list_add_tail(&req->list, &wil->back_tx_pending);
|
|
|
|
mutex_unlock(&wil->back_tx_mutex);
|
|
|
|
|
|
|
|
queue_work(wil->wq_service, &wil->back_tx_worker);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|