/*
|
* Copyright (c) 2015 South Silicon Valley Microelectronics Inc.
|
* Copyright (c) 2015 iComm Corporation
|
*
|
* This program is free software: you can redistribute it and/or modify
|
* it under the terms of the GNU General Public License as published by
|
* the Free Software Foundation, either version 3 of the License, or
|
* (at your option) any later version.
|
* 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.
|
* You should have received a copy of the GNU General Public License
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
*/
|
|
#include <linux/version.h>
|
#include <ssv6200.h>
|
#include "dev.h"
|
#include "ap.h"
|
#include "sec.h"
|
#include "ssv_rc_common.h"
|
#include "ssv_ht_rc.h"
|
extern struct ieee80211_ops ssv6200_ops;
|
#define BA_WAIT_TIMEOUT (800)
|
#define AMPDU_BA_FRAME_LEN (68)
|
#define ampdu_skb_hdr(skb) ((struct ieee80211_hdr*)((u8*)((skb)->data)+AMPDU_DELIMITER_LEN))
|
#define ampdu_skb_ssn(skb) ((ampdu_skb_hdr(skb)->seq_ctrl)>>SSV_SEQ_NUM_SHIFT)
|
#define ampdu_hdr_ssn(hdr) ((hdr)->seq_ctrl>>SSV_SEQ_NUM_SHIFT)
|
#if 0
|
#define prn_aggr_dbg(fmt,...) \
|
do { \
|
printk(KERN_DEBUG fmt, ##__VA_ARGS__); \
|
} while (0)
|
#else
|
#undef prn_aggr_dbg
|
#define prn_aggr_dbg(fmt,...)
|
#endif
|
#if 0
|
#define prn_aggr_err(fmt,...) \
|
do { \
|
printk(KERN_ERR fmt, ##__VA_ARGS__);\
|
} while (0)
|
#else
|
static void void_func(const char *fmt, ...)
|
{
|
}
|
#define prn_aggr_err(fmt,...) \
|
do { \
|
void_func(KERN_ERR fmt, ##__VA_ARGS__);\
|
} while (0)
|
#endif
|
#define get_tid_aggr_len(agg_len,tid_data) \
|
({ \
|
u32 agg_max_num = (tid_data)->agg_num_max; \
|
u32 to_agg_len = (agg_len); \
|
(agg_len >= agg_max_num) ? agg_max_num : to_agg_len; \
|
})
|
#define INDEX_PKT_BY_SSN(tid,ssn) \
|
((tid)->aggr_pkts[(ssn) % SSV_AMPDU_BA_WINDOW_SIZE])
|
#define NEXT_PKT_SN(sn) \
|
({ (sn + 1) % SSV_AMPDU_MAX_SSN; })
|
#define INC_PKT_SN(sn) \
|
({ \
|
sn = NEXT_PKT_SN(sn); \
|
sn; \
|
})
|
#ifdef CONFIG_SSV6XXX_DEBUGFS
|
static ssize_t ampdu_tx_mib_dump(struct ssv_sta_priv_data *ssv_sta_priv,
|
char *mib_str, ssize_t length);
|
static int _dump_ba_skb(char *buf, int buf_size, struct sk_buff *ba_skb);
|
#endif
|
static struct sk_buff* _aggr_retry_mpdu (struct ssv_softc *sc,
|
struct AMPDU_TID_st *cur_AMPDU_TID,
|
struct sk_buff_head *retry_queue,
|
u32 max_aggr_len);
|
static int _dump_BA_notification (char *buf,
|
struct ampdu_ba_notify_data *ba_notification);
|
static struct sk_buff *_alloc_ampdu_skb (struct ssv_softc *sc,
|
struct AMPDU_TID_st *ampdu_tid,
|
u32 len);
|
static bool _sync_ampdu_pkt_arr (struct AMPDU_TID_st *ampdu_tid,
|
struct sk_buff *ampdu_skb, bool retry);
|
static void _put_mpdu_to_ampdu (struct sk_buff *ampdu,
|
struct sk_buff *mpdu);
|
static void _add_ampdu_txinfo (struct ssv_softc *sc, struct sk_buff *ampdu_skb);
|
static u32 _flush_early_ampdu_q (struct ssv_softc *sc,
|
struct AMPDU_TID_st *ampdu_tid);
|
static bool _is_skb_q_empty (struct ssv_softc *sc, struct sk_buff *skb);
|
static void _aggr_ampdu_tx_q (struct ieee80211_hw *hw,
|
struct AMPDU_TID_st *ampdu_tid);
|
static void _queue_early_ampdu (struct ssv_softc *sc,
|
struct AMPDU_TID_st *ampdu_tid,
|
struct sk_buff *ampdu_skb);
|
static int _mark_skb_retry (struct SKB_info_st *skb_info, struct sk_buff *skb);
|
#ifdef CONFIG_DEBUG_SKB_TIMESTAMP
|
unsigned int cal_duration_of_ampdu(struct sk_buff *ampdu_skb, int stage)
|
{
|
unsigned int timeout;
|
SKB_info *mpdu_skb_info;
|
u16 ssn = 0;
|
struct sk_buff *mpdu = NULL;
|
struct ampdu_hdr_st *ampdu_hdr = NULL;
|
ktime_t current_ktime;
|
ampdu_hdr = (struct ampdu_hdr_st *) ampdu_skb->head;
|
ssn = ampdu_hdr->ssn[0];
|
mpdu = INDEX_PKT_BY_SSN(ampdu_hdr->ampdu_tid, ssn);
|
if (mpdu == NULL)
|
return 0;
|
mpdu_skb_info = (SKB_info *) (mpdu->head);
|
current_ktime = ktime_get();
|
timeout = (unsigned int)ktime_to_ms(ktime_sub(current_ktime, mpdu_skb_info->timestamp));
|
if (timeout > SKB_DURATION_TIMEOUT_MS) {
|
if (stage == SKB_DURATION_STAGE_TO_SDIO)
|
printk("*a_to_sdio: %ums\n", timeout);
|
else if (stage == SKB_DURATION_STAGE_TX_ENQ)
|
printk("*a_to_txenqueue: %ums\n", timeout);
|
else
|
printk("*a_in_hwq: %ums\n", timeout);
|
}
|
return timeout;
|
}
|
#endif
|
static u8 _cal_ampdu_delm_half_crc (u8 value)
|
{
|
u32 c32 = value, v32 = value;
|
c32 ^= (v32 >> 1) | (v32 << 7);
|
c32 ^= (v32 >> 2);
|
if (v32 & 2)
|
c32 ^= (0xC0);
|
c32 ^= ((v32 << 4) & 0x30);
|
return (u8) c32;
|
}
|
static u8 _cal_ampdu_delm_crc (u8 *pointer)
|
{
|
u8 crc = 0xCF;
|
crc ^= _cal_ampdu_delm_half_crc(*pointer++);
|
crc = _cal_ampdu_delm_half_crc(crc) ^ _cal_ampdu_delm_half_crc(*pointer);
|
return ~crc;
|
}
|
static bool ssv6200_ampdu_add_delimiter_and_crc32 (struct sk_buff *mpdu)
|
{
|
p_AMPDU_DELIMITER delimiter_p;
|
struct ieee80211_hdr *mpdu_hdr;
|
int ret;
|
u32 orig_mpdu_len = mpdu->len;
|
u32 pad = (4 - (orig_mpdu_len % 4)) % 4;
|
mpdu_hdr = (struct ieee80211_hdr*) (mpdu->data);
|
mpdu_hdr->duration_id = AMPDU_TX_NAV_MCS_567;
|
ret = skb_padto(mpdu, mpdu->len + (AMPDU_FCS_LEN + pad));
|
if (ret)
|
{
|
printk(KERN_ERR "Failed to extand skb for aggregation.");
|
return false;
|
}
|
skb_put(mpdu, AMPDU_FCS_LEN + pad);
|
skb_push(mpdu, AMPDU_DELIMITER_LEN);
|
delimiter_p = (p_AMPDU_DELIMITER) mpdu->data;
|
delimiter_p->reserved = 0;
|
delimiter_p->length = orig_mpdu_len + AMPDU_FCS_LEN;
|
delimiter_p->signature = AMPDU_SIGNATURE;
|
delimiter_p->crc = _cal_ampdu_delm_crc((u8*) (delimiter_p));
|
return true;
|
}
|
static void ssv6200_ampdu_hw_init (struct ieee80211_hw *hw)
|
{
|
struct ssv_softc *sc = hw->priv;
|
u32 temp32;
|
SMAC_REG_READ(sc->sh, ADR_MTX_MISC_EN, &temp32);
|
temp32 |= (0x1 << MTX_AMPDU_CRC_AUTO_SFT);
|
SMAC_REG_WRITE(sc->sh, ADR_MTX_MISC_EN, temp32);
|
SMAC_REG_READ(sc->sh, ADR_MTX_MISC_EN, &temp32);
|
}
|
bool _sync_ampdu_pkt_arr (struct AMPDU_TID_st *ampdu_tid, struct sk_buff *ampdu,
|
bool retry)
|
{
|
struct sk_buff **pp_aggr_pkt;
|
struct sk_buff *p_aggr_pkt;
|
unsigned long flags;
|
struct ampdu_hdr_st *ampdu_hdr = (struct ampdu_hdr_st *) ampdu->head;
|
struct sk_buff *mpdu;
|
u32 first_ssn = SSV_ILLEGAL_SN;
|
u32 old_aggr_pkt_num;
|
u32 old_baw_head;
|
u32 sync_num = skb_queue_len(&du_hdr->mpdu_q);
|
bool ret = true;
|
spin_lock_irqsave(&du_tid->pkt_array_lock, flags);
|
old_baw_head = ampdu_tid->ssv_baw_head;
|
old_aggr_pkt_num = ampdu_tid->aggr_pkt_num;
|
ampdu_tid->mib.ampdu_mib_ampdu_counter += 1;
|
ampdu_tid->mib.ampdu_mib_dist[sync_num] += 1;
|
do
|
{
|
if (!retry)
|
{
|
ampdu_tid->mib.ampdu_mib_mpdu_counter += sync_num;
|
mpdu = skb_peek_tail(&du_hdr->mpdu_q);
|
if (mpdu == NULL)
|
{
|
ret = false;
|
break;
|
}
|
else
|
{
|
u32 ssn = ampdu_skb_ssn(mpdu);
|
p_aggr_pkt = INDEX_PKT_BY_SSN(ampdu_tid, ssn);
|
if (p_aggr_pkt != NULL)
|
{
|
char msg[256];
|
u32 sn = ampdu_skb_ssn(mpdu);
|
skb_queue_walk(&du_hdr->mpdu_q, mpdu)
|
{
|
sn = ampdu_skb_ssn(mpdu);
|
sprintf(msg, " %d", sn);
|
}
|
prn_aggr_err("ES %d -> %d (%s)\n",
|
ssn, ampdu_skb_ssn(p_aggr_pkt), msg);
|
ret = false;
|
break;
|
}
|
}
|
}
|
else
|
ampdu_tid->mib.ampdu_mib_aggr_retry_counter += 1;
|
skb_queue_walk(&du_hdr->mpdu_q, mpdu)
|
{
|
u32 ssn = ampdu_skb_ssn(mpdu);
|
SKB_info *mpdu_skb_info = (SKB_info *) (mpdu->head);
|
if (first_ssn == SSV_ILLEGAL_SN)
|
first_ssn = ssn;
|
pp_aggr_pkt = &INDEX_PKT_BY_SSN(ampdu_tid, ssn);
|
p_aggr_pkt = *pp_aggr_pkt;
|
*pp_aggr_pkt = mpdu;
|
if (!retry)
|
ampdu_tid->aggr_pkt_num++;
|
mpdu_skb_info->ampdu_tx_status = AMPDU_ST_AGGREGATED;
|
if (ampdu_tid->ssv_baw_head == SSV_ILLEGAL_SN)
|
{
|
ampdu_tid->ssv_baw_head = ssn;
|
}
|
if ((p_aggr_pkt != NULL) && (mpdu != p_aggr_pkt))
|
prn_aggr_err(
|
"%d -> %d (H%d, N%d, Q%d)\n",
|
ssn, ampdu_skb_ssn(p_aggr_pkt), old_baw_head, old_aggr_pkt_num, sync_num);
|
}
|
} while (0);
|
spin_unlock_irqrestore(&du_tid->pkt_array_lock, flags);
|
#if 1
|
{
|
u32 page_count = (ampdu->len + SSV6200_ALLOC_RSVD);
|
if (page_count & HW_MMU_PAGE_MASK)
|
page_count = (page_count >> HW_MMU_PAGE_SHIFT) + 1;
|
else page_count = page_count >> HW_MMU_PAGE_SHIFT;
|
if (page_count > (SSV6200_PAGE_TX_THRESHOLD / 2))
|
printk(KERN_ERR "AMPDU requires pages %d(%d-%d-%d) exceeds resource limit %d.\n",
|
page_count, ampdu->len, ampdu_hdr->max_size, ampdu_hdr->size,
|
(SSV6200_PAGE_TX_THRESHOLD / 2));
|
}
|
#endif
|
return ret;
|
}
|
struct sk_buff* _aggr_retry_mpdu (struct ssv_softc *sc,
|
struct AMPDU_TID_st *ampdu_tid,
|
struct sk_buff_head *retry_queue,
|
u32 max_aggr_len)
|
{
|
struct sk_buff *retry_mpdu;
|
struct sk_buff *new_ampdu_skb;
|
u32 num_retry_mpdu;
|
u32 temp_i;
|
u32 total_skb_size;
|
unsigned long flags;
|
u16 head_ssn = ampdu_tid->ssv_baw_head;
|
struct ampdu_hdr_st *ampdu_hdr;
|
#if 0
|
if (cur_AMPDU_TID->ssv_baw_head == SSV_ILLEGAL_SN)
|
{
|
struct sk_buff *skb = skb_peek(ampdu_skb_retry_queue);
|
prn_aggr_err("Rr %d\n", (skb == NULL) ? (-1) : ampdu_skb_ssn(skb));
|
return NULL;
|
}
|
#else
|
BUG_ON(head_ssn == SSV_ILLEGAL_SN);
|
#endif
|
num_retry_mpdu = skb_queue_len(retry_queue);
|
if (num_retry_mpdu == 0)
|
return NULL;
|
new_ampdu_skb = _alloc_ampdu_skb(sc, ampdu_tid, max_aggr_len);
|
if (new_ampdu_skb == 0)
|
return NULL;
|
ampdu_hdr = (struct ampdu_hdr_st *)new_ampdu_skb->head;
|
total_skb_size = 0;
|
spin_lock_irqsave(&retry_queue->lock, flags);
|
for (temp_i = 0; temp_i < ampdu_tid->agg_num_max; temp_i++)
|
{
|
struct ieee80211_hdr *mpdu_hdr;
|
u16 mpdu_sn;
|
u16 diff;
|
u32 new_total_skb_size;
|
retry_mpdu = skb_peek(retry_queue);
|
if (retry_mpdu == NULL)
|
{
|
break;
|
}
|
mpdu_hdr = ampdu_skb_hdr(retry_mpdu);
|
mpdu_sn = ampdu_hdr_ssn(mpdu_hdr);
|
diff = SSV_AMPDU_SN_a_minus_b(head_ssn, mpdu_sn);
|
if ((head_ssn != SSV_ILLEGAL_SN)
|
&& (diff > 0)
|
&& (diff <= ampdu_tid->ssv_baw_size))
|
{
|
struct SKB_info_st *skb_info;
|
prn_aggr_err("Z. release skb (s %d, h %d, d %d)\n",
|
mpdu_sn, head_ssn, diff);
|
skb_info = (struct SKB_info_st *) (retry_mpdu->head);
|
skb_info->ampdu_tx_status = AMPDU_ST_DROPPED;
|
ampdu_tid->mib.ampdu_mib_discard_counter++;
|
continue;
|
}
|
new_total_skb_size = total_skb_size + retry_mpdu->len;
|
if (new_total_skb_size > ampdu_hdr->max_size)
|
break;
|
total_skb_size = new_total_skb_size;
|
#if 0
|
if (ampdu_skb_retry_queue != NULL)
|
prn_aggr_err("R %d\n", SerialNumber);
|
#endif
|
retry_mpdu = __skb_dequeue(retry_queue);
|
_put_mpdu_to_ampdu(new_ampdu_skb, retry_mpdu);
|
ampdu_tid->mib.ampdu_mib_retry_counter++;
|
}
|
ampdu_tid->mib.ampdu_mib_aggr_retry_counter += 1;
|
ampdu_tid->mib.ampdu_mib_dist[temp_i] += 1;
|
spin_unlock_irqrestore(&retry_queue->lock, flags);
|
#if 0
|
{
|
struct ampdu_hdr_st *ampdu_hdr = (struct ampdu_hdr_st *)new_ampdu_skb->head;
|
retry_mpdu = skb_peek(&du_hdr->mpdu_q);
|
dev_alert(sc->dev, "rA %d - %d\n", ampdu_skb_ssn(retry_mpdu),
|
skb_queue_len(&du_hdr->mpdu_q));
|
}
|
#endif
|
if (ampdu_hdr->mpdu_num == 0) {
|
dev_kfree_skb_any(new_ampdu_skb);
|
return NULL;
|
}
|
return new_ampdu_skb;
|
}
|
static void _add_ampdu_txinfo (struct ssv_softc *sc, struct sk_buff *ampdu_skb)
|
{
|
struct ssv6200_tx_desc *tx_desc;
|
ssv6xxx_add_txinfo(sc, ampdu_skb);
|
tx_desc = (struct ssv6200_tx_desc *) ampdu_skb->data;
|
tx_desc->tx_report = 1;
|
#if 0
|
tx_desc->len = ampdu_skb->len;
|
tx_desc->c_type = M2_TXREQ;
|
tx_desc->f80211 = 1;
|
tx_desc->ht = 1;
|
tx_desc->qos = 1;
|
tx_desc->use_4addr = 0;
|
tx_desc->security = 0;
|
tx_desc->more_data = 0;
|
tx_desc->stype_b5b4 = 0;
|
tx_desc->extra_info = 0;
|
tx_desc->hdr_offset = sc->sh->cfg.txpb_offset;;
|
tx_desc->frag = 0;
|
tx_desc->unicast = 1;
|
tx_desc->hdr_len = 0;
|
tx_desc->RSVD_4 = 0;
|
tx_desc->tx_burst = 0;
|
tx_desc->ack_policy = 1;
|
tx_desc->aggregation = 1;
|
tx_desc->RSVD_1 = 0;
|
tx_desc->do_rts_cts = 0;
|
tx_desc->reason = 0;
|
tx_desc->payload_offset = tx_desc->hdr_offset + tx_desc->hdr_len;
|
tx_desc->RSVD_4 = 0;
|
tx_desc->RSVD_2 = 0;
|
tx_desc->fCmdIdx = 0;
|
tx_desc->wsid = 0;
|
tx_desc->txq_idx = 0;
|
tx_desc->TxF_ID = 0;
|
tx_desc->rts_cts_nav = 0;
|
tx_desc->frame_consume_time = 0;
|
tx_desc->crate_idx=0;
|
tx_desc->drate_idx = 22;
|
tx_desc->dl_length = 56;
|
tx_desc->RSVD_3 = 0;
|
#endif
|
#if 0
|
if(ampdu_skb != 0)
|
{
|
u32 temp_i;
|
u8* temp8_p = (u8*)ampdu_skb->data;
|
ampdu_db_log("print txinfo.\n");
|
for(temp_i=0; temp_i < 24; temp_i++)
|
{
|
ampdu_db_log_simple("%02x",*(u8*)(temp8_p + temp_i));
|
if(((temp_i+1) % 4) == 0)
|
{
|
ampdu_db_log_simple("\n");
|
}
|
}
|
ampdu_db_log_simple("\n");
|
}
|
#endif
|
#if 0
|
if(ampdu_skb != 0)
|
{
|
u32 temp_i;
|
u8* temp8_p = (u8*)ampdu_skb->data;
|
ampdu_db_log("print all skb.\n");
|
for(temp_i=0; temp_i < ampdu_skb->len; temp_i++)
|
{
|
ampdu_db_log_simple("%02x",*(u8*)(temp8_p + temp_i));
|
if(((temp_i+1) % 4) == 0)
|
{
|
ampdu_db_log_simple("\n");
|
}
|
}
|
ampdu_db_log_simple("\n");
|
}
|
#endif
|
}
|
void _send_hci_skb (struct ssv_softc *sc, struct sk_buff *skb, u32 tx_flag)
|
{
|
struct ssv6200_tx_desc *tx_desc = (struct ssv6200_tx_desc *)skb->data;
|
int ret = AMPDU_HCI_SEND(sc->sh, skb, tx_desc->txq_idx, tx_flag);
|
#if 1
|
if ((tx_desc->txq_idx > 3) && (ret <= 0))
|
{
|
prn_aggr_err("BUG!! %d %d\n", tx_desc->txq_idx, ret);
|
}
|
#else
|
BUG_ON(tx_desc->txq_idx>3 && ret<=0);
|
#endif
|
}
|
static void ssv6200_ampdu_add_txinfo_and_send_HCI (struct ssv_softc *sc,
|
struct sk_buff *ampdu_skb,
|
u32 tx_flag)
|
{
|
_add_ampdu_txinfo(sc, ampdu_skb);
|
_send_hci_skb(sc, ampdu_skb, tx_flag);
|
}
|
static void ssv6200_ampdu_send_retry (
|
struct ieee80211_hw *hw, AMPDU_TID *cur_ampdu_tid,
|
struct sk_buff_head *ampdu_skb_retry_queue_p, bool send_aggr_tx)
|
{
|
struct ssv_softc *sc = hw->priv;
|
struct sk_buff *ampdu_retry_skb;
|
u32 ampdu_skb_retry_queue_len;
|
u32 max_agg_len;
|
u16 lowest_rate;
|
struct fw_rc_retry_params rates[SSV62XX_TX_MAX_RATES];
|
ampdu_skb_retry_queue_len = skb_queue_len(ampdu_skb_retry_queue_p);
|
if (ampdu_skb_retry_queue_len == 0)
|
return;
|
ampdu_retry_skb = skb_peek(ampdu_skb_retry_queue_p);
|
lowest_rate = ssv62xx_ht_rate_update(ampdu_retry_skb, sc, rates);
|
max_agg_len = ampdu_max_transmit_length[lowest_rate];
|
if (max_agg_len > 0)
|
{
|
u32 cur_ampdu_max_size = SSV_GET_MAX_AMPDU_SIZE(sc->sh);
|
if (max_agg_len >= cur_ampdu_max_size)
|
max_agg_len = cur_ampdu_max_size;
|
while (ampdu_skb_retry_queue_len > 0)
|
{
|
struct sk_buff *retry_mpdu = skb_peek(ampdu_skb_retry_queue_p);
|
SKB_info *mpdu_skb_info = (SKB_info *)(retry_mpdu->head);
|
mpdu_skb_info->lowest_rate = lowest_rate;
|
memcpy(mpdu_skb_info->rates, rates, sizeof(rates));
|
ampdu_retry_skb = _aggr_retry_mpdu(sc, cur_ampdu_tid, ampdu_skb_retry_queue_p,
|
max_agg_len);
|
if (ampdu_retry_skb != NULL)
|
{
|
_sync_ampdu_pkt_arr(cur_ampdu_tid, ampdu_retry_skb, true);
|
ssv6200_ampdu_add_txinfo_and_send_HCI(sc, ampdu_retry_skb,
|
AMPDU_HCI_SEND_HEAD_WITHOUT_FLOWCTRL);
|
}
|
else
|
{
|
prn_aggr_err("AMPDU retry failed.\n");
|
return;
|
}
|
ampdu_skb_retry_queue_len = skb_queue_len(ampdu_skb_retry_queue_p);
|
}
|
}
|
else
|
{
|
struct ieee80211_tx_rate rates[IEEE80211_TX_MAX_RATES];
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(ampdu_retry_skb);
|
memcpy(rates, info->control.rates, sizeof(info->control.rates));
|
while ((ampdu_retry_skb = __skb_dequeue_tail(ampdu_skb_retry_queue_p)) != NULL)
|
{
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(ampdu_retry_skb);
|
info->flags &= ~IEEE80211_TX_CTL_AMPDU;
|
memcpy(info->control.rates, rates, sizeof(info->control.rates));
|
ssv6xxx_update_txinfo(sc, ampdu_retry_skb);
|
_send_hci_skb(sc, ampdu_retry_skb, AMPDU_HCI_SEND_HEAD_WITHOUT_FLOWCTRL);
|
}
|
}
|
}
|
void ssv6200_ampdu_init (struct ieee80211_hw *hw)
|
{
|
struct ssv_softc *sc = hw->priv;
|
ssv6200_ampdu_hw_init(hw);
|
sc->tx.ampdu_tx_group_id = 0;
|
#ifdef USE_ENCRYPT_WORK
|
INIT_WORK(&sc->ampdu_tx_encry_work, encry_work);
|
INIT_WORK(&sc->sync_hwkey_work, sync_hw_key_work);
|
#endif
|
}
|
void ssv6200_ampdu_deinit (struct ieee80211_hw *hw)
|
{
|
}
|
void ssv6200_ampdu_release_skb (struct sk_buff *skb, struct ieee80211_hw *hw)
|
{
|
#if LINUX_VERSION_CODE >= 0x030400
|
ieee80211_free_txskb(hw, skb);
|
#else
|
dev_kfree_skb_any(skb);
|
#endif
|
}
|
#ifdef CONFIG_SSV6XXX_DEBUGFS
|
struct mib_dump_data {
|
char *prt_buff;
|
size_t buff_size;
|
size_t prt_len;
|
};
|
#define AMPDU_TX_MIB_SUMMARY_BUF_SIZE (4096)
|
static ssize_t ampdu_tx_mib_summary_read (struct file *file,
|
char __user *user_buf, size_t count,
|
loff_t *ppos)
|
{
|
struct ssv_sta_priv_data *ssv_sta_priv =
|
(struct ssv_sta_priv_data *) file->private_data;
|
char *summary_buf = kzalloc(AMPDU_TX_MIB_SUMMARY_BUF_SIZE, GFP_KERNEL);
|
ssize_t summary_size;
|
ssize_t ret;
|
if (!summary_buf)
|
return -ENOMEM;
|
summary_size = ampdu_tx_mib_dump(ssv_sta_priv, summary_buf,
|
AMPDU_TX_MIB_SUMMARY_BUF_SIZE);
|
ret = simple_read_from_buffer(user_buf, count, ppos, summary_buf,
|
summary_size);
|
kfree(summary_buf);
|
return ret;
|
}
|
static int ampdu_tx_mib_summary_open (struct inode *inode, struct file *file)
|
{
|
file->private_data = inode->i_private;
|
return 0;
|
}
|
static const struct file_operations mib_summary_fops = { .read =
|
ampdu_tx_mib_summary_read, .open = ampdu_tx_mib_summary_open, };
|
static ssize_t ampdu_tx_tid_window_read (struct file *file,
|
char __user *user_buf, size_t count,
|
loff_t *ppos)
|
{
|
struct AMPDU_TID_st *ampdu_tid = (struct AMPDU_TID_st *)file->private_data;
|
char *summary_buf = kzalloc(AMPDU_TX_MIB_SUMMARY_BUF_SIZE, GFP_KERNEL);
|
ssize_t ret;
|
char *prn_ptr = summary_buf;
|
int prt_size;
|
int buf_size = AMPDU_TX_MIB_SUMMARY_BUF_SIZE;
|
int i;
|
struct sk_buff *ba_skb, *tmp_ba_skb;
|
if (!summary_buf)
|
return -ENOMEM;
|
prt_size = snprintf(prn_ptr, buf_size, "\nWMM_TID %d:\n"
|
"\tWindow:",
|
ampdu_tid->tidno);
|
prn_ptr += prt_size;
|
buf_size -= prt_size;
|
for (i = 0; i < SSV_AMPDU_BA_WINDOW_SIZE; i++)
|
{
|
struct sk_buff *skb = ampdu_tid->aggr_pkts[i];
|
if ((i % 8) == 0)
|
{
|
prt_size = snprintf(prn_ptr, buf_size, "\n\t\t");
|
prn_ptr += prt_size;
|
buf_size -= prt_size;
|
}
|
if (skb == NULL)
|
prt_size = snprintf(prn_ptr, buf_size, " %s", "NULL ");
|
else
|
{
|
struct SKB_info_st *skb_info = (struct SKB_info_st *)(skb->head);
|
const char status_symbol[] = {'N',
|
'A',
|
'S',
|
'R',
|
'P',
|
'D'};
|
prt_size = snprintf(prn_ptr, buf_size, " %4d%c", ampdu_skb_ssn(skb),
|
( (skb_info->ampdu_tx_status <= AMPDU_ST_DONE)
|
? status_symbol[skb_info->ampdu_tx_status]
|
: 'X'));
|
}
|
prn_ptr += prt_size;
|
buf_size -= prt_size;
|
}
|
prt_size = snprintf(prn_ptr, buf_size, "\n\tEarly aggregated #: %d\n", ampdu_tid->early_aggr_skb_num);
|
prn_ptr += prt_size;
|
buf_size -= prt_size;
|
prt_size = snprintf(prn_ptr, buf_size, "\tBAW skb #: %d\n", ampdu_tid->aggr_pkt_num);
|
prn_ptr += prt_size;
|
buf_size -= prt_size;
|
prt_size = snprintf(prn_ptr, buf_size, "\tBAW head: %d\n", ampdu_tid->ssv_baw_head);
|
prn_ptr += prt_size;
|
buf_size -= prt_size;
|
prt_size = snprintf(prn_ptr, buf_size, "\tState: %d\n", ampdu_tid->state);
|
prn_ptr += prt_size;
|
buf_size -= prt_size;
|
prt_size = snprintf(prn_ptr, buf_size, "\tBA:\n");
|
prn_ptr += prt_size;
|
buf_size -= prt_size;
|
skb_queue_walk_safe(&du_tid->ba_q, ba_skb, tmp_ba_skb)
|
{
|
prt_size = _dump_ba_skb(prn_ptr, buf_size, ba_skb);
|
prn_ptr += prt_size;
|
buf_size -= prt_size;
|
}
|
buf_size = AMPDU_TX_MIB_SUMMARY_BUF_SIZE - buf_size;
|
ret = simple_read_from_buffer(user_buf, count, ppos, summary_buf,
|
buf_size);
|
kfree(summary_buf);
|
return ret;
|
}
|
static int ampdu_tx_tid_window_open (struct inode *inode, struct file *file)
|
{
|
file->private_data = inode->i_private;
|
return 0;
|
}
|
static const struct file_operations tid_window_fops = { .read =
|
ampdu_tx_tid_window_read, .open = ampdu_tx_tid_window_open, };
|
static int ampdu_tx_mib_reset_open (struct inode *inode, struct file *file)
|
{
|
file->private_data = inode->i_private;
|
return 0;
|
}
|
static ssize_t ampdu_tx_mib_reset_read (struct file *file,
|
char __user *user_buf, size_t count,
|
loff_t *ppos)
|
{
|
char *reset_buf = kzalloc(64, GFP_KERNEL);
|
ssize_t ret;
|
u32 reset_size;
|
if (!reset_buf)
|
return -ENOMEM;
|
reset_size = snprintf(reset_buf, 63, "%d", 0);
|
ret = simple_read_from_buffer(user_buf, count, ppos, reset_buf,
|
reset_size);
|
kfree(reset_buf);
|
return ret;
|
}
|
static ssize_t ampdu_tx_mib_reset_write (struct file *file,
|
const char __user *buffer,
|
size_t count,
|
loff_t *pos)
|
{
|
struct AMPDU_TID_st *ampdu_tid = (struct AMPDU_TID_st *)file->private_data;
|
memset(&du_tid->mib, 0, sizeof(struct AMPDU_MIB_st));
|
return count;
|
}
|
static const struct file_operations mib_reset_fops
|
= { .read = ampdu_tx_mib_reset_read,
|
.open = ampdu_tx_mib_reset_open,
|
.write = ampdu_tx_mib_reset_write};
|
static void ssv6200_ampdu_tx_init_debugfs (
|
struct ssv_softc *sc, struct ssv_sta_priv_data *ssv_sta_priv)
|
{
|
struct ssv_sta_info *sta_info = ssv_sta_priv->sta_info;
|
int i;
|
struct dentry *sta_debugfs_dir = sta_info->debugfs_dir;
|
dev_info(sc->dev, "Creating AMPDU TX debugfs.\n");
|
if (sta_debugfs_dir == NULL)
|
{
|
dev_err(sc->dev, "No STA debugfs.\n");
|
return;
|
}
|
debugfs_create_file("ampdu_tx_summary", 00444, sta_debugfs_dir,
|
ssv_sta_priv, &mib_summary_fops);
|
debugfs_create_u32("total_BA", 00644, sta_debugfs_dir,
|
&ssv_sta_priv->ampdu_mib_total_BA_counter);
|
for (i = 0; i < WMM_TID_NUM; i++)
|
{
|
char debugfs_name[20];
|
struct dentry *ampdu_tx_debugfs_dir;
|
int j;
|
struct AMPDU_TID_st *ampdu_tid = &ssv_sta_priv->ampdu_tid[i];
|
struct AMPDU_MIB_st *ampdu_mib = &du_tid->mib;
|
snprintf(debugfs_name, sizeof(debugfs_name), "ampdu_tx_%d", i);
|
ampdu_tx_debugfs_dir = debugfs_create_dir(debugfs_name,
|
sta_debugfs_dir);
|
if (ampdu_tx_debugfs_dir == NULL)
|
{
|
dev_err(sc->dev,
|
"Failed to create debugfs for AMPDU TX TID %d: %s\n", i,
|
debugfs_name);
|
continue;
|
}
|
ssv_sta_priv->ampdu_tid[i].debugfs_dir = ampdu_tx_debugfs_dir;
|
debugfs_create_file("baw_status", 00444, ampdu_tx_debugfs_dir,
|
ampdu_tid, &tid_window_fops);
|
debugfs_create_file("reset", 00644, ampdu_tx_debugfs_dir,
|
ampdu_tid, &mib_reset_fops);
|
debugfs_create_u32("total", 00444, ampdu_tx_debugfs_dir,
|
&du_mib->ampdu_mib_ampdu_counter);
|
debugfs_create_u32("retry", 00444, ampdu_tx_debugfs_dir,
|
&du_mib->ampdu_mib_retry_counter);
|
debugfs_create_u32("aggr_retry", 00444, ampdu_tx_debugfs_dir,
|
&du_mib->ampdu_mib_aggr_retry_counter);
|
debugfs_create_u32("BAR", 00444, ampdu_tx_debugfs_dir,
|
&du_mib->ampdu_mib_bar_counter);
|
debugfs_create_u32("Discarded", 00444, ampdu_tx_debugfs_dir,
|
&du_mib->ampdu_mib_discard_counter);
|
debugfs_create_u32("BA", 00444, ampdu_tx_debugfs_dir,
|
&du_mib->ampdu_mib_BA_counter);
|
debugfs_create_u32("Pass", 00444, ampdu_tx_debugfs_dir,
|
&du_mib->ampdu_mib_pass_counter);
|
for (j = 0; j <= SSV_AMPDU_aggr_num_max; j++)
|
{
|
char dist_dbg_name[10];
|
snprintf(dist_dbg_name, sizeof(dist_dbg_name), "aggr_%d", j);
|
debugfs_create_u32(dist_dbg_name, 00444, ampdu_tx_debugfs_dir,
|
&du_mib->ampdu_mib_dist[j]);
|
}
|
skb_queue_head_init(&ssv_sta_priv->ampdu_tid[i].ba_q);
|
}
|
}
|
#endif
|
void ssv6200_ampdu_tx_add_sta (struct ieee80211_hw *hw,
|
struct ieee80211_sta *sta)
|
{
|
struct ssv_sta_priv_data *ssv_sta_priv;
|
struct ssv_softc *sc;
|
u32 temp_i;
|
ssv_sta_priv = (struct ssv_sta_priv_data *) sta->drv_priv;
|
sc = (struct ssv_softc *) hw->priv;
|
for (temp_i = 0; temp_i < WMM_TID_NUM; temp_i++)
|
{
|
ssv_sta_priv->ampdu_tid[temp_i].sta = sta;
|
ssv_sta_priv->ampdu_tid[temp_i].state = AMPDU_STATE_STOP;
|
spin_lock_init(
|
&ssv_sta_priv->ampdu_tid[temp_i].ampdu_skb_tx_queue_lock);
|
spin_lock_init(&ssv_sta_priv->ampdu_tid[temp_i].pkt_array_lock);
|
}
|
#ifdef CONFIG_SSV6XXX_DEBUGFS
|
ssv6200_ampdu_tx_init_debugfs(sc, ssv_sta_priv);
|
#endif
|
}
|
void ssv6200_ampdu_tx_start (u16 tid, struct ieee80211_sta *sta,
|
struct ieee80211_hw *hw, u16 *ssn)
|
{
|
struct ssv_softc *sc = hw->priv;
|
struct ssv_sta_priv_data *ssv_sta_priv;
|
struct AMPDU_TID_st *ampdu_tid;
|
int i;
|
ssv_sta_priv = (struct ssv_sta_priv_data *) sta->drv_priv;
|
ampdu_tid = &ssv_sta_priv->ampdu_tid[tid];
|
ampdu_tid->ssv_baw_head = SSV_ILLEGAL_SN;
|
#if 0
|
if (list_empty(&sc->tx.ampdu_tx_que))
|
{
|
sc->ampdu_rekey_pause = AMPDU_REKEY_PAUSE_STOP;
|
}
|
#endif
|
#ifdef DEBUG_AMPDU_FLUSH
|
printk(KERN_ERR "Adding %02X-%02X-%02X-%02X-%02X-%02X TID %d (%p).\n",
|
sta->addr[0], sta->addr[1], sta->addr[2],
|
sta->addr[3], sta->addr[4], sta->addr[5],
|
ampdu_tid->tidno, ampdu_tid);
|
{
|
int j;
|
for (j = 0; j <= MAX_TID; j++)
|
{
|
if (sc->tid[j] == 0)
|
break;
|
}
|
if (j == MAX_TID)
|
{
|
printk(KERN_ERR "No room for new TID.\n");
|
}
|
else
|
sc->tid[j] = ampdu_tid;
|
}
|
#endif
|
list_add_tail_rcu(&du_tid->list, &sc->tx.ampdu_tx_que);
|
skb_queue_head_init(&du_tid->ampdu_skb_tx_queue);
|
#ifdef ENABLE_INCREMENTAL_AGGREGATION
|
skb_queue_head_init(&du_tid->early_aggr_ampdu_q);
|
ampdu_tid->early_aggr_skb_num = 0;
|
#endif
|
skb_queue_head_init(&du_tid->ampdu_skb_wait_encry_queue);
|
skb_queue_head_init(&du_tid->retry_queue);
|
skb_queue_head_init(&du_tid->release_queue);
|
for (i = 0;
|
i < (sizeof(ampdu_tid->aggr_pkts) / sizeof(ampdu_tid->aggr_pkts[0]));
|
i++)
|
ampdu_tid->aggr_pkts[i] = 0;
|
ampdu_tid->aggr_pkt_num = 0;
|
#ifdef ENABLE_AGGREGATE_IN_TIME
|
ampdu_tid->cur_ampdu_pkt = _alloc_ampdu_skb(sc, ampdu_tid, 0);
|
#endif
|
#ifdef AMPDU_CHECK_SKB_SEQNO
|
ssv_sta_priv->ampdu_tid[tid].last_seqno = (-1);
|
#endif
|
ssv_sta_priv->ampdu_mib_total_BA_counter = 0;
|
memset(&ssv_sta_priv->ampdu_tid[tid].mib, 0, sizeof(struct AMPDU_MIB_st));
|
ssv_sta_priv->ampdu_tid[tid].state = AMPDU_STATE_START;
|
#ifdef CONFIG_SSV6XXX_DEBUGFS
|
skb_queue_head_init(&ssv_sta_priv->ampdu_tid[tid].ba_q);
|
#endif
|
}
|
void ssv6200_ampdu_tx_operation (u16 tid, struct ieee80211_sta *sta,
|
struct ieee80211_hw *hw, u8 buffer_size)
|
{
|
struct ssv_sta_priv_data *ssv_sta_priv;
|
ssv_sta_priv = (struct ssv_sta_priv_data *) sta->drv_priv;
|
ssv_sta_priv->ampdu_tid[tid].tidno = tid;
|
ssv_sta_priv->ampdu_tid[tid].sta = sta;
|
ssv_sta_priv->ampdu_tid[tid].agg_num_max = MAX_AGGR_NUM;
|
#if 1
|
if (buffer_size > IEEE80211_MAX_AMPDU_BUF)
|
{
|
buffer_size = IEEE80211_MAX_AMPDU_BUF;
|
}
|
printk("ssv6200_ampdu_tx_operation:buffer_size=%d\n", buffer_size);
|
ssv_sta_priv->ampdu_tid[tid].ssv_baw_size = SSV_AMPDU_WINDOW_SIZE;
|
#else
|
ssv_sta_priv->ampdu_tid[tid].ssv_baw_size = IEEE80211_MIN_AMPDU_BUF << sta->ht_cap.ampdu_factor;
|
if(buffer_size > IEEE80211_MAX_AMPDU_BUF)
|
{
|
buffer_size = IEEE80211_MAX_AMPDU_BUF;
|
}
|
if(ssv_sta_priv->ampdu_tid[tid].ssv_baw_size > buffer_size)
|
{
|
ssv_sta_priv->ampdu_tid[tid].ssv_baw_size = buffer_size;
|
}
|
#endif
|
ssv_sta_priv->ampdu_tid[tid].state = AMPDU_STATE_OPERATION;
|
}
|
static void _clear_mpdu_q (struct ieee80211_hw *hw, struct sk_buff_head *q,
|
bool aggregated_mpdu)
|
{
|
struct sk_buff *skb;
|
while (1)
|
{
|
skb = skb_dequeue(q);
|
if (!skb)
|
break;
|
if (aggregated_mpdu)
|
skb_pull(skb, AMPDU_DELIMITER_LEN);
|
ieee80211_tx_status(hw, skb);
|
}
|
}
|
void ssv6200_ampdu_tx_stop (u16 tid, struct ieee80211_sta *sta,
|
struct ieee80211_hw *hw)
|
{
|
struct ssv_softc *sc = hw->priv;
|
struct ssv_sta_priv_data *ssv_sta_priv;
|
ssv_sta_priv = (struct ssv_sta_priv_data *) sta->drv_priv;
|
if (ssv_sta_priv->ampdu_tid[tid].state == AMPDU_STATE_STOP)
|
return;
|
ssv_sta_priv->ampdu_tid[tid].state = AMPDU_STATE_STOP;
|
printk("ssv6200_ampdu_tx_stop\n");
|
if (!list_empty(&sc->tx.ampdu_tx_que))
|
{
|
#ifdef DEBUG_AMPDU_FLUSH
|
{
|
int j;
|
struct AMPDU_TID_st *ampdu_tid = &ssv_sta_priv->ampdu_tid[tid];
|
for (j = 0; j <= MAX_TID; j++)
|
{
|
if (sc->tid[j] == ampdu_tid)
|
break;
|
}
|
if (j == MAX_TID)
|
{
|
printk(KERN_ERR "No TID found when deleting it.\n");
|
}
|
else
|
sc->tid[j] = NULL;
|
printk(KERN_ERR "Deleting %02X-%02X-%02X-%02X-%02X-%02X TID %d (%p).\n",
|
sta->addr[0], sta->addr[1], sta->addr[2],
|
sta->addr[3], sta->addr[4], sta->addr[5],
|
ampdu_tid->tidno, ampdu_tid);
|
}
|
#endif
|
list_del_rcu(&ssv_sta_priv->ampdu_tid[tid].list);
|
}
|
printk("clear tx q len=%d\n",
|
skb_queue_len(&ssv_sta_priv->ampdu_tid[tid].ampdu_skb_tx_queue));
|
_clear_mpdu_q(sc->hw, &ssv_sta_priv->ampdu_tid[tid].ampdu_skb_tx_queue,
|
true);
|
printk("clear retry q len=%d\n",
|
skb_queue_len(&ssv_sta_priv->ampdu_tid[tid].retry_queue));
|
_clear_mpdu_q(sc->hw, &ssv_sta_priv->ampdu_tid[tid].retry_queue, true);
|
#ifdef USE_ENCRYPT_WORK
|
printk("clear encrypt q len=%d\n",skb_queue_len(&ssv_sta_priv->ampdu_tid[tid].ampdu_skb_wait_encry_queue));
|
_clear_mpdu_q(sc->hw, &ssv_sta_priv->ampdu_tid[tid].ampdu_skb_wait_encry_queue, false);
|
#endif
|
#ifdef ENABLE_AGGREGATE_IN_TIME
|
if (ssv_sta_priv->ampdu_tid[tid].cur_ampdu_pkt != NULL)
|
{
|
dev_kfree_skb_any(ssv_sta_priv->ampdu_tid[tid].cur_ampdu_pkt);
|
ssv_sta_priv->ampdu_tid[tid].cur_ampdu_pkt = NULL;
|
}
|
#endif
|
ssv6200_tx_flow_control((void *) sc,
|
sc->tx.hw_txqid[ssv_sta_priv->ampdu_tid[tid].ac],
|
false, 1000);
|
}
|
static void ssv6200_ampdu_tx_state_stop_func (
|
struct ssv_softc *sc, struct ieee80211_sta *sta, struct sk_buff *skb,
|
struct AMPDU_TID_st *cur_AMPDU_TID)
|
{
|
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data;
|
u8 *skb_qos_ctl = ieee80211_get_qos_ctl(hdr);
|
u8 tid_no = skb_qos_ctl[0] & 0xf;
|
if ((sta->ht_cap.ht_supported == true)
|
&& (!!(sc->sh->cfg.hw_caps & SSV6200_HW_CAP_AMPDU_TX)))
|
{
|
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,32)
|
ieee80211_start_tx_ba_session(sc->hw, (u8*)(sta->addr), (u16)tid_no);
|
#elif LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,37)
|
ieee80211_start_tx_ba_session(sta, tid_no);
|
#else
|
ieee80211_start_tx_ba_session(sta, tid_no, 0);
|
#endif
|
ampdu_db_log("start ampdu_tx(rc) : tid_no = %d\n", tid_no);
|
}
|
}
|
static void ssv6200_ampdu_tx_state_operation_func (
|
struct ssv_softc *sc, struct ieee80211_sta *sta, struct sk_buff *skb,
|
struct AMPDU_TID_st *cur_AMPDU_TID)
|
{
|
#if 0
|
else if (sc->ampdu_rekey_pause == AMPDU_REKEY_PAUSE_ONGOING)
|
{
|
pause_ampdu = true;
|
printk("!!!ampdu_rekey_pause\n");
|
}
|
#endif
|
}
|
void ssv6200_ampdu_tx_update_state (void *priv, struct ieee80211_sta *sta,
|
struct sk_buff *skb)
|
{
|
struct ssv_softc *sc = (struct ssv_softc *) priv;
|
struct ssv_sta_priv_data *ssv_sta_priv =
|
(struct ssv_sta_priv_data *) sta->drv_priv;
|
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data;
|
u8 *skb_qos_ctl;
|
u8 tid_no;
|
{
|
skb_qos_ctl = ieee80211_get_qos_ctl(hdr);
|
tid_no = skb_qos_ctl[0] & 0xf;
|
switch (ssv_sta_priv->ampdu_tid[tid_no].state)
|
{
|
case AMPDU_STATE_STOP:
|
ssv6200_ampdu_tx_state_stop_func(
|
sc, sta, skb, &(ssv_sta_priv->ampdu_tid[tid_no]));
|
break;
|
case AMPDU_STATE_START:
|
break;
|
case AMPDU_STATE_OPERATION:
|
ssv6200_ampdu_tx_state_operation_func(
|
sc, sta, skb, &(ssv_sta_priv->ampdu_tid[tid_no]));
|
break;
|
default:
|
break;
|
}
|
}
|
}
|
void _put_mpdu_to_ampdu (struct sk_buff *ampdu, struct sk_buff *mpdu)
|
{
|
bool is_empty_ampdu = (ampdu->len == 0);
|
unsigned char *data_dest;
|
struct ampdu_hdr_st *ampdu_hdr = (struct ampdu_hdr_st *) ampdu->head;
|
BUG_ON(skb_tailroom(ampdu) < mpdu->len);
|
data_dest = skb_tail_pointer(ampdu);
|
skb_put(ampdu, mpdu->len);
|
if (is_empty_ampdu)
|
{
|
struct ieee80211_tx_info *ampdu_info = IEEE80211_SKB_CB(ampdu);
|
struct ieee80211_tx_info *mpdu_info = IEEE80211_SKB_CB(mpdu);
|
SKB_info *mpdu_skb_info = (SKB_info *)(mpdu->head);
|
u32 max_size_for_rate = ampdu_max_transmit_length[mpdu_skb_info->lowest_rate];
|
BUG_ON(max_size_for_rate == 0);
|
memcpy(ampdu_info, mpdu_info, sizeof(struct ieee80211_tx_info));
|
skb_set_queue_mapping(ampdu, skb_get_queue_mapping(mpdu));
|
ampdu_hdr->first_sn = ampdu_skb_ssn(mpdu);
|
ampdu_hdr->sta = ((struct SKB_info_st *)mpdu->head)->sta;
|
if (ampdu_hdr->max_size > max_size_for_rate)
|
ampdu_hdr->max_size = max_size_for_rate;
|
memcpy(ampdu_hdr->rates, mpdu_skb_info->rates, sizeof(ampdu_hdr->rates));
|
}
|
memcpy(data_dest, mpdu->data, mpdu->len);
|
__skb_queue_tail(&du_hdr->mpdu_q, mpdu);
|
ampdu_hdr->ssn[ampdu_hdr->mpdu_num++] = ampdu_skb_ssn(mpdu);
|
ampdu_hdr->size += mpdu->len;
|
BUG_ON(ampdu_hdr->size > ampdu_hdr->max_size);
|
}
|
u32 _flush_early_ampdu_q (struct ssv_softc *sc, struct AMPDU_TID_st *ampdu_tid)
|
{
|
u32 flushed_ampdu = 0;
|
unsigned long flags;
|
struct sk_buff_head *early_aggr_ampdu_q = &du_tid->early_aggr_ampdu_q;
|
spin_lock_irqsave(&early_aggr_ampdu_q->lock, flags);
|
while (skb_queue_len(early_aggr_ampdu_q))
|
{
|
struct sk_buff *head_ampdu;
|
struct ampdu_hdr_st *head_ampdu_hdr;
|
u32 ampdu_aggr_num;
|
head_ampdu = skb_peek(early_aggr_ampdu_q);
|
head_ampdu_hdr = (struct ampdu_hdr_st *) head_ampdu->head;
|
ampdu_aggr_num = skb_queue_len(&head_ampdu_hdr->mpdu_q);
|
if ((SSV_AMPDU_BA_WINDOW_SIZE - ampdu_tid->aggr_pkt_num)
|
< ampdu_aggr_num)
|
break;
|
if (_sync_ampdu_pkt_arr(ampdu_tid, head_ampdu, false))
|
{
|
head_ampdu = __skb_dequeue(early_aggr_ampdu_q);
|
ampdu_tid->early_aggr_skb_num -= ampdu_aggr_num;
|
#ifdef SSV_AMPDU_FLOW_CONTROL
|
if (ampdu_tid->early_aggr_skb_num
|
<= SSV_AMPDU_FLOW_CONTROL_LOWER_BOUND)
|
{
|
ssv6200_tx_flow_control((void *) sc,
|
sc->tx.hw_txqid[ampdu_tid->ac], false, 1000);
|
}
|
#endif
|
if ((skb_queue_len(early_aggr_ampdu_q) == 0)
|
&& (ampdu_tid->early_aggr_skb_num > 0))
|
{
|
printk(KERN_ERR "Empty early Q w. %d.\n", ampdu_tid->early_aggr_skb_num);
|
}
|
spin_unlock_irqrestore(&early_aggr_ampdu_q->lock, flags);
|
_send_hci_skb(sc, head_ampdu,
|
AMPDU_HCI_SEND_TAIL_WITHOUT_FLOWCTRL);
|
spin_lock_irqsave(&early_aggr_ampdu_q->lock, flags);
|
flushed_ampdu++;
|
}
|
else
|
break;
|
}
|
spin_unlock_irqrestore(&early_aggr_ampdu_q->lock, flags);
|
return flushed_ampdu;
|
}
|
volatile int max_aggr_num = 24;
|
void _aggr_ampdu_tx_q (struct ieee80211_hw *hw, struct AMPDU_TID_st *ampdu_tid)
|
{
|
struct ssv_softc *sc = hw->priv;
|
struct sk_buff *ampdu_skb = ampdu_tid->cur_ampdu_pkt;
|
while (skb_queue_len(&du_tid->ampdu_skb_tx_queue))
|
{
|
u32 aggr_len;
|
struct sk_buff *mpdu_skb;
|
struct ampdu_hdr_st *ampdu_hdr;
|
bool is_aggr_full = false;
|
if (ampdu_skb == NULL)
|
{
|
ampdu_skb = _alloc_ampdu_skb(sc, ampdu_tid, 0);
|
if (ampdu_skb == NULL)
|
break;
|
ampdu_tid->cur_ampdu_pkt = ampdu_skb;
|
}
|
ampdu_hdr = (struct ampdu_hdr_st *) ampdu_skb->head;
|
aggr_len = skb_queue_len(&du_hdr->mpdu_q);
|
do
|
{
|
struct sk_buff_head *tx_q = &du_tid->ampdu_skb_tx_queue;
|
unsigned long flags;
|
spin_lock_irqsave(&tx_q->lock, flags);
|
mpdu_skb = skb_peek(&du_tid->ampdu_skb_tx_queue);
|
if (mpdu_skb == NULL)
|
{
|
spin_unlock_irqrestore(&tx_q->lock, flags);
|
break;
|
}
|
if ((mpdu_skb->len + ampdu_hdr->size) > ampdu_hdr->max_size)
|
{
|
is_aggr_full = true;
|
spin_unlock_irqrestore(&tx_q->lock, flags);
|
break;
|
}
|
mpdu_skb = __skb_dequeue(&du_tid->ampdu_skb_tx_queue);
|
spin_unlock_irqrestore(&tx_q->lock, flags);
|
_put_mpdu_to_ampdu(ampdu_skb, mpdu_skb);
|
} while (++aggr_len < max_aggr_num );
|
if ( (is_aggr_full || (aggr_len >= max_aggr_num))
|
|| ( (aggr_len > 0)
|
&& (skb_queue_len(&du_tid->early_aggr_ampdu_q) == 0)
|
&& (ampdu_tid->ssv_baw_head == SSV_ILLEGAL_SN)
|
&& _is_skb_q_empty(sc, ampdu_skb)))
|
{
|
_add_ampdu_txinfo(sc, ampdu_skb);
|
_queue_early_ampdu(sc, ampdu_tid, ampdu_skb);
|
ampdu_tid->cur_ampdu_pkt = ampdu_skb = NULL;
|
}
|
_flush_early_ampdu_q(sc, ampdu_tid);
|
}
|
}
|
void _queue_early_ampdu (struct ssv_softc *sc, struct AMPDU_TID_st *ampdu_tid,
|
struct sk_buff *ampdu_skb)
|
{
|
unsigned long flags;
|
struct ampdu_hdr_st *ampdu_hdr = (struct ampdu_hdr_st *) ampdu_skb->head;
|
spin_lock_irqsave(&du_tid->early_aggr_ampdu_q.lock, flags);
|
__skb_queue_tail(&du_tid->early_aggr_ampdu_q, ampdu_skb);
|
ampdu_tid->early_aggr_skb_num += skb_queue_len(&du_hdr->mpdu_q);
|
#ifdef SSV_AMPDU_FLOW_CONTROL
|
if (ampdu_tid->early_aggr_skb_num >= SSV_AMPDU_FLOW_CONTROL_UPPER_BOUND)
|
{
|
ssv6200_tx_flow_control((void *) sc, sc->tx.hw_txqid[ampdu_tid->ac],
|
true, 1000);
|
}
|
#endif
|
spin_unlock_irqrestore(&du_tid->early_aggr_ampdu_q.lock, flags);
|
}
|
void _flush_mpdu (struct ssv_softc *sc, struct ieee80211_sta *sta)
|
{
|
unsigned long flags;
|
struct ssv_sta_priv_data *ssv_sta_priv = (struct ssv_sta_priv_data *) sta->drv_priv;
|
int i;
|
for (i = 0; i < (sizeof(ssv_sta_priv->ampdu_tid) / sizeof(ssv_sta_priv->ampdu_tid[0])); i++)
|
{
|
struct AMPDU_TID_st *ampdu_tid = &ssv_sta_priv->ampdu_tid[i];
|
struct sk_buff_head *early_aggr_ampdu_q;
|
struct sk_buff *ampdu;
|
struct ampdu_hdr_st *ampdu_hdr;
|
struct sk_buff_head *mpdu_q;
|
struct sk_buff *mpdu;
|
if (ampdu_tid->state != AMPDU_STATE_OPERATION)
|
continue;
|
early_aggr_ampdu_q = &du_tid->early_aggr_ampdu_q;
|
spin_lock_irqsave(&early_aggr_ampdu_q->lock, flags);
|
while ((ampdu = __skb_dequeue(early_aggr_ampdu_q)) != NULL)
|
{
|
ampdu_hdr = (struct ampdu_hdr_st *)ampdu->head;
|
mpdu_q = &du_hdr->mpdu_q;
|
spin_unlock_irqrestore(&early_aggr_ampdu_q->lock, flags);
|
while ((mpdu = __skb_dequeue(mpdu_q)) != NULL)
|
{
|
_send_hci_skb(sc, mpdu, AMPDU_HCI_SEND_TAIL_WITHOUT_FLOWCTRL);
|
}
|
ssv6200_ampdu_release_skb(ampdu, sc->hw);
|
spin_lock_irqsave(&early_aggr_ampdu_q->lock, flags);
|
}
|
if (ampdu_tid->cur_ampdu_pkt != NULL)
|
{
|
ampdu_hdr = (struct ampdu_hdr_st *)ampdu_tid->cur_ampdu_pkt->head;
|
mpdu_q = &du_hdr->mpdu_q;
|
spin_unlock_irqrestore(&early_aggr_ampdu_q->lock, flags);
|
while ((mpdu = __skb_dequeue(mpdu_q)) != NULL)
|
{
|
_send_hci_skb(sc, mpdu, AMPDU_HCI_SEND_TAIL_WITHOUT_FLOWCTRL);
|
}
|
ssv6200_ampdu_release_skb(ampdu_tid->cur_ampdu_pkt, sc->hw);
|
spin_lock_irqsave(&early_aggr_ampdu_q->lock, flags);
|
ampdu_tid->cur_ampdu_pkt = NULL;
|
}
|
spin_unlock_irqrestore(&early_aggr_ampdu_q->lock, flags);
|
}
|
}
|
bool ssv6200_ampdu_tx_handler (struct ieee80211_hw *hw, struct sk_buff *skb)
|
{
|
struct ssv_softc *sc = hw->priv;
|
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data;
|
#ifdef REPORT_TX_STATUS_DIRECTLY
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
|
struct sk_buff *tx_skb = skb;
|
struct sk_buff *copy_skb = NULL;
|
#endif
|
struct SKB_info_st *mpdu_skb_info_p = (SKB_info *) (skb->head);
|
struct ieee80211_sta *sta = mpdu_skb_info_p->sta;
|
struct ssv_sta_priv_data *ssv_sta_priv =
|
(struct ssv_sta_priv_data *) sta->drv_priv;
|
u8 tidno;
|
struct AMPDU_TID_st *ampdu_tid;
|
if (sta == NULL)
|
{
|
WARN_ON(1);
|
return false;
|
}
|
tidno = ieee80211_get_qos_ctl(hdr)[0] & IEEE80211_QOS_CTL_TID_MASK;
|
ampdu_db_log("tidno = %d\n", tidno);
|
ampdu_tid = &ssv_sta_priv->ampdu_tid[tidno];
|
if (ampdu_tid->state != AMPDU_STATE_OPERATION)
|
return false;
|
#ifdef AMPDU_CHECK_SKB_SEQNO
|
{
|
u32 skb_seqno = ((struct ieee80211_hdr*) (skb->data))->seq_ctrl
|
>> SSV_SEQ_NUM_SHIFT;
|
u32 tid_seqno = ampdu_tid->last_seqno;
|
if ((tid_seqno != (-1)) && (skb_seqno != NEXT_PKT_SN(tid_seqno)))
|
{
|
prn_aggr_err("Non continueous seq no: %d - %d\n", tid_seqno, skb_seqno);
|
return false;
|
}
|
ampdu_tid->last_seqno = skb_seqno;
|
}
|
#endif
|
#if 1
|
mpdu_skb_info_p->lowest_rate = ssv62xx_ht_rate_update(skb, sc, mpdu_skb_info_p->rates);
|
if (ampdu_max_transmit_length[mpdu_skb_info_p->lowest_rate] == 0)
|
{
|
_flush_mpdu(sc, sta);
|
return false;
|
}
|
#endif
|
#if 0
|
if ((ampdu_tid->ssv_baw_head == SSV_ILLEGAL_SN)
|
&& (skb_queue_len(&du_tid->ampdu_skb_tx_queue) == 0)
|
&& (skb_queue_len(&du_tid->early_aggr_ampdu_q) == 0)
|
&& ((ampdu_tid->cur_ampdu_pkt == NULL)
|
|| (skb_queue_len(
|
&((struct ampdu_hdr_st *) (ampdu_tid->cur_ampdu_pkt->head))->mpdu_q)
|
== 0))
|
&& _is_skb_q_empty(sc, skb))
|
{
|
ampdu_tid->mib.ampdu_mib_pass_counter++;
|
#if 0
|
prn_aggr_err(
|
"pass %d\n",
|
((struct ieee80211_hdr*)(skb->data))->seq_ctrl >> SSV_SEQ_NUM_SHIFT);
|
#else
|
if ((ssv_sta_priv->ampdu_tid[tidno].mib.ampdu_mib_pass_counter % 1024) == 0)
|
prn_aggr_err("STA %d TID %d passed %d\n", ssv_sta_priv->sta_idx,
|
ampdu_tid->tidno,
|
ampdu_tid->mib.ampdu_mib_pass_counter);
|
#endif
|
return false;
|
}
|
#endif
|
mpdu_skb_info_p = (SKB_info *) (skb->head);
|
mpdu_skb_info_p->mpdu_retry_counter = 0;
|
mpdu_skb_info_p->ampdu_tx_status = AMPDU_ST_NON_AMPDU;
|
mpdu_skb_info_p->ampdu_tx_final_retry_count = 0;
|
ssv_sta_priv->ampdu_tid[tidno].ac = skb_get_queue_mapping(skb);
|
#if 0
|
#ifndef CONFIG_SSV_SW_ENCRYPT_HW_DECRYPT
|
if ((sc->ampdu_ccmp_encrypt == true))
|
{
|
skb_queue_tail(&ssv_sta_priv->ampdu_tid[tidno].ampdu_skb_wait_encry_queue, skb);
|
if (sc->ampdu_encry_work_scheduled == false)
|
{
|
schedule_work(&sc->ampdu_tx_encry_work);
|
}
|
}
|
else if (sc->algorithm != ME_NONE)
|
{
|
printk("None ccmp skb into AMPDU sc->algorithm = %d\n",sc->algorithm);
|
info = IEEE80211_SKB_CB(skb);
|
info->flags &= (~IEEE80211_TX_CTL_AMPDU);
|
if(ssv6200_mpdu_send_HCI(sc->hw, skb))
|
ieee80211_tx_status_irqsafe(sc->hw, skb);
|
}
|
else
|
#endif
|
#endif
|
#ifdef REPORT_TX_STATUS_DIRECTLY
|
info->flags |= IEEE80211_TX_STAT_ACK;
|
copy_skb = skb_copy(tx_skb, GFP_ATOMIC);
|
if (!copy_skb) {
|
printk("create TX skb copy failed!\n");
|
return false;
|
}
|
ieee80211_tx_status(sc->hw, tx_skb);
|
skb = copy_skb;
|
#endif
|
{
|
bool ret;
|
ret = ssv6200_ampdu_add_delimiter_and_crc32(skb);
|
if (ret == false)
|
{
|
ssv6200_ampdu_release_skb(skb, hw);
|
return false;
|
}
|
skb_queue_tail(&ssv_sta_priv->ampdu_tid[tidno].ampdu_skb_tx_queue, skb);
|
ssv_sta_priv->ampdu_tid[tidno].timestamp = jiffies;
|
}
|
_aggr_ampdu_tx_q(hw, &ssv_sta_priv->ampdu_tid[tidno]);
|
return true;
|
}
|
u32 ssv6xxx_ampdu_flush (struct ieee80211_hw *hw)
|
{
|
struct ssv_softc *sc = hw->priv;
|
struct AMPDU_TID_st *cur_AMPDU_TID;
|
u32 flushed_ampdu = 0;
|
u32 tid_idx = 0;
|
if (!list_empty(&sc->tx.ampdu_tx_que))
|
{
|
list_for_each_entry_rcu(cur_AMPDU_TID, &sc->tx.ampdu_tx_que, list)
|
{
|
tid_idx++;
|
#ifdef DEBUG_AMPDU_FLUSH
|
{
|
int i = 0;
|
for (i = 0; i < MAX_TID; i++)
|
if (sc->tid[i] == cur_AMPDU_TID)
|
break;
|
if (i == MAX_TID)
|
{
|
printk(KERN_ERR "No matching TID (%d) found! %p\n", tid_idx, cur_AMPDU_TID);
|
continue;
|
}
|
}
|
#endif
|
if (cur_AMPDU_TID->state != AMPDU_STATE_OPERATION)
|
{
|
struct ieee80211_sta *sta = cur_AMPDU_TID->sta;
|
struct ssv_sta_priv_data *sta_priv = (struct ssv_sta_priv_data *)sta->drv_priv;
|
dev_dbg(sc->dev, "STA %d TID %d is @%d\n",
|
sta_priv->sta_idx, cur_AMPDU_TID->tidno,
|
cur_AMPDU_TID->state);
|
continue;
|
}
|
if ((cur_AMPDU_TID->state == AMPDU_STATE_OPERATION)
|
&& (skb_queue_len(&cur_AMPDU_TID->early_aggr_ampdu_q) == 0)
|
&& (cur_AMPDU_TID->cur_ampdu_pkt != NULL))
|
{
|
struct ampdu_hdr_st *ampdu_hdr =
|
(struct ampdu_hdr_st *) (cur_AMPDU_TID->cur_ampdu_pkt->head);
|
u32 aggr_len = skb_queue_len(&du_hdr->mpdu_q);
|
if (aggr_len)
|
{
|
struct sk_buff *ampdu_skb = cur_AMPDU_TID->cur_ampdu_pkt;
|
cur_AMPDU_TID->cur_ampdu_pkt = NULL;
|
_add_ampdu_txinfo(sc, ampdu_skb);
|
_queue_early_ampdu(sc, cur_AMPDU_TID, ampdu_skb);
|
#if 0
|
prn_aggr_err("A%c %d %d\n", sc->tx_q_empty ? 'e' : 't',
|
ampdu_hdr->first_sn, aggr_len);
|
#endif
|
}
|
}
|
if (skb_queue_len(&cur_AMPDU_TID->early_aggr_ampdu_q) > 0)
|
flushed_ampdu += _flush_early_ampdu_q(sc, cur_AMPDU_TID);
|
}
|
}
|
return flushed_ampdu;
|
}
|
int _dump_BA_notification (char *buf,
|
struct ampdu_ba_notify_data *ba_notification)
|
{
|
int i;
|
char *orig_buf = buf;
|
for (i = 0; i < MAX_AGGR_NUM; i++)
|
{
|
if (ba_notification->seq_no[i] == (u16) (-1))
|
break;
|
buf += sprintf(buf, " %d", ba_notification->seq_no[i]);
|
}
|
return ((size_t)buf - (size_t)orig_buf);
|
}
|
int _dump_ba_skb (char *buf, int buf_size, struct sk_buff *ba_skb)
|
{
|
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) (ba_skb->data
|
+ SSV6XXX_RX_DESC_LEN);
|
AMPDU_BLOCKACK *BA_frame = (AMPDU_BLOCKACK *) hdr;
|
u32 ssn = BA_frame->BA_ssn;
|
struct ampdu_ba_notify_data *ba_notification =
|
(struct ampdu_ba_notify_data *) (ba_skb->data + ba_skb->len
|
- sizeof(struct ampdu_ba_notify_data));
|
int prt_size;
|
prt_size = snprintf(buf, buf_size, "\n\t\t%04d %08X %08X -",
|
ssn, BA_frame->BA_sn_bit_map[0], BA_frame->BA_sn_bit_map[1]);
|
buf_size -= prt_size;
|
buf += prt_size;
|
prt_size = prt_size + _dump_BA_notification(buf, ba_notification);
|
return prt_size;
|
}
|
static bool _ssn_to_bit_idx (u32 start_ssn, u32 mpdu_ssn, u32 *word_idx,
|
u32 *bit_idx)
|
{
|
u32 ret_bit_idx, ret_word_idx = 0;
|
s32 diff = mpdu_ssn - start_ssn;
|
if (diff >= 0)
|
{
|
if (diff >= SSV_AMPDU_BA_WINDOW_SIZE)
|
{
|
return false;
|
}
|
ret_bit_idx = diff;
|
}
|
else
|
{
|
diff = -diff;
|
if (diff <= (SSV_AMPDU_MAX_SSN - SSV_AMPDU_BA_WINDOW_SIZE))
|
{
|
*word_idx = 0;
|
*bit_idx = 0;
|
return false;
|
}
|
ret_bit_idx = SSV_AMPDU_MAX_SSN - diff;
|
}
|
if (ret_bit_idx >= 32)
|
{
|
ret_bit_idx -= 32;
|
ret_word_idx = 1;
|
}
|
*bit_idx = ret_bit_idx;
|
*word_idx = ret_word_idx;
|
return true;
|
}
|
static bool _inc_bit_idx (u32 ssn_1st, u32 ssn_next, u32 *word_idx,
|
u32 *bit_idx)
|
{
|
u32 ret_word_idx = *word_idx, ret_bit_idx = *bit_idx;
|
s32 diff = (s32) ssn_1st - (s32) ssn_next;
|
if (diff > 0)
|
{
|
if (diff < (SSV_AMPDU_MAX_SSN - SSV_AMPDU_BA_WINDOW_SIZE))
|
{
|
prn_aggr_err("Irrational SN distance in AMPDU: %d %d.\n",
|
ssn_1st, ssn_next);
|
return false;
|
}
|
diff = SSV_AMPDU_MAX_SSN - diff;
|
}
|
else
|
{
|
diff = -diff;
|
}
|
if (diff > SSV_AMPDU_MAX_SSN)
|
prn_aggr_err("DF %d - %d = %d\n", ssn_1st, ssn_next, diff);
|
ret_bit_idx += diff;
|
if (ret_bit_idx >= 32)
|
{
|
ret_bit_idx -= 32;
|
ret_word_idx++;
|
}
|
*word_idx = ret_word_idx;
|
*bit_idx = ret_bit_idx;
|
return true;
|
}
|
static void _release_frames (struct AMPDU_TID_st *ampdu_tid)
|
{
|
u32 head_ssn, head_ssn_before, last_ssn;
|
struct sk_buff **skb;
|
struct SKB_info_st *skb_info;
|
spin_lock_bh(&du_tid->pkt_array_lock);
|
head_ssn_before = ampdu_tid->ssv_baw_head;
|
#if 1
|
if (head_ssn_before >= SSV_AMPDU_MAX_SSN)
|
{
|
spin_unlock_bh(&du_tid->pkt_array_lock);
|
prn_aggr_err("l x.x %d\n", head_ssn_before);
|
return;
|
}
|
#else
|
BUG_ON(head_ssn_before >= SSV_AMPDU_MAX_SSN);
|
#endif
|
head_ssn = ampdu_tid->ssv_baw_head;
|
last_ssn = head_ssn;
|
do
|
{
|
skb = &INDEX_PKT_BY_SSN(ampdu_tid, head_ssn);
|
if (*skb == NULL)
|
{
|
head_ssn = SSV_ILLEGAL_SN;
|
{
|
int i;
|
char sn_str[66 * 5] = "";
|
char *str = sn_str;
|
for (i = 0; i < 64; i++)
|
if (ampdu_tid->aggr_pkts[i] != NULL)
|
{
|
str += sprintf(str, "%d ",
|
ampdu_skb_ssn(ampdu_tid->aggr_pkts[i]));
|
}
|
*str = 0;
|
if (str == sn_str)
|
{
|
}
|
else
|
prn_aggr_err(
|
"ILL %d %d - %d (%s)\n",
|
head_ssn_before, last_ssn, ampdu_tid->aggr_pkt_num, sn_str);
|
}
|
break;
|
}
|
skb_info = (struct SKB_info_st *) ((*skb)->head);
|
if ((skb_info->ampdu_tx_status == AMPDU_ST_DONE)
|
|| (skb_info->ampdu_tx_status == AMPDU_ST_DROPPED))
|
{
|
__skb_queue_tail(&du_tid->release_queue, *skb);
|
*skb = NULL;
|
last_ssn = head_ssn;
|
INC_PKT_SN(head_ssn);
|
ampdu_tid->aggr_pkt_num--;
|
if (skb_info->ampdu_tx_status == AMPDU_ST_DROPPED)
|
ampdu_tid->mib.ampdu_mib_discard_counter++;
|
}
|
else
|
{
|
break;
|
}
|
} while (1);
|
ampdu_tid->ssv_baw_head = head_ssn;
|
#if 0
|
if (head_ssn == SSV_ILLEGAL_SN)
|
{
|
u32 i = head_ssn_before;
|
do
|
{
|
skb = &INDEX_PKT_BY_SSN(ampdu_tid, i);
|
if (*skb != NULL)
|
prn_aggr_err("O.o %d: %d - %d\n", head_ssn_before, i, ampdu_skb_ssn(*skb));
|
INC_PKT_SN(i);
|
}while (i != head_ssn_before);
|
}
|
#endif
|
spin_unlock_bh(&du_tid->pkt_array_lock);
|
#if 0
|
if (head_ssn_before != head_ssn)
|
{
|
prn_aggr_err("H %d -> %d (%d - %d)\n", head_ssn_before, head_ssn,
|
ampdu_tid->aggr_pkt_num, skb_queue_len(&du_tid->ampdu_skb_tx_queue));
|
}
|
#endif
|
}
|
static int _collect_retry_frames (struct AMPDU_TID_st *ampdu_tid)
|
{
|
u16 ssn, head_ssn, end_ssn;
|
int num_retry = 0;
|
int timeout_check = 1;
|
unsigned long check_jiffies = jiffies;
|
head_ssn = ampdu_tid->ssv_baw_head;
|
ssn = head_ssn;
|
if (ssn == SSV_ILLEGAL_SN)
|
return 0;
|
end_ssn = (head_ssn + SSV_AMPDU_BA_WINDOW_SIZE) % SSV_AMPDU_MAX_SSN;
|
do
|
{
|
struct sk_buff *skb = INDEX_PKT_BY_SSN(ampdu_tid, ssn);
|
struct SKB_info_st *skb_info;
|
int timeout_retry = 0;
|
if (skb == NULL)
|
break;
|
skb_info = (SKB_info *) (skb->head);
|
if ( timeout_check
|
&& (skb_info->ampdu_tx_status == AMPDU_ST_SENT))
|
{
|
unsigned long cur_jiffies = jiffies;
|
unsigned long timeout_jiffies = skb_info->aggr_timestamp
|
+ msecs_to_jiffies(BA_WAIT_TIMEOUT);
|
u32 delta_ms;
|
if (time_before(cur_jiffies, timeout_jiffies))
|
{
|
timeout_check = 0;
|
continue;
|
}
|
_mark_skb_retry(skb_info, skb);
|
delta_ms = jiffies_to_msecs(cur_jiffies - skb_info->aggr_timestamp);
|
prn_aggr_err("t S%d-T%d-%d (%u)\n",
|
((struct ssv_sta_priv_data *)skb_info->sta->drv_priv)->sta_idx,
|
ampdu_tid->tidno, ssn,
|
delta_ms);
|
if (delta_ms > 1000)
|
{
|
prn_aggr_err("Last checktime %lu - %lu = %u\n",
|
check_jiffies, ampdu_tid->timestamp,
|
jiffies_to_msecs(check_jiffies - ampdu_tid->timestamp));
|
}
|
timeout_retry = 1;
|
}
|
if (skb_info->ampdu_tx_status == AMPDU_ST_RETRY)
|
{
|
#if 0
|
if (!timeout_retry)
|
prn_aggr_dbg("r %d - %d\n", ssn, ampdu_skb_ssn(skb));
|
#endif
|
skb_queue_tail(&du_tid->retry_queue, skb);
|
ampdu_tid->mib.ampdu_mib_retry_counter++;
|
num_retry++;
|
}
|
INC_PKT_SN(ssn);
|
} while (ssn != end_ssn);
|
ampdu_tid->timestamp = check_jiffies;
|
return num_retry;
|
}
|
int _mark_skb_retry (struct SKB_info_st *skb_info, struct sk_buff *skb)
|
{
|
if (skb_info->mpdu_retry_counter < SSV_AMPDU_retry_counter_max)
|
{
|
if (skb_info->mpdu_retry_counter == 0)
|
{
|
struct ieee80211_hdr *skb_hdr = ampdu_skb_hdr(skb);
|
skb_hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_RETRY);
|
}
|
skb_info->ampdu_tx_status = AMPDU_ST_RETRY;
|
skb_info->mpdu_retry_counter++;
|
return 1;
|
}
|
else
|
{
|
skb_info->ampdu_tx_status = AMPDU_ST_DROPPED;
|
prn_aggr_err("p %d\n", ampdu_skb_ssn(skb));
|
return 0;
|
}
|
}
|
static u32 _ba_map_walker (struct AMPDU_TID_st *ampdu_tid, u32 start_ssn,
|
u32 sn_bit_map[2],
|
struct ampdu_ba_notify_data *ba_notify_data,
|
u32 *p_acked_num)
|
{
|
int i = 0;
|
u32 ssn = ba_notify_data->seq_no[0];
|
u32 word_idx = (-1), bit_idx = (-1);
|
bool found = _ssn_to_bit_idx(start_ssn, ssn, &word_idx, &bit_idx);
|
bool first_found = found;
|
u32 aggr_num = 0;
|
u32 acked_num = 0;
|
if (found && (word_idx >= 2 || bit_idx >= 32))
|
prn_aggr_err("idx error 1: %d %d %d %d\n",
|
start_ssn, ssn, word_idx, bit_idx);
|
while ((i < MAX_AGGR_NUM) && (ssn < SSV_AMPDU_MAX_SSN))
|
{
|
u32 cur_ssn;
|
struct sk_buff *skb = INDEX_PKT_BY_SSN(ampdu_tid, ssn);
|
u32 skb_ssn = (skb == NULL) ? (-1) : ampdu_skb_ssn(skb);
|
struct SKB_info_st *skb_info;
|
aggr_num++;
|
if (skb_ssn != ssn)
|
{
|
prn_aggr_err("Unmatched SSN packet: %d - %d - %d\n",
|
ssn, skb_ssn, start_ssn);
|
}
|
else
|
{
|
skb_info = (struct SKB_info_st *) (skb->head);
|
if (found && (sn_bit_map[word_idx] & (1 << bit_idx)))
|
{
|
if (skb_info->ampdu_tx_status != AMPDU_ST_SENT)
|
{
|
printk(KERN_ERR "BA marks a MPDU of status %d!\n",
|
skb_info->ampdu_tx_status);
|
}
|
skb_info->ampdu_tx_status = AMPDU_ST_DONE;
|
acked_num++;
|
}
|
else
|
{
|
_mark_skb_retry(skb_info, skb);
|
}
|
}
|
cur_ssn = ssn;
|
if (++i >= MAX_AGGR_NUM)
|
break;
|
ssn = ba_notify_data->seq_no[i];
|
if (ssn >= SSV_AMPDU_MAX_SSN)
|
break;
|
if (first_found)
|
{
|
u32 old_word_idx = word_idx, old_bit_idx = bit_idx;
|
found = _inc_bit_idx(cur_ssn, ssn, &word_idx, &bit_idx);
|
if (found && (word_idx >= 2 || bit_idx >= 32))
|
{
|
prn_aggr_err(
|
"idx error 2: %d 0x%08X 0X%08X %d %d (%d %d) (%d %d)\n",
|
start_ssn, sn_bit_map[1], sn_bit_map[0], cur_ssn, ssn, word_idx, bit_idx, old_word_idx, old_bit_idx);
|
found = false;
|
}
|
else if (!found)
|
{
|
char strbuf[256];
|
_dump_BA_notification(strbuf, ba_notify_data);
|
prn_aggr_err("SN out-of-order: %d\n%s\n", start_ssn, strbuf);
|
}
|
}
|
else
|
{
|
found = _ssn_to_bit_idx(start_ssn, ssn, &word_idx, &bit_idx);
|
first_found = found;
|
if (found && (word_idx >= 2 || bit_idx >= 32))
|
prn_aggr_err("idx error 3: %d %d %d %d\n",
|
cur_ssn, ssn, word_idx, bit_idx);
|
}
|
}
|
_release_frames(ampdu_tid);
|
if (p_acked_num != NULL)
|
*p_acked_num = acked_num;
|
return aggr_num;
|
}
|
static void _flush_release_queue (struct ieee80211_hw *hw,
|
struct sk_buff_head *release_queue)
|
{
|
do
|
{
|
struct sk_buff *ampdu_skb = __skb_dequeue(release_queue);
|
struct ieee80211_tx_info *tx_info;
|
struct SKB_info_st *skb_info;
|
if (ampdu_skb == NULL)
|
break;
|
skb_info = (struct SKB_info_st *) (ampdu_skb->head);
|
skb_pull(ampdu_skb, AMPDU_DELIMITER_LEN);
|
tx_info = IEEE80211_SKB_CB(ampdu_skb);
|
ieee80211_tx_info_clear_status(tx_info);
|
tx_info->flags |= IEEE80211_TX_STAT_AMPDU;
|
if (skb_info->ampdu_tx_status == AMPDU_ST_DONE)
|
tx_info->flags |= IEEE80211_TX_STAT_ACK;
|
tx_info->status.ampdu_len = 1;
|
tx_info->status.ampdu_ack_len = 1;
|
#ifdef REPORT_TX_STATUS_DIRECTLY
|
dev_kfree_skb_any(ampdu_skb);
|
#else
|
#if defined(USE_THREAD_RX) && !defined(IRQ_PROC_TX_DATA)
|
ieee80211_tx_status(hw, ampdu_skb);
|
#else
|
ieee80211_tx_status_irqsafe(hw, ampdu_skb);
|
#endif
|
#endif
|
} while (1);
|
}
|
#if 0
|
static u16 _get_BA_notification_hits(u16 ssn,u32 *bit_map,struct ampdu_ba_notify_data *ba_notification,u16 *max_continue_hits,u16 *aggr_num)
|
{
|
int i;
|
u16 hits=0,continue_hits=0;
|
u64 bitMap=0;
|
if(bit_map)
|
bitMap = ((u64)bit_map[1]<<32) | (u64)(bit_map[0]);
|
for (i = 0; i < MAX_AGGR_NUM; i++)
|
{
|
if (ba_notification->seq_no[i] == (u16)(-1))
|
break;
|
if(ssn <= ba_notification->seq_no[i])
|
{
|
if((bitMap>>(ba_notification->seq_no[i]-ssn))&0x1)
|
{
|
hits++;
|
continue_hits++;
|
if(*max_continue_hits<=continue_hits)
|
*max_continue_hits = continue_hits;
|
}
|
else
|
{
|
continue_hits=0;
|
}
|
}
|
}
|
*aggr_num = i;
|
return hits;
|
}
|
#endif
|
void ssv6200_ampdu_no_BA_handler (struct ieee80211_hw *hw, struct sk_buff *skb)
|
{
|
struct cfg_host_event *host_event = (struct cfg_host_event *) skb->data;
|
struct ampdu_ba_notify_data *ba_notification =
|
(struct ampdu_ba_notify_data *) &host_event->dat[0];
|
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) (ba_notification + 1);
|
struct ssv_softc *sc = hw->priv;
|
struct ieee80211_sta *sta = ssv6xxx_find_sta_by_addr(sc, hdr->addr1);
|
u8 tidno = ieee80211_get_qos_ctl(hdr)[0] & IEEE80211_QOS_CTL_TID_MASK;
|
struct ssv_sta_priv_data *ssv_sta_priv;
|
char seq_str[256];
|
struct AMPDU_TID_st *ampdu_tid;
|
int i;
|
u16 aggr_num = 0;
|
struct firmware_rate_control_report_data *report_data;
|
if (sta == NULL)
|
{
|
prn_aggr_err(
|
"NO BA for %d to unmatched STA %02X-%02X-%02X-%02X-%02X-%02X: %s\n",
|
tidno, hdr->addr1[0], hdr->addr1[1], hdr->addr1[2], hdr->addr1[3], hdr->addr1[4], hdr->addr1[5], seq_str);
|
dev_kfree_skb_any(skb);
|
return;
|
}
|
ssv_sta_priv = (struct ssv_sta_priv_data *) sta->drv_priv;
|
_dump_BA_notification(seq_str, ba_notification);
|
prn_aggr_err(
|
"NO BA for %d to %02X-%02X-%02X-%02X-%02X-%02X: %s\n",
|
tidno, sta->addr[0], sta->addr[1], sta->addr[2], sta->addr[3], sta->addr[4], sta->addr[5], seq_str);
|
ampdu_tid = &ssv_sta_priv->ampdu_tid[tidno];
|
if (ampdu_tid->state != AMPDU_STATE_OPERATION)
|
{
|
dev_kfree_skb_any(skb);
|
return;
|
}
|
for (i = 0; i < MAX_AGGR_NUM; i++)
|
{
|
u32 ssn = ba_notification->seq_no[i];
|
struct sk_buff *skb;
|
u32 skb_ssn;
|
struct SKB_info_st *skb_info;
|
if (ssn >= (4096))
|
break;
|
aggr_num++;
|
skb = INDEX_PKT_BY_SSN(ampdu_tid, ssn);
|
skb_ssn = (skb == NULL) ? (-1) : ampdu_skb_ssn(skb);
|
if (skb_ssn != ssn)
|
{
|
prn_aggr_err("Unmatched SSN packet: %d - %d\n", ssn, skb_ssn);
|
continue;
|
}
|
skb_info = (struct SKB_info_st *) (skb->head);
|
if (skb_info->ampdu_tx_status == AMPDU_ST_SENT)
|
{
|
if (skb_info->mpdu_retry_counter < SSV_AMPDU_retry_counter_max)
|
{
|
if (skb_info->mpdu_retry_counter == 0)
|
{
|
struct ieee80211_hdr *skb_hdr = ampdu_skb_hdr(skb);
|
skb_hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_RETRY);
|
}
|
skb_info->ampdu_tx_status = AMPDU_ST_RETRY;
|
skb_info->mpdu_retry_counter++;
|
}
|
else
|
{
|
skb_info->ampdu_tx_status = AMPDU_ST_DROPPED;
|
prn_aggr_err("p %d\n", skb_ssn);
|
}
|
}
|
else
|
{
|
prn_aggr_err("S %d %d\n", skb_ssn, skb_info->ampdu_tx_status);
|
}
|
}
|
_release_frames(ampdu_tid);
|
host_event->h_event = SOC_EVT_RC_AMPDU_REPORT;
|
report_data =
|
(struct firmware_rate_control_report_data *) &host_event->dat[0];
|
report_data->ampdu_len = aggr_num;
|
report_data->ampdu_ack_len = 0;
|
report_data->wsid = ssv_sta_priv->sta_info->hw_wsid;
|
#if 0
|
printk("AMPDU report NO BA!!wsid[%d]didx[%d]F[%d]R[%d]S[%d]\n",report_data->wsid,report_data->data_rate,report_data->mpduFrames,report_data->mpduFrameRetry,report_data->mpduFrameSuccess);
|
#endif
|
skb_queue_tail(&sc->rc_report_queue, skb);
|
if (sc->rc_sample_sechedule == 0)
|
queue_work(sc->rc_sample_workqueue, &sc->rc_sample_work);
|
}
|
void ssv6200_ampdu_BA_handler (struct ieee80211_hw *hw, struct sk_buff *skb)
|
{
|
struct ssv_softc *sc = hw->priv;
|
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) (skb->data
|
+ SSV6XXX_RX_DESC_LEN);
|
AMPDU_BLOCKACK *BA_frame = (AMPDU_BLOCKACK *) hdr;
|
struct ieee80211_sta *sta;
|
struct ssv_sta_priv_data *ssv_sta_priv;
|
struct ampdu_ba_notify_data *ba_notification;
|
u32 ssn, aggr_num = 0, acked_num = 0;
|
u8 tid_no;
|
u32 sn_bit_map[2];
|
struct firmware_rate_control_report_data *report_data;
|
HDR_HostEvent *host_evt;
|
sta = ssv6xxx_find_sta_by_rx_skb(sc, skb);
|
if (sta == NULL)
|
{
|
if (skb->len > AMPDU_BA_FRAME_LEN)
|
{
|
char strbuf[256];
|
struct ampdu_ba_notify_data *ba_notification =
|
(struct ampdu_ba_notify_data *) (skb->data + skb->len
|
- sizeof(struct ampdu_ba_notify_data));
|
_dump_BA_notification(strbuf, ba_notification);
|
prn_aggr_err("BA from not connected STA (%02X-%02X-%02X-%02X-%02X-%02X) (%s)\n",
|
BA_frame->ta_addr[0], BA_frame->ta_addr[1], BA_frame->ta_addr[2],
|
BA_frame->ta_addr[3], BA_frame->ta_addr[4], BA_frame->ta_addr[5], strbuf);
|
}
|
dev_kfree_skb_any(skb);
|
return;
|
}
|
ssv_sta_priv = (struct ssv_sta_priv_data *) sta->drv_priv;
|
ssn = BA_frame->BA_ssn;
|
sn_bit_map[0] = BA_frame->BA_sn_bit_map[0];
|
sn_bit_map[1] = BA_frame->BA_sn_bit_map[1];
|
tid_no = BA_frame->tid_info;
|
ssv_sta_priv->ampdu_mib_total_BA_counter++;
|
if (ssv_sta_priv->ampdu_tid[tid_no].state == AMPDU_STATE_STOP)
|
{
|
prn_aggr_err("ssv6200_ampdu_BA_handler state == AMPDU_STATE_STOP.\n");
|
dev_kfree_skb_any(skb);
|
return;
|
}
|
ssv_sta_priv->ampdu_tid[tid_no].mib.ampdu_mib_BA_counter++;
|
if (skb->len <= AMPDU_BA_FRAME_LEN)
|
{
|
prn_aggr_err("b %d\n", ssn);
|
dev_kfree_skb_any(skb);
|
return;
|
}
|
ba_notification =
|
(struct ampdu_ba_notify_data *) (skb->data + skb->len
|
- sizeof(struct ampdu_ba_notify_data));
|
#if 0
|
if (1)
|
{
|
char strbuf[256];
|
_dump_BA_notification(strbuf, ba_notification);
|
prn_aggr_err("B %d %08X %08X: %s\n", ssn, sn_bit_map[0], sn_bit_map[1], strbuf);
|
}
|
#endif
|
aggr_num = _ba_map_walker(&(ssv_sta_priv->ampdu_tid[tid_no]), ssn,
|
sn_bit_map, ba_notification, &acked_num);
|
#ifdef CONFIG_SSV6XXX_DEBUGFS
|
if (ssv_sta_priv->ampdu_tid[tid_no].debugfs_dir)
|
{
|
struct sk_buff *dup_skb;
|
if (skb_queue_len(&ssv_sta_priv->ampdu_tid[tid_no].ba_q) > 24)
|
{
|
struct sk_buff *ba_skb = skb_dequeue(&ssv_sta_priv->ampdu_tid[tid_no].ba_q);
|
if (ba_skb)
|
dev_kfree_skb_any(ba_skb);
|
}
|
dup_skb = skb_clone(skb, GFP_ATOMIC);
|
if (dup_skb)
|
skb_queue_tail(&ssv_sta_priv->ampdu_tid[tid_no].ba_q, dup_skb);
|
}
|
#endif
|
skb_trim(skb, skb->len - sizeof(struct ampdu_ba_notify_data));
|
#if 0
|
total_debug_count++;
|
if (ba_notification_hits != ba_notification_aggr_num)
|
printk("rate[%d] firmware retry [%d] agg nums[%d] hits[%d] continue_hits[%d] \n",ba_notification.data_rate,ba_notification.retry_count,ba_notification_aggr_num,ba_notification_hits,ba_notification_continue_hits);
|
else
|
{
|
if (ba_notification.retry_count==0)
|
total_perfect_debug_count++;
|
else
|
total_perfect_debug_count_but_firmware_retry++;
|
}
|
if ((total_debug_count % 2000) == 0)
|
{
|
printk("Percentage %d/2000\n",total_perfect_debug_count);
|
printk("firmware retry [%d] no BA[%d]\n",total_perfect_debug_count_but_firmware_retry,no_ba_debug_count);
|
total_debug_count = 0;
|
total_perfect_debug_count_but_firmware_retry=0;
|
total_perfect_debug_count = 0;
|
no_ba_debug_count = 0;
|
}
|
#endif
|
host_evt = (HDR_HostEvent *) skb->data;
|
host_evt->h_event = SOC_EVT_RC_AMPDU_REPORT;
|
report_data =
|
(struct firmware_rate_control_report_data *) &host_evt->dat[0];
|
memcpy(report_data, ba_notification,
|
sizeof(struct firmware_rate_control_report_data));
|
report_data->ampdu_len = aggr_num;
|
report_data->ampdu_ack_len = acked_num;
|
#ifdef RATE_CONTROL_HT_PERCENTAGE_TRACE
|
if((acked_num) && (acked_num != aggr_num))
|
{
|
int i;
|
for (i = 0; i < SSV62XX_TX_MAX_RATES ; i++) {
|
if(report_data->rates[i].data_rate == -1)
|
break;
|
if(report_data->rates[i].count == 0) {
|
printk("*********************************\n");
|
printk(" Illegal HT report \n");
|
printk("*********************************\n");
|
}
|
printk(" i=[%d] rate[%d] count[%d]\n",i,report_data->rates[i].data_rate,report_data->rates[i].count);
|
}
|
printk("AMPDU percentage = %d%% \n",acked_num*100/aggr_num);
|
}
|
else if(acked_num == 0)
|
{
|
printk("AMPDU percentage = 0%% aggr_num=%d acked_num=%d\n",aggr_num,acked_num);
|
}
|
#endif
|
skb_queue_tail(&sc->rc_report_queue, skb);
|
if (sc->rc_sample_sechedule == 0)
|
queue_work(sc->rc_sample_workqueue, &sc->rc_sample_work);
|
}
|
static void _postprocess_BA (struct ssv_softc *sc, struct ssv_sta_info *sta_info, void *param)
|
{
|
int j;
|
struct ssv_sta_priv_data *ssv_sta_priv;
|
if ( (sta_info->sta == NULL)
|
|| ((sta_info->s_flags & STA_FLAG_VALID) == 0))
|
return;
|
ssv_sta_priv = (struct ssv_sta_priv_data *) sta_info->sta->drv_priv;
|
for (j = 0; j < WMM_TID_NUM; j++)
|
{
|
AMPDU_TID *ampdu_tid = &ssv_sta_priv->ampdu_tid[j];
|
if (ampdu_tid->state != AMPDU_STATE_OPERATION)
|
continue;
|
_collect_retry_frames(ampdu_tid);
|
ssv6200_ampdu_send_retry(sc->hw, ampdu_tid, &du_tid->retry_queue,
|
true);
|
_flush_early_ampdu_q(sc, ampdu_tid);
|
_flush_release_queue(sc->hw, &du_tid->release_queue);
|
}
|
}
|
void ssv6xxx_ampdu_postprocess_BA (struct ieee80211_hw *hw)
|
{
|
struct ssv_softc *sc = hw->priv;
|
ssv6xxx_foreach_sta(sc, _postprocess_BA, NULL);
|
}
|
static void ssv6200_hw_set_rx_ba_session (struct ssv_hw *sh, bool on, u8 *ta,
|
u16 tid, u16 ssn, u8 buf_size)
|
{
|
if (on)
|
{
|
u32 u32ta;
|
u32ta = 0;
|
u32ta |= (ta[0] & 0xff) << (8 * 0);
|
u32ta |= (ta[1] & 0xff) << (8 * 1);
|
u32ta |= (ta[2] & 0xff) << (8 * 2);
|
u32ta |= (ta[3] & 0xff) << (8 * 3);
|
SMAC_REG_WRITE(sh, ADR_BA_TA_0, u32ta);
|
u32ta = 0;
|
u32ta |= (ta[4] & 0xff) << (8 * 0);
|
u32ta |= (ta[5] & 0xff) << (8 * 1);
|
SMAC_REG_WRITE(sh, ADR_BA_TA_1, u32ta);
|
SMAC_REG_WRITE(sh, ADR_BA_TID, tid);
|
SMAC_REG_WRITE(sh, ADR_BA_ST_SEQ, ssn);
|
SMAC_REG_WRITE(sh, ADR_BA_SB0, 0);
|
SMAC_REG_WRITE(sh, ADR_BA_SB1, 0);
|
SMAC_REG_WRITE(sh, ADR_BA_CTRL, 0xb);
|
}
|
else
|
{
|
SMAC_REG_WRITE(sh, ADR_BA_CTRL, 0x0);
|
}
|
}
|
void ssv6xxx_set_ampdu_rx_add_work (struct work_struct *work)
|
{
|
struct ssv_softc
|
*sc = container_of(work, struct ssv_softc, set_ampdu_rx_add_work);
|
ssv6200_hw_set_rx_ba_session(sc->sh, true, sc->ba_ra_addr, sc->ba_tid,
|
sc->ba_ssn, 64);
|
}
|
void ssv6xxx_set_ampdu_rx_del_work (struct work_struct *work)
|
{
|
struct ssv_softc*sc = container_of(work, struct ssv_softc,
|
set_ampdu_rx_del_work);
|
u8 addr[6] = { 0 };
|
ssv6200_hw_set_rx_ba_session(sc->sh, false, addr, 0, 0, 0);
|
}
|
static void _reset_ampdu_mib (struct ssv_softc *sc, struct ssv_sta_info *sta_info, void *param)
|
{
|
struct ieee80211_sta *sta = sta_info->sta;
|
struct ssv_sta_priv_data *ssv_sta_priv;
|
int i;
|
ssv_sta_priv = (struct ssv_sta_priv_data *)sta->drv_priv;
|
for (i = 0; i < WMM_TID_NUM; i++)
|
{
|
ssv_sta_priv->ampdu_tid[i].ampdu_mib_reset = 1;
|
}
|
}
|
void ssv6xxx_ampdu_mib_reset (struct ieee80211_hw *hw)
|
{
|
struct ssv_softc *sc = hw->priv;
|
if (sc == NULL)
|
return;
|
ssv6xxx_foreach_sta(sc, _reset_ampdu_mib, NULL);
|
}
|
#ifdef CONFIG_SSV6XXX_DEBUGFS
|
ssize_t ampdu_tx_mib_dump (struct ssv_sta_priv_data *ssv_sta_priv,
|
char *mib_str, ssize_t length)
|
{
|
ssize_t buf_size = length;
|
ssize_t prt_size;
|
int j;
|
struct ssv_sta_info *ssv_sta = ssv_sta_priv->sta_info;
|
if (ssv_sta->sta == NULL)
|
{
|
prt_size = snprintf(mib_str, buf_size, "\n NULL STA.\n");
|
mib_str += prt_size;
|
buf_size -= prt_size;
|
goto mib_dump_exit;
|
}
|
for (j = 0; j < WMM_TID_NUM; j++)
|
{
|
int k;
|
struct AMPDU_TID_st *ampdu_tid = &ssv_sta_priv->ampdu_tid[j];
|
struct AMPDU_MIB_st *ampdu_mib = &du_tid->mib;
|
prt_size = snprintf(mib_str, buf_size, "\n WMM_TID %d@%d\n", j,
|
ampdu_tid->state);
|
mib_str += prt_size;
|
buf_size -= prt_size;
|
if (ampdu_tid->state != AMPDU_STATE_OPERATION)
|
continue;
|
prt_size = snprintf(mib_str, buf_size, " BA window size: %d\n",
|
ampdu_tid->ssv_baw_size);
|
mib_str += prt_size;
|
buf_size -= prt_size;
|
prt_size = snprintf(mib_str, buf_size, " BA window head: %d\n",
|
ampdu_tid->ssv_baw_head);
|
mib_str += prt_size;
|
buf_size -= prt_size;
|
prt_size = snprintf(mib_str, buf_size,
|
" Sending aggregated #: %d\n",
|
ampdu_tid->aggr_pkt_num);
|
mib_str += prt_size;
|
buf_size -= prt_size;
|
prt_size = snprintf(
|
mib_str, buf_size, " Waiting #: %d\n",
|
skb_queue_len(&du_tid->ampdu_skb_tx_queue));
|
mib_str += prt_size;
|
buf_size -= prt_size;
|
prt_size = snprintf(mib_str, buf_size, " Early aggregated %d\n",
|
ampdu_tid->early_aggr_skb_num);
|
mib_str += prt_size;
|
buf_size -= prt_size;
|
prt_size = snprintf(mib_str, buf_size,
|
" MPDU: %d\n",
|
ampdu_mib->ampdu_mib_mpdu_counter);
|
mib_str += prt_size;
|
buf_size -= prt_size;
|
prt_size = snprintf(mib_str, buf_size,
|
" Passed: %d\n", ampdu_mib->ampdu_mib_pass_counter);
|
mib_str += prt_size;
|
buf_size -= prt_size;
|
prt_size = snprintf(mib_str, buf_size,
|
" Retry: %d\n",
|
ampdu_mib->ampdu_mib_retry_counter);
|
mib_str += prt_size;
|
buf_size -= prt_size;
|
prt_size = snprintf(mib_str, buf_size,
|
" AMPDU: %d\n",
|
ampdu_mib->ampdu_mib_ampdu_counter);
|
mib_str += prt_size;
|
buf_size -= prt_size;
|
prt_size = snprintf(mib_str, buf_size,
|
" Retry AMPDU: %d\n",
|
ampdu_mib->ampdu_mib_aggr_retry_counter);
|
mib_str += prt_size;
|
buf_size -= prt_size;
|
prt_size = snprintf(mib_str, buf_size,
|
" BAR count: %d\n",
|
ampdu_mib->ampdu_mib_bar_counter);
|
mib_str += prt_size;
|
buf_size -= prt_size;
|
prt_size = snprintf(mib_str, buf_size,
|
" Discard count: %d\n",
|
ampdu_mib->ampdu_mib_discard_counter);
|
mib_str += prt_size;
|
buf_size -= prt_size;
|
prt_size = snprintf(mib_str, buf_size,
|
" BA count: %d\n",
|
ampdu_mib->ampdu_mib_BA_counter);
|
mib_str += prt_size;
|
buf_size -= prt_size;
|
prt_size = snprintf(mib_str, buf_size, " Total BA count: %d\n",
|
ssv_sta_priv->ampdu_mib_total_BA_counter);
|
mib_str += prt_size;
|
buf_size -= prt_size;
|
prt_size = snprintf(mib_str, buf_size, " Aggr # count:\n");
|
mib_str += prt_size;
|
buf_size -= prt_size;
|
for (k = 0; k <= SSV_AMPDU_aggr_num_max; k++)
|
{
|
prt_size = snprintf(mib_str, buf_size, " %d: %d\n", k,
|
ampdu_mib->ampdu_mib_dist[k]);
|
mib_str += prt_size;
|
buf_size -= prt_size;
|
}
|
}
|
mib_dump_exit:
|
return (length - buf_size);
|
}
|
static void _dump_ampdu_mib (struct ssv_softc *sc, struct ssv_sta_info *sta_info, void *param)
|
{
|
struct mib_dump_data *dump_data = (struct mib_dump_data *)param;
|
struct ieee80211_sta *sta;
|
struct ssv_sta_priv_data *ssv_sta_priv;
|
ssize_t buf_size;
|
ssize_t prt_size;
|
char *mib_str = dump_data->prt_buff;
|
if (param == NULL)
|
return;
|
buf_size = dump_data->buff_size - 1;
|
sta = sta_info->sta;
|
if ((sta == NULL) || ((sta_info->s_flags & STA_FLAG_VALID) == 0))
|
return;
|
prt_size = snprintf(mib_str, buf_size,
|
"STA: %02X-%02X-%02X-%02X-%02X-%02X:\n",
|
sta->addr[0], sta->addr[1], sta->addr[2],
|
sta->addr[3], sta->addr[4], sta->addr[5]);
|
mib_str += prt_size;
|
buf_size -= prt_size;
|
ssv_sta_priv = (struct ssv_sta_priv_data *) sta->drv_priv;
|
prt_size = ampdu_tx_mib_dump(ssv_sta_priv, mib_str, buf_size);
|
mib_str += prt_size;
|
buf_size -= prt_size;
|
dump_data->prt_len = (dump_data->buff_size - 1 - buf_size);
|
dump_data->prt_buff = mib_str;
|
dump_data->buff_size = buf_size;
|
}
|
ssize_t ssv6xxx_ampdu_mib_dump (struct ieee80211_hw *hw, char *mib_str,
|
ssize_t length)
|
{
|
struct ssv_softc *sc = hw->priv;
|
ssize_t buf_size = length - 1;
|
struct mib_dump_data dump_data = {mib_str, buf_size, 0};
|
if (sc == NULL)
|
return 0;
|
ssv6xxx_foreach_sta(sc, _dump_ampdu_mib, &dump_data);
|
return dump_data.prt_len;
|
}
|
#endif
|
struct sk_buff *_alloc_ampdu_skb (struct ssv_softc *sc, struct AMPDU_TID_st *ampdu_tid, u32 len)
|
{
|
unsigned char *payload_addr;
|
u32 headroom = sc->hw->extra_tx_headroom;
|
u32 offset;
|
u32 cur_max_ampdu_size = SSV_GET_MAX_AMPDU_SIZE(sc->sh);
|
u32 extra_room = sc->sh->tx_desc_len * 2 + 48;
|
u32 max_physical_len = (len && ((len + extra_room) < cur_max_ampdu_size))
|
? (len + extra_room)
|
: cur_max_ampdu_size;
|
u32 skb_len = max_physical_len + headroom + 3;
|
struct sk_buff *ampdu_skb = __dev_alloc_skb(skb_len, GFP_KERNEL);
|
struct ampdu_hdr_st *ampdu_hdr;
|
if (ampdu_skb == NULL)
|
{
|
dev_err(sc->dev, "AMPDU allocation of size %d(%d) failed\n", len, skb_len);
|
return NULL;
|
}
|
payload_addr = ampdu_skb->data + headroom - sc->sh->tx_desc_len;
|
offset = ((size_t) payload_addr) % 4U;
|
if (offset)
|
{
|
printk(KERN_ERR "Align AMPDU data %d\n", offset);
|
skb_reserve(ampdu_skb, headroom + 4 - offset);
|
}
|
else
|
skb_reserve(ampdu_skb, headroom);
|
ampdu_hdr = (struct ampdu_hdr_st *) ampdu_skb->head;
|
skb_queue_head_init(&du_hdr->mpdu_q);
|
ampdu_hdr->max_size = max_physical_len - extra_room;
|
ampdu_hdr->size = 0;
|
ampdu_hdr->ampdu_tid = ampdu_tid;
|
memset(ampdu_hdr->ssn, 0xFF, sizeof(ampdu_hdr->ssn));
|
ampdu_hdr->mpdu_num = 0;
|
return ampdu_skb;
|
}
|
bool _is_skb_q_empty (struct ssv_softc *sc, struct sk_buff *skb)
|
{
|
u32 ac = skb_get_queue_mapping(skb);
|
u32 hw_txqid = sc->tx.hw_txqid[ac];
|
return AMPDU_HCI_Q_EMPTY(sc->sh, hw_txqid);
|
}
|
static u32 _check_timeout (struct AMPDU_TID_st *ampdu_tid)
|
{
|
u16 ssn, head_ssn, end_ssn;
|
unsigned long check_jiffies = jiffies;
|
u32 has_retry = 0;
|
head_ssn = ampdu_tid->ssv_baw_head;
|
ssn = head_ssn;
|
if (ssn == SSV_ILLEGAL_SN)
|
return 0;
|
end_ssn = (head_ssn + SSV_AMPDU_BA_WINDOW_SIZE)% SSV_AMPDU_MAX_SSN;
|
do {
|
struct sk_buff *skb = INDEX_PKT_BY_SSN(ampdu_tid, ssn);
|
struct SKB_info_st *skb_info;
|
unsigned long cur_jiffies;
|
unsigned long timeout_jiffies;
|
u32 delta_ms;
|
if (skb == NULL)
|
break;
|
skb_info = (SKB_info *) (skb->head);
|
cur_jiffies = jiffies;
|
timeout_jiffies = skb_info->aggr_timestamp + msecs_to_jiffies(BA_WAIT_TIMEOUT);
|
if ( (skb_info->ampdu_tx_status != AMPDU_ST_SENT)
|
|| time_before(cur_jiffies, timeout_jiffies))
|
break;
|
delta_ms = jiffies_to_msecs(cur_jiffies - skb_info->aggr_timestamp);
|
prn_aggr_err("rt S%d-T%d-%d (%u)\n",
|
((struct ssv_sta_priv_data *)skb_info->sta->drv_priv)->sta_idx,
|
ampdu_tid->tidno, ssn,
|
delta_ms);
|
if (delta_ms > 1000)
|
{
|
prn_aggr_err("Last checktime %lu - %lu = %u\n",
|
check_jiffies, ampdu_tid->timestamp,
|
jiffies_to_msecs(check_jiffies - ampdu_tid->timestamp));
|
}
|
has_retry += _mark_skb_retry(skb_info, skb);
|
INC_PKT_SN(ssn);
|
} while (ssn != end_ssn);
|
ampdu_tid->timestamp = check_jiffies;
|
return has_retry;
|
}
|
void ssv6xxx_ampdu_check_timeout (struct ieee80211_hw *hw)
|
{
|
struct ssv_softc *sc = hw->priv;
|
struct AMPDU_TID_st *cur_AMPDU_TID;
|
if (!list_empty(&sc->tx.ampdu_tx_que))
|
{
|
list_for_each_entry_rcu(cur_AMPDU_TID, &sc->tx.ampdu_tx_que, list)
|
{
|
u32 has_retry;
|
if (cur_AMPDU_TID->state != AMPDU_STATE_OPERATION)
|
continue;
|
has_retry = _check_timeout(cur_AMPDU_TID);
|
if (has_retry)
|
{
|
_collect_retry_frames(cur_AMPDU_TID);
|
ssv6200_ampdu_send_retry(sc->hw, cur_AMPDU_TID, &cur_AMPDU_TID->retry_queue,
|
true);
|
}
|
}
|
}
|
}
|
void ssv6xxx_ampdu_sent(struct ieee80211_hw *hw, struct sk_buff *ampdu)
|
{
|
struct ssv_softc *sc = hw->priv;
|
struct ampdu_hdr_st *ampdu_hdr = (struct ampdu_hdr_st *) ampdu->head;
|
struct sk_buff *mpdu;
|
unsigned long cur_jiffies = jiffies;
|
int i;
|
SKB_info *mpdu_skb_info;
|
u16 ssn;
|
if (ampdu_hdr->ampdu_tid->state != AMPDU_STATE_OPERATION)
|
return;
|
spin_lock_bh(&du_hdr->ampdu_tid->pkt_array_lock);
|
for (i = 0; i < ampdu_hdr->mpdu_num; i++)
|
{
|
ssn = ampdu_hdr->ssn[i];
|
mpdu = INDEX_PKT_BY_SSN(ampdu_hdr->ampdu_tid, ssn);
|
if (mpdu == NULL)
|
{
|
dev_err(sc->dev, "T%d-%d is a NULL MPDU.\n",
|
ampdu_hdr->ampdu_tid->tidno, ssn);
|
continue;
|
}
|
if (ampdu_skb_ssn(mpdu) != ssn)
|
{
|
dev_err(sc->dev, "T%d-%d does not match %d MPDU.\n",
|
ampdu_hdr->ampdu_tid->tidno, ssn, ampdu_skb_ssn(mpdu));
|
continue;
|
}
|
mpdu_skb_info = (SKB_info *) (mpdu->head);
|
mpdu_skb_info->aggr_timestamp = cur_jiffies;
|
mpdu_skb_info->ampdu_tx_status = AMPDU_ST_SENT;
|
}
|
spin_unlock_bh(&du_hdr->ampdu_tid->pkt_array_lock);
|
}
|