/* * 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 . */ #include #include #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); }