2015-07-20 19:43:16

by Grumbach, Emmanuel

[permalink] [raw]
Subject: [RFC 00/10] Add TSO and Tx A-MSDU for iwlwifi

This patch series is really just an RFC: it won't work as
is. I just wanted to share early what we have in the pipe.
We enable TSO to get a lot of data at once to build A-MSDUs.
Our hardware doesn't have (yet) TCP CSUM offload, so we do
it manually. TSO won't be enabled on hardware that don't
support CSUM offload in release code, computing TCP CSUM
in the driver is just a way to start coding the flows.
This is why the "CSUM offload" implementation in the driver
in so bad in terms of efficiency. I preferred to have the
flows as close as they will be when the hardware will be
able to the CSUM than to try to seek efficiency.
The hardware that will have CSUM offload will still require
the driver to split the skb in software including the
IP / TCP header copy and update etc...

I am pretty much a newbie in all the netstack stuff, so
I am certain that people out there will have remarks which
I will be more than happy to hear.
I know that I can use more skb APIs instead of reinventing
the wheel, OTOH, for most of the cases, the code was hacky
enough to make it easier to touch the skb members than to
use the API functions.
Of course, suggestions are most welcome.

I ran quite a bit of testing on that code. I could run
long iperf sessions with lots of A-MSDUs in the air.
Corner cases are still failing. E.g. if you put a very
small MSS so that the number of DMA descriptors for an
A-MSDU starts to be the limit, then at some point, I blow
up.

Emmanuel Grumbach (8):
mac80211: allow the driver to adverise A-MSDU within A-MPDU Rx support
mac80211: allow to transmit A-MSDU within A-MPDU
mac80211: limit the A-MSDU based on the Extended Capabilities element
iwlwifi: mvm: don't adverise SUPPORTS_CLONED_SKBS anymore
iwlwifi: mvm: support TSO
iwlwifi: mvm: add support for A-MSDU Tx
iwlwifi: mvm: allow to configure the maximal size of an A-MSDU
iwlwifi: don't send A-MSDU when not allowed by peer

Johannes Berg (2):
iwlwifi: mvm: pretend to have CSUM OFFLOAD
iwlwifi: mvm: move TX PN assignment for CCMP to the driver

drivers/net/wireless/iwlwifi/dvm/mac80211.c | 2 +-
drivers/net/wireless/iwlwifi/mvm/constants.h | 1 +
drivers/net/wireless/iwlwifi/mvm/debugfs.c | 19 +
drivers/net/wireless/iwlwifi/mvm/mac80211.c | 17 +-
drivers/net/wireless/iwlwifi/mvm/mvm.h | 16 +-
drivers/net/wireless/iwlwifi/mvm/sta.c | 6 +-
drivers/net/wireless/iwlwifi/mvm/sta.h | 6 +-
drivers/net/wireless/iwlwifi/mvm/tdls.c | 12 +-
drivers/net/wireless/iwlwifi/mvm/tx.c | 666 ++++++++++++++++++++++++++-
drivers/net/wireless/mac80211_hwsim.c | 2 +-
include/linux/ieee80211.h | 9 +
include/net/mac80211.h | 14 +-
net/mac80211/agg-rx.c | 8 +-
net/mac80211/agg-tx.c | 15 +-
net/mac80211/cfg.c | 23 +
net/mac80211/debugfs.c | 1 +
net/mac80211/driver-ops.h | 7 +-
net/mac80211/sta_info.h | 2 +
net/mac80211/trace.h | 10 +-
net/mac80211/vht.c | 16 +
20 files changed, 795 insertions(+), 57 deletions(-)

--
2.1.4



2015-07-20 19:43:16

by Grumbach, Emmanuel

[permalink] [raw]
Subject: [RFC 02/10] mac80211: allow to transmit A-MSDU within A-MPDU

Advertise the capability to send A-MSDU within A-MPDU
in the AddBA request sent by mac80211. Let the driver
know about the peer's capabilities.

Signed-off-by: Emmanuel Grumbach <[email protected]>
---
drivers/net/wireless/iwlwifi/dvm/mac80211.c | 2 +-
drivers/net/wireless/iwlwifi/mvm/mac80211.c | 2 +-
drivers/net/wireless/mac80211_hwsim.c | 2 +-
include/linux/ieee80211.h | 1 +
include/net/mac80211.h | 5 ++++-
net/mac80211/agg-rx.c | 4 ++--
net/mac80211/agg-tx.c | 15 ++++++++++-----
net/mac80211/driver-ops.h | 7 ++++---
net/mac80211/sta_info.h | 2 ++
net/mac80211/trace.h | 10 ++++++----
10 files changed, 32 insertions(+), 18 deletions(-)

diff --git a/drivers/net/wireless/iwlwifi/dvm/mac80211.c b/drivers/net/wireless/iwlwifi/dvm/mac80211.c
index f603fb3..8ccfea6 100644
--- a/drivers/net/wireless/iwlwifi/dvm/mac80211.c
+++ b/drivers/net/wireless/iwlwifi/dvm/mac80211.c
@@ -731,7 +731,7 @@ static int iwlagn_mac_ampdu_action(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
enum ieee80211_ampdu_mlme_action action,
struct ieee80211_sta *sta, u16 tid, u16 *ssn,
- u8 buf_size)
+ u8 buf_size, bool amsdu)
{
struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw);
int ret = -EINVAL;
diff --git a/drivers/net/wireless/iwlwifi/mvm/mac80211.c b/drivers/net/wireless/iwlwifi/mvm/mac80211.c
index 45e9913..f5c508a 100644
--- a/drivers/net/wireless/iwlwifi/mvm/mac80211.c
+++ b/drivers/net/wireless/iwlwifi/mvm/mac80211.c
@@ -815,7 +815,7 @@ static int iwl_mvm_mac_ampdu_action(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
enum ieee80211_ampdu_mlme_action action,
struct ieee80211_sta *sta, u16 tid,
- u16 *ssn, u8 buf_size)
+ u16 *ssn, u8 buf_size, bool amsdu)
{
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
int ret;
diff --git a/drivers/net/wireless/mac80211_hwsim.c b/drivers/net/wireless/mac80211_hwsim.c
index 99e873d..d0b0fdf 100644
--- a/drivers/net/wireless/mac80211_hwsim.c
+++ b/drivers/net/wireless/mac80211_hwsim.c
@@ -1819,7 +1819,7 @@ static int mac80211_hwsim_ampdu_action(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
enum ieee80211_ampdu_mlme_action action,
struct ieee80211_sta *sta, u16 tid, u16 *ssn,
- u8 buf_size)
+ u8 buf_size, bool amsdu)
{
switch (action) {
case IEEE80211_AMPDU_TX_START:
diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h
index b9c7897..2226496 100644
--- a/include/linux/ieee80211.h
+++ b/include/linux/ieee80211.h
@@ -1379,6 +1379,7 @@ struct ieee80211_ht_operation {


/* block-ack parameters */
+#define IEEE80211_ADDBA_PARAM_AMSDU_MASK 0x0001
#define IEEE80211_ADDBA_PARAM_POLICY_MASK 0x0002
#define IEEE80211_ADDBA_PARAM_TID_MASK 0x003C
#define IEEE80211_ADDBA_PARAM_BUF_SIZE_MASK 0xFFC0
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 95ea827..280419a 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -3013,6 +3013,9 @@ enum ieee80211_reconfig_type {
* buffer size of 8. Correct ways to retransmit #1 would be:
* - TX: 1 or 18 or 81
* Even "189" would be wrong since 1 could be lost again.
+ * The @amsdu parameter is valid when the action is set to
+ * %IEEE80211_AMPDU_TX_OPERATIONAL and indicates the peer's ability
+ * to receive A-MSDU within A-MPDU.
*
* Returns a negative error code on failure.
* The callback can sleep.
@@ -3346,7 +3349,7 @@ struct ieee80211_ops {
struct ieee80211_vif *vif,
enum ieee80211_ampdu_mlme_action action,
struct ieee80211_sta *sta, u16 tid, u16 *ssn,
- u8 buf_size);
+ u8 buf_size, bool amsdu);
int (*get_survey)(struct ieee80211_hw *hw, int idx,
struct survey_info *survey);
void (*rfkill_poll)(struct ieee80211_hw *hw);
diff --git a/net/mac80211/agg-rx.c b/net/mac80211/agg-rx.c
index 78de79b..10ad4ac 100644
--- a/net/mac80211/agg-rx.c
+++ b/net/mac80211/agg-rx.c
@@ -79,7 +79,7 @@ void ___ieee80211_stop_rx_ba_session(struct sta_info *sta, u16 tid,
(int)reason);

if (drv_ampdu_action(local, sta->sdata, IEEE80211_AMPDU_RX_STOP,
- &sta->sta, tid, NULL, 0))
+ &sta->sta, tid, NULL, 0, false))
sdata_info(sta->sdata,
"HW problem - can not stop rx aggregation for %pM tid %d\n",
sta->sta.addr, tid);
@@ -323,7 +323,7 @@ void __ieee80211_start_rx_ba_session(struct sta_info *sta,
__skb_queue_head_init(&tid_agg_rx->reorder_buf[i]);

ret = drv_ampdu_action(local, sta->sdata, IEEE80211_AMPDU_RX_START,
- &sta->sta, tid, &start_seq_num, 0);
+ &sta->sta, tid, &start_seq_num, 0, false);
ht_dbg(sta->sdata, "Rx A-MPDU request on %pM tid %d result %d\n",
sta->sta.addr, tid, ret);
if (ret) {
diff --git a/net/mac80211/agg-tx.c b/net/mac80211/agg-tx.c
index c8ba2e7..a758eb84 100644
--- a/net/mac80211/agg-tx.c
+++ b/net/mac80211/agg-tx.c
@@ -97,7 +97,8 @@ static void ieee80211_send_addba_request(struct ieee80211_sub_if_data *sdata,
mgmt->u.action.u.addba_req.action_code = WLAN_ACTION_ADDBA_REQ;

mgmt->u.action.u.addba_req.dialog_token = dialog_token;
- capab = (u16)(1 << 1); /* bit 1 aggregation policy */
+ capab = (u16)(1 << 0); /* bit 0 A-MSDU support */
+ capab |= (u16)(1 << 1); /* bit 1 aggregation policy */
capab |= (u16)(tid << 2); /* bit 5:2 TID number */
capab |= (u16)(agg_size << 6); /* bit 15:6 max size of aggergation */

@@ -331,7 +332,7 @@ int ___ieee80211_stop_tx_ba_session(struct sta_info *sta, u16 tid,
return -EALREADY;
ret = drv_ampdu_action(local, sta->sdata,
IEEE80211_AMPDU_TX_STOP_FLUSH_CONT,
- &sta->sta, tid, NULL, 0);
+ &sta->sta, tid, NULL, 0, false);
WARN_ON_ONCE(ret);
return 0;
}
@@ -381,7 +382,7 @@ int ___ieee80211_stop_tx_ba_session(struct sta_info *sta, u16 tid,
tid_tx->tx_stop = reason == AGG_STOP_LOCAL_REQUEST;

ret = drv_ampdu_action(local, sta->sdata, action,
- &sta->sta, tid, NULL, 0);
+ &sta->sta, tid, NULL, 0, false);

/* HW shall not deny going back to legacy */
if (WARN_ON(ret)) {
@@ -469,7 +470,7 @@ void ieee80211_tx_ba_session_handle_start(struct sta_info *sta, int tid)
start_seq_num = sta->tid_seq[tid] >> 4;

ret = drv_ampdu_action(local, sdata, IEEE80211_AMPDU_TX_START,
- &sta->sta, tid, &start_seq_num, 0);
+ &sta->sta, tid, &start_seq_num, 0, false);
if (ret) {
ht_dbg(sdata,
"BA request denied - HW unavailable for %pM tid %d\n",
@@ -693,7 +694,8 @@ static void ieee80211_agg_tx_operational(struct ieee80211_local *local,

drv_ampdu_action(local, sta->sdata,
IEEE80211_AMPDU_TX_OPERATIONAL,
- &sta->sta, tid, NULL, tid_tx->buf_size);
+ &sta->sta, tid, NULL, tid_tx->buf_size,
+ tid_tx->amsdu);

/*
* synchronize with TX path, while splicing the TX path
@@ -918,8 +920,10 @@ void ieee80211_process_addba_resp(struct ieee80211_local *local,
struct tid_ampdu_tx *tid_tx;
u16 capab, tid;
u8 buf_size;
+ bool amsdu;

capab = le16_to_cpu(mgmt->u.action.u.addba_resp.capab);
+ amsdu = capab & IEEE80211_ADDBA_PARAM_AMSDU_MASK;
tid = (capab & IEEE80211_ADDBA_PARAM_TID_MASK) >> 2;
buf_size = (capab & IEEE80211_ADDBA_PARAM_BUF_SIZE_MASK) >> 6;

@@ -968,6 +972,7 @@ void ieee80211_process_addba_resp(struct ieee80211_local *local,
}

tid_tx->buf_size = buf_size;
+ tid_tx->amsdu = amsdu;

if (test_bit(HT_AGG_STATE_DRV_READY, &tid_tx->state))
ieee80211_agg_tx_operational(local, sta, tid);
diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h
index 32a2e70..68c4b7a 100644
--- a/net/mac80211/driver-ops.h
+++ b/net/mac80211/driver-ops.h
@@ -743,7 +743,7 @@ static inline int drv_ampdu_action(struct ieee80211_local *local,
struct ieee80211_sub_if_data *sdata,
enum ieee80211_ampdu_mlme_action action,
struct ieee80211_sta *sta, u16 tid,
- u16 *ssn, u8 buf_size)
+ u16 *ssn, u8 buf_size, bool amsdu)
{
int ret = -EOPNOTSUPP;

@@ -753,11 +753,12 @@ static inline int drv_ampdu_action(struct ieee80211_local *local,
if (!check_sdata_in_driver(sdata))
return -EIO;

- trace_drv_ampdu_action(local, sdata, action, sta, tid, ssn, buf_size);
+ trace_drv_ampdu_action(local, sdata, action, sta, tid,
+ ssn, buf_size, amsdu);

if (local->ops->ampdu_action)
ret = local->ops->ampdu_action(&local->hw, &sdata->vif, action,
- sta, tid, ssn, buf_size);
+ sta, tid, ssn, buf_size, amsdu);

trace_drv_return_int(local, ret);

diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index 226f8ca4..fde5d9f 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -130,6 +130,7 @@ enum ieee80211_agg_stop_reason {
* @buf_size: reorder buffer size at receiver
* @failed_bar_ssn: ssn of the last failed BAR tx attempt
* @bar_pending: BAR needs to be re-sent
+ * @amsdu: support A-MSDU withing A-MDPU
*
* This structure's lifetime is managed by RCU, assignments to
* the array holding it must hold the aggregation mutex.
@@ -155,6 +156,7 @@ struct tid_ampdu_tx {

u16 failed_bar_ssn;
bool bar_pending;
+ bool amsdu;
};

/**
diff --git a/net/mac80211/trace.h b/net/mac80211/trace.h
index 6f14591..8244f16 100644
--- a/net/mac80211/trace.h
+++ b/net/mac80211/trace.h
@@ -944,9 +944,9 @@ TRACE_EVENT(drv_ampdu_action,
struct ieee80211_sub_if_data *sdata,
enum ieee80211_ampdu_mlme_action action,
struct ieee80211_sta *sta, u16 tid,
- u16 *ssn, u8 buf_size),
+ u16 *ssn, u8 buf_size, bool amsdu),

- TP_ARGS(local, sdata, action, sta, tid, ssn, buf_size),
+ TP_ARGS(local, sdata, action, sta, tid, ssn, buf_size, amsdu),

TP_STRUCT__entry(
LOCAL_ENTRY
@@ -955,6 +955,7 @@ TRACE_EVENT(drv_ampdu_action,
__field(u16, tid)
__field(u16, ssn)
__field(u8, buf_size)
+ __field(bool, amsdu)
VIF_ENTRY
),

@@ -966,12 +967,13 @@ TRACE_EVENT(drv_ampdu_action,
__entry->tid = tid;
__entry->ssn = ssn ? *ssn : 0;
__entry->buf_size = buf_size;
+ __entry->amsdu = amsdu;
),

TP_printk(
- LOCAL_PR_FMT VIF_PR_FMT STA_PR_FMT " action:%d tid:%d buf:%d",
+ LOCAL_PR_FMT VIF_PR_FMT STA_PR_FMT " action:%d tid:%d buf:%d amsdu:%d",
LOCAL_PR_ARG, VIF_PR_ARG, STA_PR_ARG, __entry->action,
- __entry->tid, __entry->buf_size
+ __entry->tid, __entry->buf_size, __entry->amsdu
)
);

--
2.1.4


2015-07-20 19:43:30

by Grumbach, Emmanuel

[permalink] [raw]
Subject: [RFC 08/10] iwlwifi: mvm: add support for A-MSDU Tx

Now that LSO is working, we can have a big buffer at once
in the driver. Allow to build an A-MSDU out of all this
data. Using A-MSDU will allow better throughput.

Signed-off-by: Emmanuel Grumbach <[email protected]>
---
drivers/net/wireless/iwlwifi/mvm/mac80211.c | 1 +
drivers/net/wireless/iwlwifi/mvm/tx.c | 105 +++++++++++++++++++++++++---
2 files changed, 98 insertions(+), 8 deletions(-)

diff --git a/drivers/net/wireless/iwlwifi/mvm/mac80211.c b/drivers/net/wireless/iwlwifi/mvm/mac80211.c
index def314e..0334ad4 100644
--- a/drivers/net/wireless/iwlwifi/mvm/mac80211.c
+++ b/drivers/net/wireless/iwlwifi/mvm/mac80211.c
@@ -435,6 +435,7 @@ int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm)
ieee80211_hw_set(hw, CONNECTION_MONITOR);
ieee80211_hw_set(hw, CHANCTX_STA_CSA);
ieee80211_hw_set(hw, SUPPORT_FAST_XMIT);
+ ieee80211_hw_set(hw, SUPPORTS_AMSDU_IN_AMPDU);

if (IWL_MVM_SW_CSUM_OFFLOAD)
hw->netdev_features |= NETIF_F_IP_CSUM | NETIF_F_TSO |
diff --git a/drivers/net/wireless/iwlwifi/mvm/tx.c b/drivers/net/wireless/iwlwifi/mvm/tx.c
index 27b8491..3f89d71 100644
--- a/drivers/net/wireless/iwlwifi/mvm/tx.c
+++ b/drivers/net/wireless/iwlwifi/mvm/tx.c
@@ -476,8 +476,10 @@ static void iwl_update_ip_tcph(void *iph, struct tcphdr *tcph, bool ipv6,
* @ieee80211_hdr *hdr: Points to the WiFi header.
* @gso_nr_frags: The number of frags in the original GSO skb.
* @wifi_hdr_iv_len: The length of the WiFi header including IV.
+ * @amsdu_pad: Number of bytes for the A-MSDU subframe
* @tcp_fin: True if TCP_FIN is set in the original GSO skb.
* @tcp_push: True if TCP_PSH is set in the original GSO skb.
+ * @amsdu: True if we are building an A-MSDU
*/
struct iwl_lso_splitter {
unsigned int linear_payload_len;
@@ -493,8 +495,10 @@ struct iwl_lso_splitter {
struct ieee80211_hdr *hdr;
u8 gso_nr_frags;
u8 wifi_hdr_iv_len;
+ u8 amsdu_pad;
bool tcp_fin;
bool tcp_push;
+ bool amsdu;
};

/*
@@ -616,9 +620,10 @@ static int iwl_add_msdu(struct iwl_mvm *mvm, struct sk_buff *skb_gso,
struct iwl_lso_splitter *p)
{

- unsigned int tcp_seg_sz, snap_ip_tcp_len, copy_sz = 0;
+ unsigned int tcp_seg_sz, snap_ip_tcp_len, subframe_sz, copy_sz = 0;
bool ipv6 = p->si->gso_type & SKB_GSO_TCPV6;
u8 *start_hdr = *hdr_page_pos;
+ __be16 *length = NULL;
struct tcphdr *tcph;
struct iphdr *iph;

@@ -626,10 +631,32 @@ static int iwl_add_msdu(struct iwl_mvm *mvm, struct sk_buff *skb_gso,
8 + skb_network_header_len(skb_gso) + tcp_hdrlen(skb_gso);
copy_sz = min_t(unsigned int, p->linear_payload_len, p->mss);

- *hdr_page_pos = get_page_pos(hdr_page, *hdr_page_pos, snap_ip_tcp_len + copy_sz);
+ *hdr_page_pos =
+ get_page_pos(hdr_page, *hdr_page_pos,
+ snap_ip_tcp_len + copy_sz +
+ sizeof(struct ethhdr) + 4);
if (!*hdr_page_pos)
return -1;

+ subframe_sz = snap_ip_tcp_len;
+
+ if (p->amsdu) {
+ memset(*hdr_page_pos, 0, p->amsdu_pad);
+ *hdr_page_pos += p->amsdu_pad;
+
+ /* TODO: AP MODE */
+
+ memcpy(*hdr_page_pos, p->hdr->addr3, ETH_ALEN);
+ *hdr_page_pos += ETH_ALEN;
+
+ memcpy(*hdr_page_pos, p->hdr->addr2, ETH_ALEN);
+ *hdr_page_pos += ETH_ALEN;
+
+ length = (void *)*hdr_page_pos;
+ *hdr_page_pos += sizeof(*length);
+ subframe_sz += sizeof(struct ethhdr);
+ }
+
/*
* Copy SNAP / IP / TCP headers from the original GSO skb to the
* header page.
@@ -671,6 +698,11 @@ static int iwl_add_msdu(struct iwl_mvm *mvm, struct sk_buff *skb_gso,

/* .. and now add the payload coming from the frags. */
tcp_seg_sz = iwl_add_tcp_segment(mvm, skb_gso, skb, p, copy_sz);
+ subframe_sz += tcp_seg_sz;
+ p->amsdu_pad = (4 - (subframe_sz)) & 0x3;
+
+ if (length)
+ *length = cpu_to_be16(subframe_sz - sizeof(struct ethhdr));

iwl_update_ip_tcph(iph, tcph, ipv6, tcp_seg_sz,
p->gso_payload_pos - tcp_seg_sz, ip_id);
@@ -691,7 +723,7 @@ static int iwl_add_msdu(struct iwl_mvm *mvm, struct sk_buff *skb_gso,
tcp_seg_sz, tcp_hdrlen(skb_gso));
skb->ip_summed = CHECKSUM_COMPLETE;

- return 0;
+ return subframe_sz;
}

static int iwl_mvm_tx_tso(struct iwl_mvm *mvm, struct sk_buff* skb_gso,
@@ -847,9 +879,12 @@ static int iwl_mvm_tx_tso(struct iwl_mvm *mvm, struct sk_buff* skb_gso,
if (keyconf && keyconf->cipher == WLAN_CIPHER_SUITE_CCMP)
s.wifi_hdr_iv_len += IEEE80211_CCMP_HDR_LEN;

+ s.amsdu = true;
while (s.gso_payload_pos < s.gso_payload_len) {
struct sk_buff *skb = dev_alloc_skb(s.wifi_hdr_iv_len);
- int ret;
+ unsigned int ip_tcp_snap_hdrlen, amsdu_sz, max_amsdu_sz;
+ u8 *qc;
+
s.frag_in_mpdu = 0;

if (WARN_ON(s.gso_frag_num >= s.gso_nr_frags))
@@ -864,11 +899,65 @@ static int iwl_mvm_tx_tso(struct iwl_mvm *mvm, struct sk_buff* skb_gso,
memcpy(skb_put(skb, s.wifi_hdr_iv_len),
wifi_hdr, s.wifi_hdr_iv_len);

- ret = iwl_add_msdu(mvm, skb_gso, skb, &hdr_page,
- &hdr_page_pos, i++, &s);
- if (ret < 0) {
- skb_queue_purge(mpdus_skb);
+ /* No need to have an AMSDU if we have at most mss bytes */
+ if (s.gso_payload_len - s.gso_payload_pos <= s.mss)
+ s.amsdu = false;
+
+ max_amsdu_sz = sta->max_amsdu_sz;
+
+ if (!max_amsdu_sz || !s.amsdu) {
+ int l;
+
+ s.amsdu = false;
+ l = iwl_add_msdu(mvm, skb_gso, skb, &hdr_page,
+ &hdr_page_pos, i++, &s);
+ if (l < 0) {
+ skb_queue_purge(mpdus_skb);
+ goto err;
+ }
+
+ __skb_queue_tail(mpdus_skb, skb);
+ continue;
+ }
+
+ if (WARN_ON_ONCE(max_amsdu_sz < s.mss))
goto err;
+
+ qc = ieee80211_get_qos_ctl((void *)skb->data);
+ *qc |= IEEE80211_QOS_CTL_A_MSDU_PRESENT;
+
+ amsdu_sz = 0;
+ s.amsdu_pad = 0;
+ ip_tcp_snap_hdrlen =
+ 8 + ip_hdrlen(skb_gso) + tcp_hdrlen(skb_gso);
+
+ i = 0;
+
+ /*
+ * Make sure we have enough room for
+ * ethernet header, SNAP header, IP header, TCP header and MSS.
+ * Make sure we don't add more MSDUs than allowed
+ */
+ while (amsdu_sz + sizeof(struct ethhdr) + s.mss +
+ ip_tcp_snap_hdrlen < max_amsdu_sz &&
+ (!sta->max_msdus || i < sta->max_msdus) &&
+ s.gso_payload_pos < s.gso_payload_len) {
+ unsigned int l;
+
+
+ if (s.frag_in_mpdu >= mvm->trans->max_skb_frags - 1) {
+ IWL_ERR(mvm, "2 frags need for each MSDU\n");
+ break;
+ }
+
+ l = iwl_add_msdu(mvm, skb_gso, skb, &hdr_page,
+ &hdr_page_pos, i++, &s);
+ if (l < 0) {
+ skb_queue_purge(mpdus_skb);
+ goto err;
+ }
+
+ amsdu_sz += l;
}

__skb_queue_tail(mpdus_skb, skb);
--
2.1.4


2015-07-28 11:35:11

by Grumbach, Emmanuel

[permalink] [raw]
Subject: RE: [RFC 02/10] mac80211: allow to transmit A-MSDU within A-MPDU

PiANCj4gT24gVHVlLCBKdWwgMjEsIDIwMTUgYXQgMToxMiBBTSwgRW1tYW51ZWwgR3J1bWJhY2gN
Cj4gPGVtbWFudWVsLmdydW1iYWNoQGludGVsLmNvbT4gd3JvdGU6DQo+ID4NCj4gPiBkaWZmIC0t
Z2l0IGEvbmV0L21hYzgwMjExL2FnZy10eC5jIGIvbmV0L21hYzgwMjExL2FnZy10eC5jIGluZGV4
DQo+ID4gYzhiYTJlNy4uYTc1OGViODQgMTAwNjQ0DQo+ID4gLS0tIGEvbmV0L21hYzgwMjExL2Fn
Zy10eC5jDQo+ID4gKysrIGIvbmV0L21hYzgwMjExL2FnZy10eC5jDQo+ID4gQEAgLTk3LDcgKzk3
LDggQEAgc3RhdGljIHZvaWQgaWVlZTgwMjExX3NlbmRfYWRkYmFfcmVxdWVzdChzdHJ1Y3QNCj4g
aWVlZTgwMjExX3N1Yl9pZl9kYXRhICpzZGF0YSwNCj4gPiAgICAgICAgIG1nbXQtPnUuYWN0aW9u
LnUuYWRkYmFfcmVxLmFjdGlvbl9jb2RlID0NCj4gPiBXTEFOX0FDVElPTl9BRERCQV9SRVE7DQo+
ID4NCj4gPiAgICAgICAgIG1nbXQtPnUuYWN0aW9uLnUuYWRkYmFfcmVxLmRpYWxvZ190b2tlbiA9
IGRpYWxvZ190b2tlbjsNCj4gPiAtICAgICAgIGNhcGFiID0gKHUxNikoMSA8PCAxKTsgICAgICAg
ICAgLyogYml0IDEgYWdncmVnYXRpb24gcG9saWN5ICovDQo+ID4gKyAgICAgICBjYXBhYiA9ICh1
MTYpKDEgPDwgMCk7ICAgICAgICAgIC8qIGJpdCAwIEEtTVNEVSBzdXBwb3J0ICovDQo+IA0KPiBT
aG91bGRuJ3QgdGhpcyBiZSBiYXNlZCBvbiBIVyBjYXBhYmlsaXR5PyBXZSBjYW4gYWRkIGNvdXBs
ZSBvZiBtb3JlIF9IV18NCj4gZmxhZ3MgZm9yIFRYIGFuZCBSWCBhbmQgcG9wdWxhdGUgdGhpcyBi
YXNlZCBvbiB0aGF0Pw0KDQpXaHk/DQpXaGF0IHdpbGwgaGFwcGVuIGlmIHdlIGFzayBmb3IgQS1N
U0RVIGluc2lkZSBBLU1QRFUgYW5kIHdlIHdvbid0IGRvIEEtTVNEVT8NCldvcnN0IGNhc2UsIHRo
ZSBBUCB3aWxsIGRlbnkgdGhpcyBhbmQgd2lsbCBjbGVhciB0aGF0IGJpdCBpbiBpdHMgcmVzcG9u
c2UuDQoNCj4gDQo+ID4NCj4gPiArICAgICAgIGNhcGFiIHw9ICh1MTYpKDEgPDwgMSk7ICAgICAg
ICAgLyogYml0IDEgYWdncmVnYXRpb24gcG9saWN5ICovDQo+ID4gICAgICAgICBjYXBhYiB8PSAo
dTE2KSh0aWQgPDwgMik7ICAgICAgIC8qIGJpdCA1OjIgVElEIG51bWJlciAqLw0KPiA+ICAgICAg
ICAgY2FwYWIgfD0gKHUxNikoYWdnX3NpemUgPDwgNik7ICAvKiBiaXQgMTU6NiBtYXggc2l6ZSBv
Zg0KPiA+IGFnZ2VyZ2F0aW9uICovDQo=

2015-07-20 19:43:17

by Grumbach, Emmanuel

[permalink] [raw]
Subject: [RFC 01/10] mac80211: allow the driver to adverise A-MSDU within A-MPDU Rx support

Drivers may be interested in receiving A-MSDU within A-MDPU.
Not all the devices may be able to do so, make it configurable.

Signed-off-by: Emmanuel Grumbach <[email protected]>
---
include/net/mac80211.h | 4 ++++
net/mac80211/agg-rx.c | 4 +++-
net/mac80211/debugfs.c | 1 +
3 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 6b1077c..95ea827 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -1888,6 +1888,9 @@ struct ieee80211_txq {
* @IEEE80211_HW_SINGLE_SCAN_ON_ALL_BANDS: The HW supports scanning on all bands
* in one command, mac80211 doesn't have to run separate scans per band.
*
+ * @IEEE80211_HW_SUPPORTS_AMSDU_IN_AMPDU:
+ * Hardware supports A-MSDU within A-MPDU.
+ *
* @NUM_IEEE80211_HW_FLAGS: number of hardware flags, used for sizing arrays
*/
enum ieee80211_hw_flags {
@@ -1920,6 +1923,7 @@ enum ieee80211_hw_flags {
IEEE80211_HW_CHANCTX_STA_CSA,
IEEE80211_HW_SUPPORTS_CLONED_SKBS,
IEEE80211_HW_SINGLE_SCAN_ON_ALL_BANDS,
+ IEEE80211_HW_SUPPORTS_AMSDU_IN_AMPDU,

/* keep last, obviously */
NUM_IEEE80211_HW_FLAGS
diff --git a/net/mac80211/agg-rx.c b/net/mac80211/agg-rx.c
index 5c564a6..78de79b 100644
--- a/net/mac80211/agg-rx.c
+++ b/net/mac80211/agg-rx.c
@@ -189,6 +189,7 @@ static void ieee80211_send_addba_resp(struct ieee80211_sub_if_data *sdata, u8 *d
struct ieee80211_local *local = sdata->local;
struct sk_buff *skb;
struct ieee80211_mgmt *mgmt;
+ bool amsdu = ieee80211_hw_check(&local->hw, SUPPORTS_AMSDU_IN_AMPDU);
u16 capab;

skb = dev_alloc_skb(sizeof(*mgmt) + local->hw.extra_tx_headroom);
@@ -217,7 +218,8 @@ static void ieee80211_send_addba_resp(struct ieee80211_sub_if_data *sdata, u8 *d
mgmt->u.action.u.addba_resp.action_code = WLAN_ACTION_ADDBA_RESP;
mgmt->u.action.u.addba_resp.dialog_token = dialog_token;

- capab = (u16)(policy << 1); /* bit 1 aggregation policy */
+ capab = (u16)(amsdu << 0); /* bit 0 A-MSDU support */
+ capab |= (u16)(policy << 1); /* bit 1 aggregation policy */
capab |= (u16)(tid << 2); /* bit 5:2 TID number */
capab |= (u16)(buf_size << 6); /* bit 15:6 max size of aggregation */

diff --git a/net/mac80211/debugfs.c b/net/mac80211/debugfs.c
index 3ea8b7d..6d36249 100644
--- a/net/mac80211/debugfs.c
+++ b/net/mac80211/debugfs.c
@@ -122,6 +122,7 @@ static const char *hw_flag_names[NUM_IEEE80211_HW_FLAGS + 1] = {
FLAG(CHANCTX_STA_CSA),
FLAG(SUPPORTS_CLONED_SKBS),
FLAG(SINGLE_SCAN_ON_ALL_BANDS),
+ FLAG(SUPPORTS_AMSDU_IN_AMPDU),

/* keep last for the build bug below */
(void *)0x1
--
2.1.4


2015-07-20 19:43:19

by Grumbach, Emmanuel

[permalink] [raw]
Subject: [RFC 04/10] iwlwifi: mvm: don't adverise SUPPORTS_CLONED_SKBS anymore

In the upcoming patches, iwlmvm will mangle the SKBs if
receives from mac80211, so we can't support
SUPPORTS_CLONED_SKBS anymore.
The next patches introduce support for LSO and the
segmentation will be done while re-using the original SKB
sent by mac80211.

Signed-off-by: Emmanuel Grumbach <[email protected]>
---
drivers/net/wireless/iwlwifi/mvm/mac80211.c | 1 -
1 file changed, 1 deletion(-)

diff --git a/drivers/net/wireless/iwlwifi/mvm/mac80211.c b/drivers/net/wireless/iwlwifi/mvm/mac80211.c
index f5c508a..a5bebbb 100644
--- a/drivers/net/wireless/iwlwifi/mvm/mac80211.c
+++ b/drivers/net/wireless/iwlwifi/mvm/mac80211.c
@@ -435,7 +435,6 @@ int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm)
ieee80211_hw_set(hw, CONNECTION_MONITOR);
ieee80211_hw_set(hw, CHANCTX_STA_CSA);
ieee80211_hw_set(hw, SUPPORT_FAST_XMIT);
- ieee80211_hw_set(hw, SUPPORTS_CLONED_SKBS);

hw->queues = mvm->first_agg_queue;
hw->offchannel_tx_hw_queue = IWL_MVM_OFFCHANNEL_QUEUE;
--
2.1.4


2015-07-20 19:43:17

by Grumbach, Emmanuel

[permalink] [raw]
Subject: [RFC 03/10] mac80211: limit the A-MSDU based on the Extended Capabilities element

In VHT, the specification allows to limit the number of
MSDUs in an A-MSDU. Parse the capabilities from the peer
and make them available to the driver.
While at it, add a mask for the A-MSDU length in bytes.

Signed-off-by: Emmanuel Grumbach <[email protected]>
---
include/linux/ieee80211.h | 8 ++++++++
include/net/mac80211.h | 5 +++++
net/mac80211/cfg.c | 23 +++++++++++++++++++++++
net/mac80211/vht.c | 16 ++++++++++++++++
4 files changed, 52 insertions(+)

diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h
index 2226496..23f22ae 100644
--- a/include/linux/ieee80211.h
+++ b/include/linux/ieee80211.h
@@ -1498,6 +1498,7 @@ struct ieee80211_vht_operation {
#define IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_3895 0x00000000
#define IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_7991 0x00000001
#define IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454 0x00000002
+#define IEEE80211_VHT_CAP_MAX_MPDU_MASK 0x00000003
#define IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ 0x00000004
#define IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ 0x00000008
#define IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK 0x0000000C
@@ -2078,6 +2079,13 @@ enum ieee80211_tdls_actioncode {
#define WLAN_EXT_CAPA8_OPMODE_NOTIF BIT(6)
#define WLAN_EXT_CAPA8_TDLS_WIDE_BW_ENABLED BIT(7)

+/*
+ * Defines the number of maximanl number of MSDUs in
+ * an A-MSDUs.
+ */
+#define WLAN_EXT_CAPA8_MAX_AMSDU_IN_AMSDU_LSB BIT(7)
+#define WLAN_EXT_CAPA9_MAX_AMSDU_IN_AMSDU_MSB BIT(0)
+
/* TDLS specific payload type in the LLC/SNAP header */
#define WLAN_TDLS_SNAP_RFTYPE 0x2

diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 280419a..c65c1e7 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -1671,11 +1671,14 @@ struct ieee80211_sta_rates {
* notifications and capabilities. The value is only valid after
* the station moves to associated state.
* @smps_mode: current SMPS mode (off, static or dynamic)
+ * @max_amsdu_sz: indicates the maximal length of an A-MSDU in bytes
* @rates: rate control selection table
* @tdls: indicates whether the STA is a TDLS peer
* @tdls_initiator: indicates the STA is an initiator of the TDLS link. Only
* valid if the STA is a TDLS peer in the first place.
* @mfp: indicates whether the STA uses management frame protection or not.
+ * @max_msdus: indicates the maximal number of MSDUs in a single A-MSDUs.
+ * Taken from the Extended Capabilities element.
* @txq: per-TID data TX queues (if driver uses the TXQ abstraction)
*/
struct ieee80211_sta {
@@ -1690,10 +1693,12 @@ struct ieee80211_sta {
u8 rx_nss;
enum ieee80211_sta_rx_bandwidth bandwidth;
enum ieee80211_smps_mode smps_mode;
+ unsigned int max_amsdu_sz;
struct ieee80211_sta_rates __rcu *rates;
bool tdls;
bool tdls_initiator;
bool mfp;
+ u8 max_msdus;

struct ieee80211_txq *txq[IEEE80211_NUM_TIDS];

diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index bf7023f..03183e2 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -1102,6 +1102,29 @@ static int sta_apply_parameters(struct ieee80211_local *local,
sta->sta.max_sp = params->max_sp;
}

+ if (params->ext_capab_len >= 8) {
+ u8 val;
+
+ val = params->ext_capab[8] &
+ WLAN_EXT_CAPA9_MAX_AMSDU_IN_AMSDU_MSB;
+ val <<= 1;
+ val |= params->ext_capab[7] &
+ WLAN_EXT_CAPA8_MAX_AMSDU_IN_AMSDU_LSB;
+ switch (val) {
+ case 1:
+ sta->sta.max_msdus = 32;
+ break;
+ case 2:
+ sta->sta.max_msdus = 16;
+ break;
+ case 3:
+ sta->sta.max_msdus = 8;
+ break;
+ default:
+ sta->sta.max_msdus = 0;
+ };
+ }
+
/*
* cfg80211 validates this (1-2007) and allows setting the AID
* only when creating a new station entry
diff --git a/net/mac80211/vht.c b/net/mac80211/vht.c
index 80694d5..b7a5a6a 100644
--- a/net/mac80211/vht.c
+++ b/net/mac80211/vht.c
@@ -263,6 +263,22 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,
}

sta->sta.bandwidth = ieee80211_sta_cur_vht_bw(sta);
+
+ switch (vht_cap->cap & IEEE80211_VHT_CAP_MAX_MPDU_MASK) {
+ case IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454:
+ sta->sta.max_amsdu_sz = 11454;
+ break;
+ case IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_7991:
+ sta->sta.max_amsdu_sz = 7991;
+ break;
+ case IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_3895:
+ sta->sta.max_amsdu_sz = 3895;
+ break;
+ default:
+ sta->sta.max_amsdu_sz = 3895;
+ WARN_ON(1);
+ }
+
}

enum ieee80211_sta_rx_bandwidth ieee80211_sta_cap_rx_bw(struct sta_info *sta)
--
2.1.4


2015-07-20 19:43:24

by Grumbach, Emmanuel

[permalink] [raw]
Subject: [RFC 06/10] iwlwifi: mvm: move TX PN assignment for CCMP to the driver

From: Johannes Berg <[email protected]>

Move the TX PN assignment (for CCMP only) to the driver. This prepares
the driver for future DSO (driver segmentation offload) where it will
split an SKB into multiple MPDUs by itself.

For TDLS, split out the CCMP TX command handling so that it won't get
a PN assigned, the firmware assigns the PN in that case.

Signed-off-by: Johannes Berg <[email protected]>
Signed-off-by: Emmanuel Grumbach <[email protected]>
---
drivers/net/wireless/iwlwifi/mvm/mac80211.c | 5 ++--
drivers/net/wireless/iwlwifi/mvm/mvm.h | 15 ++++++++---
drivers/net/wireless/iwlwifi/mvm/tdls.c | 12 ++++++---
drivers/net/wireless/iwlwifi/mvm/tx.c | 40 +++++++++++++++++++----------
4 files changed, 49 insertions(+), 23 deletions(-)

diff --git a/drivers/net/wireless/iwlwifi/mvm/mac80211.c b/drivers/net/wireless/iwlwifi/mvm/mac80211.c
index 2be6f32..def314e 100644
--- a/drivers/net/wireless/iwlwifi/mvm/mac80211.c
+++ b/drivers/net/wireless/iwlwifi/mvm/mac80211.c
@@ -2872,10 +2872,11 @@ static int iwl_mvm_mac_set_key(struct ieee80211_hw *hw,
switch (key->cipher) {
case WLAN_CIPHER_SUITE_TKIP:
key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIC;
- /* fall-through */
- case WLAN_CIPHER_SUITE_CCMP:
key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV;
break;
+ case WLAN_CIPHER_SUITE_CCMP:
+ key->flags |= IEEE80211_KEY_FLAG_PUT_IV_SPACE;
+ break;
case WLAN_CIPHER_SUITE_AES_CMAC:
WARN_ON_ONCE(!ieee80211_hw_check(hw, MFP_CAPABLE));
break;
diff --git a/drivers/net/wireless/iwlwifi/mvm/mvm.h b/drivers/net/wireless/iwlwifi/mvm/mvm.h
index 767880b..c8b9d88 100644
--- a/drivers/net/wireless/iwlwifi/mvm/mvm.h
+++ b/drivers/net/wireless/iwlwifi/mvm/mvm.h
@@ -990,10 +990,6 @@ int iwl_mvm_tx_skb_non_sta(struct iwl_mvm *mvm, struct sk_buff *skb);
void iwl_mvm_set_tx_cmd(struct iwl_mvm *mvm, struct sk_buff *skb,
struct iwl_tx_cmd *tx_cmd,
struct ieee80211_tx_info *info, u8 sta_id);
-void iwl_mvm_set_tx_cmd_crypto(struct iwl_mvm *mvm,
- struct ieee80211_tx_info *info,
- struct iwl_tx_cmd *tx_cmd,
- struct sk_buff *skb_frag);
void iwl_mvm_set_tx_cmd_rate(struct iwl_mvm *mvm, struct iwl_tx_cmd *tx_cmd,
struct ieee80211_tx_info *info,
struct ieee80211_sta *sta, __le16 fc);
@@ -1005,6 +1001,17 @@ static inline const char *iwl_mvm_get_tx_fail_reason(u32 status) { return ""; }
int iwl_mvm_flush_tx_path(struct iwl_mvm *mvm, u32 tfd_msk, bool sync);
void iwl_mvm_async_handlers_purge(struct iwl_mvm *mvm);

+static inline void iwl_mvm_set_tx_cmd_ccmp(struct ieee80211_tx_info *info,
+ struct iwl_tx_cmd *tx_cmd)
+{
+ struct ieee80211_key_conf *keyconf = info->control.hw_key;
+
+ tx_cmd->sec_ctl = TX_CMD_SEC_CCM;
+ memcpy(tx_cmd->key, keyconf->key, keyconf->keylen);
+ if (info->flags & IEEE80211_TX_CTL_AMPDU)
+ tx_cmd->tx_flags |= cpu_to_le32(TX_CMD_FLG_CCMP_AGG);
+}
+
static inline void iwl_mvm_wait_for_async_handlers(struct iwl_mvm *mvm)
{
flush_work(&mvm->async_handlers_wk);
diff --git a/drivers/net/wireless/iwlwifi/mvm/tdls.c b/drivers/net/wireless/iwlwifi/mvm/tdls.c
index d44d02d..fe2fa56 100644
--- a/drivers/net/wireless/iwlwifi/mvm/tdls.c
+++ b/drivers/net/wireless/iwlwifi/mvm/tdls.c
@@ -460,13 +460,19 @@ iwl_mvm_tdls_config_channel_switch(struct iwl_mvm *mvm,
cmd.frame.switch_time_offset = cpu_to_le32(ch_sw_tm_ie + 2);

info = IEEE80211_SKB_CB(skb);
- if (info->control.hw_key)
- iwl_mvm_set_tx_cmd_crypto(mvm, info, &cmd.frame.tx_cmd, skb);
+ hdr = (void *)skb->data;
+ if (info->control.hw_key) {
+ if (info->control.hw_key->cipher != WLAN_CIPHER_SUITE_CCMP) {
+ rcu_read_unlock();
+ ret = -EINVAL;
+ goto out;
+ }
+ iwl_mvm_set_tx_cmd_ccmp(info, &cmd.frame.tx_cmd);
+ }

iwl_mvm_set_tx_cmd(mvm, skb, &cmd.frame.tx_cmd, info,
mvmsta->sta_id);

- hdr = (void *)skb->data;
iwl_mvm_set_tx_cmd_rate(mvm, &cmd.frame.tx_cmd, info, sta,
hdr->frame_control);
rcu_read_unlock();
diff --git a/drivers/net/wireless/iwlwifi/mvm/tx.c b/drivers/net/wireless/iwlwifi/mvm/tx.c
index fa3eaf9..3243d73 100644
--- a/drivers/net/wireless/iwlwifi/mvm/tx.c
+++ b/drivers/net/wireless/iwlwifi/mvm/tx.c
@@ -268,19 +268,28 @@ void iwl_mvm_set_tx_cmd_rate(struct iwl_mvm *mvm, struct iwl_tx_cmd *tx_cmd,
/*
* Sets the fields in the Tx cmd that are crypto related
*/
-void iwl_mvm_set_tx_cmd_crypto(struct iwl_mvm *mvm,
- struct ieee80211_tx_info *info,
- struct iwl_tx_cmd *tx_cmd,
- struct sk_buff *skb_frag)
+static void iwl_mvm_set_tx_cmd_crypto(struct iwl_mvm *mvm,
+ struct ieee80211_tx_info *info,
+ struct iwl_tx_cmd *tx_cmd,
+ struct sk_buff *skb_frag,
+ int hdrlen)
{
struct ieee80211_key_conf *keyconf = info->control.hw_key;
+ u8 *crypto_hdr = skb_frag->data + hdrlen;
+ u64 pn;

switch (keyconf->cipher) {
case WLAN_CIPHER_SUITE_CCMP:
- tx_cmd->sec_ctl = TX_CMD_SEC_CCM;
- memcpy(tx_cmd->key, keyconf->key, keyconf->keylen);
- if (info->flags & IEEE80211_TX_CTL_AMPDU)
- tx_cmd->tx_flags |= cpu_to_le32(TX_CMD_FLG_CCMP_AGG);
+ iwl_mvm_set_tx_cmd_ccmp(info, tx_cmd);
+ pn = atomic64_inc_return(&keyconf->tx_pn);
+ crypto_hdr[0] = pn;
+ crypto_hdr[2] = 0;
+ crypto_hdr[3] = 0x20 | keyconf->keyidx;
+ crypto_hdr[1] = pn >> 8;
+ crypto_hdr[4] = pn >> 16;
+ crypto_hdr[5] = pn >> 24;
+ crypto_hdr[6] = pn >> 32;
+ crypto_hdr[7] = pn >> 40;
break;

case WLAN_CIPHER_SUITE_TKIP:
@@ -308,7 +317,7 @@ void iwl_mvm_set_tx_cmd_crypto(struct iwl_mvm *mvm,
*/
static struct iwl_device_cmd *
iwl_mvm_set_tx_params(struct iwl_mvm *mvm, struct sk_buff *skb,
- struct ieee80211_sta *sta, u8 sta_id)
+ int hdrlen, struct ieee80211_sta *sta, u8 sta_id)
{
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
@@ -325,7 +334,7 @@ iwl_mvm_set_tx_params(struct iwl_mvm *mvm, struct sk_buff *skb,
tx_cmd = (struct iwl_tx_cmd *)dev_cmd->payload;

if (info->control.hw_key)
- iwl_mvm_set_tx_cmd_crypto(mvm, info, tx_cmd, skb);
+ iwl_mvm_set_tx_cmd_crypto(mvm, info, tx_cmd, skb, hdrlen);

iwl_mvm_set_tx_cmd(mvm, skb, tx_cmd, info, sta_id);

@@ -346,6 +355,7 @@ int iwl_mvm_tx_skb_non_sta(struct iwl_mvm *mvm, struct sk_buff *skb)
struct iwl_device_cmd *dev_cmd;
struct iwl_tx_cmd *tx_cmd;
u8 sta_id;
+ int hdrlen = ieee80211_hdrlen(hdr->frame_control);

if (WARN_ON_ONCE(info->flags & IEEE80211_TX_CTL_AMPDU))
return -1;
@@ -393,7 +403,7 @@ int iwl_mvm_tx_skb_non_sta(struct iwl_mvm *mvm, struct sk_buff *skb)

IWL_DEBUG_TX(mvm, "station Id %d, queue=%d\n", sta_id, info->hw_queue);

- dev_cmd = iwl_mvm_set_tx_params(mvm, skb, NULL, sta_id);
+ dev_cmd = iwl_mvm_set_tx_params(mvm, skb, hdrlen, NULL, sta_id);
if (!dev_cmd)
return -1;

@@ -401,7 +411,7 @@ int iwl_mvm_tx_skb_non_sta(struct iwl_mvm *mvm, struct sk_buff *skb)
tx_cmd = (struct iwl_tx_cmd *)dev_cmd->payload;

/* Copy MAC header from skb into command buffer */
- memcpy(tx_cmd->hdr, hdr, ieee80211_hdrlen(hdr->frame_control));
+ memcpy(tx_cmd->hdr, hdr, hdrlen);

if (iwl_trans_tx(mvm->trans, skb, dev_cmd, info->hw_queue)) {
iwl_trans_free_tx_cmd(mvm->trans, dev_cmd);
@@ -427,9 +437,11 @@ int iwl_mvm_tx_skb(struct iwl_mvm *mvm, struct sk_buff *skb,
u8 tid = IWL_MAX_TID_COUNT;
u8 txq_id = info->hw_queue;
bool is_data_qos = false, is_ampdu = false;
+ int hdrlen;

mvmsta = iwl_mvm_sta_from_mac80211(sta);
fc = hdr->frame_control;
+ hdrlen = ieee80211_hdrlen(fc);

if (WARN_ON_ONCE(!mvmsta))
return -1;
@@ -437,7 +449,7 @@ int iwl_mvm_tx_skb(struct iwl_mvm *mvm, struct sk_buff *skb,
if (WARN_ON_ONCE(mvmsta->sta_id == IWL_MVM_STATION_COUNT))
return -1;

- dev_cmd = iwl_mvm_set_tx_params(mvm, skb, sta, mvmsta->sta_id);
+ dev_cmd = iwl_mvm_set_tx_params(mvm, skb, hdrlen, sta, mvmsta->sta_id);
if (!dev_cmd)
goto drop;

@@ -469,7 +481,7 @@ int iwl_mvm_tx_skb(struct iwl_mvm *mvm, struct sk_buff *skb,
}

/* Copy MAC header from skb into command buffer */
- memcpy(tx_cmd->hdr, hdr, ieee80211_hdrlen(fc));
+ memcpy(tx_cmd->hdr, hdr, hdrlen);

WARN_ON_ONCE(info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM);

--
2.1.4


2015-07-20 19:43:36

by Grumbach, Emmanuel

[permalink] [raw]
Subject: [RFC 10/10] iwlwifi: don't send A-MSDU when not allowed by peer

A-MSDU is mandatory in the specification, but A-MSDU inside
A-MDPU is not. We need to ensure that the peer is able to
receive an A-MSDU inside an A-MPDU before sending it.
Add the relevant checks.

Signed-off-by: Emmanuel Grumbach <[email protected]>
---
drivers/net/wireless/iwlwifi/mvm/mac80211.c | 2 +-
drivers/net/wireless/iwlwifi/mvm/sta.c | 6 +++++-
drivers/net/wireless/iwlwifi/mvm/sta.h | 6 +++++-
drivers/net/wireless/iwlwifi/mvm/tx.c | 15 ++++++++++++---
4 files changed, 23 insertions(+), 6 deletions(-)

diff --git a/drivers/net/wireless/iwlwifi/mvm/mac80211.c b/drivers/net/wireless/iwlwifi/mvm/mac80211.c
index 0334ad4..cacd7d0 100644
--- a/drivers/net/wireless/iwlwifi/mvm/mac80211.c
+++ b/drivers/net/wireless/iwlwifi/mvm/mac80211.c
@@ -886,7 +886,7 @@ static int iwl_mvm_mac_ampdu_action(struct ieee80211_hw *hw,
ret = iwl_mvm_sta_tx_agg_flush(mvm, vif, sta, tid);
break;
case IEEE80211_AMPDU_TX_OPERATIONAL:
- ret = iwl_mvm_sta_tx_agg_oper(mvm, vif, sta, tid, buf_size);
+ ret = iwl_mvm_sta_tx_agg_oper(mvm, vif, sta, tid, buf_size, amsdu);
break;
default:
WARN_ON_ONCE(1);
diff --git a/drivers/net/wireless/iwlwifi/mvm/sta.c b/drivers/net/wireless/iwlwifi/mvm/sta.c
index f7d3921..4686be9 100644
--- a/drivers/net/wireless/iwlwifi/mvm/sta.c
+++ b/drivers/net/wireless/iwlwifi/mvm/sta.c
@@ -976,7 +976,8 @@ int iwl_mvm_sta_tx_agg_start(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
}

int iwl_mvm_sta_tx_agg_oper(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
- struct ieee80211_sta *sta, u16 tid, u8 buf_size)
+ struct ieee80211_sta *sta, u16 tid, u8 buf_size,
+ bool amsdu)
{
struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta);
struct iwl_mvm_tid_data *tid_data = &mvmsta->tid_data[tid];
@@ -995,6 +996,7 @@ int iwl_mvm_sta_tx_agg_oper(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
queue = tid_data->txq_id;
tid_data->state = IWL_AGG_ON;
mvmsta->agg_tids |= BIT(tid);
+ tid_data->amsdu_in_ampdu_allowed = amsdu;
tid_data->ssn = 0xffff;
spin_unlock_bh(&mvmsta->lock);

@@ -1045,6 +1047,7 @@ int iwl_mvm_sta_tx_agg_stop(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
spin_lock_bh(&mvmsta->lock);

txq_id = tid_data->txq_id;
+ tid_data->amsdu_in_ampdu_allowed = false;

IWL_DEBUG_TX_QUEUES(mvm, "Stop AGG: sta %d tid %d q %d state %d\n",
mvmsta->sta_id, tid, txq_id, tid_data->state);
@@ -1125,6 +1128,7 @@ int iwl_mvm_sta_tx_agg_flush(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
old_state = tid_data->state;
tid_data->state = IWL_AGG_OFF;
mvmsta->agg_tids &= ~BIT(tid);
+ tid_data->amsdu_in_ampdu_allowed = false;
spin_unlock_bh(&mvmsta->lock);

if (old_state >= IWL_AGG_ON) {
diff --git a/drivers/net/wireless/iwlwifi/mvm/sta.h b/drivers/net/wireless/iwlwifi/mvm/sta.h
index eedb215..26d1e31 100644
--- a/drivers/net/wireless/iwlwifi/mvm/sta.h
+++ b/drivers/net/wireless/iwlwifi/mvm/sta.h
@@ -258,6 +258,8 @@ enum iwl_mvm_agg_state {
* Tx response (TX_CMD), and the block ack notification (COMPRESSED_BA).
* @reduced_tpc: Reduced tx power. Holds the data between the
* Tx response (TX_CMD), and the block ack notification (COMPRESSED_BA).
+ * @amsdu_in_ampdu_allowed: true if A-MSDU in A-MPDU is allowed. Relevant only
+ * if &state is %IWL_AGG_ON.
* @state: state of the BA agreement establishment / tear down.
* @txq_id: Tx queue used by the BA session
* @ssn: the first packet to be sent in AGG HW queue in Tx AGG start flow, or
@@ -272,6 +274,7 @@ struct iwl_mvm_tid_data {
/* The rest is Tx AGG related */
u32 rate_n_flags;
u8 reduced_tpc;
+ bool amsdu_in_ampdu_allowed;
enum iwl_mvm_agg_state state;
u16 txq_id;
u16 ssn;
@@ -387,7 +390,8 @@ int iwl_mvm_sta_rx_agg(struct iwl_mvm *mvm, struct ieee80211_sta *sta,
int iwl_mvm_sta_tx_agg_start(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
struct ieee80211_sta *sta, u16 tid, u16 *ssn);
int iwl_mvm_sta_tx_agg_oper(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
- struct ieee80211_sta *sta, u16 tid, u8 buf_size);
+ struct ieee80211_sta *sta, u16 tid, u8 buf_size,
+ bool amsdu);
int iwl_mvm_sta_tx_agg_stop(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
struct ieee80211_sta *sta, u16 tid);
int iwl_mvm_sta_tx_agg_flush(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
diff --git a/drivers/net/wireless/iwlwifi/mvm/tx.c b/drivers/net/wireless/iwlwifi/mvm/tx.c
index cddc296..c23fd2e 100644
--- a/drivers/net/wireless/iwlwifi/mvm/tx.c
+++ b/drivers/net/wireless/iwlwifi/mvm/tx.c
@@ -730,6 +730,7 @@ static int iwl_mvm_tx_tso(struct iwl_mvm *mvm, struct sk_buff* skb_gso,
struct ieee80211_sta *sta,
struct sk_buff_head *mpdus_skb)
{
+ struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta);
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb_gso);
struct ieee80211_key_conf *keyconf = info->control.hw_key;
struct ieee80211_hdr *wifi_hdr = (void *)skb_gso->data;
@@ -737,7 +738,7 @@ static int iwl_mvm_tx_tso(struct iwl_mvm *mvm, struct sk_buff* skb_gso,
struct iwl_lso_splitter s = {};
struct page *hdr_page;
unsigned int mpdu_sz;
- u8 *hdr_page_pos;
+ u8 *hdr_page_pos, *qc, tid;
int i;

s.si = skb_shinfo(skb_gso);
@@ -879,11 +880,19 @@ static int iwl_mvm_tx_tso(struct iwl_mvm *mvm, struct sk_buff* skb_gso,
if (keyconf && keyconf->cipher == WLAN_CIPHER_SUITE_CCMP)
s.wifi_hdr_iv_len += IEEE80211_CCMP_HDR_LEN;

- s.amsdu = true;
+ spin_lock(&mvmsta->lock);
+ qc = ieee80211_get_qos_ctl(s.hdr);
+ tid = qc[0] & IEEE80211_QOS_CTL_TID_MASK;
+ if (WARN_ON_ONCE(tid >= IWL_MAX_TID_COUNT))
+ return -1;
+
+ s.amsdu = !(info->flags & IEEE80211_TX_CTL_AMPDU) ||
+ mvmsta->tid_data[tid].amsdu_in_ampdu_allowed;
+ spin_unlock(&mvmsta->lock);
+
while (s.gso_payload_pos < s.gso_payload_len) {
struct sk_buff *skb = dev_alloc_skb(s.wifi_hdr_iv_len);
unsigned int ip_tcp_snap_hdrlen, amsdu_sz, max_amsdu_sz;
- u8 *qc;

s.frag_in_mpdu = 0;

--
2.1.4


2015-07-28 18:12:52

by Grumbach, Emmanuel

[permalink] [raw]
Subject: Re: [RFC 02/10] mac80211: allow to transmit A-MSDU within A-MPDU



On 07/28/2015 08:27 PM, Krishna Chaitanya wrote:
> On Tue, Jul 28, 2015 at 10:42 PM, Grumbach, Emmanuel
> <[email protected]> wrote:
>>
>> On 07/28/2015 02:35 PM, Grumbach, Emmanuel wrote:
>>>> On Tue, Jul 21, 2015 at 1:12 AM, Emmanuel Grumbach
>>>> <[email protected]> wrote:
>>>>> diff --git a/net/mac80211/agg-tx.c b/net/mac80211/agg-tx.c index
>>>>> c8ba2e7..a758eb84 100644
>>>>> --- a/net/mac80211/agg-tx.c
>>>>> +++ b/net/mac80211/agg-tx.c
>>>>> @@ -97,7 +97,8 @@ static void ieee80211_send_addba_request(struct
>>>> ieee80211_sub_if_data *sdata,
>>>>> mgmt->u.action.u.addba_req.action_code =
>>>>> WLAN_ACTION_ADDBA_REQ;
>>>>>
>>>>> mgmt->u.action.u.addba_req.dialog_token = dialog_token;
>>>>> - capab = (u16)(1 << 1); /* bit 1 aggregation policy */
>>>>> + capab = (u16)(1 << 0); /* bit 0 A-MSDU support */
>>>> Shouldn't this be based on HW capability? We can add couple of more _HW_
>>>> flags for TX and RX and populate this based on that?
>>> Why?
>>> What will happen if we ask for A-MSDU inside A-MPDU and we won't do A-MSDU?
>>> Worst case, the AP will deny this and will clear that bit in its response.
>> I feel that my response was accurate, but the wording wasn't precise enough.
>> We have two possibilities:
>> 1) the AP does support A-MSDU in A-MPDU
>> 2) the AP does not support A-MSDU in A-MPDU
>>
>> In the first case, the AP will reply with that bit set and the client
>> can choose to send A-MSDU or not. I don't think that the AP needs to
>> allocate anything special to support A-MSDU in A-MPDU, so it is free to ask.
> AP needs to allocate large RX buffers to accomodate AMSDU + AMPDU, no?
>> In the second case, the AP will reply with that same bit clear in the
>> AddBA response, and mac0211 / driver will know not to send A-MSDU in
>> A-MPDU (which the driver doesn't support in the case you were describing
>> - so this is a no-op).
>> The only concern you may have is about APs that would decline the AddBA
>> request because of that bit. That doesn't seem to happen since a lot of
>> vendors use A-MSDU in A-MPDU.
> I understand from AP point of view, but what about the local HW? Don't we need
> HW support to handle this A-MSDU in AMPDU? (or) the driver should handle this
> support in ampdu_action based on the amsdu flag?
>
Depends on the HW design. But even if a device does not support it, then
nothing bad will happen. mac80211 will request A-MSDU in A-MDPU and that
specific device simply won't ever use that capability.

2015-07-20 19:43:28

by Grumbach, Emmanuel

[permalink] [raw]
Subject: [RFC 07/10] iwlwifi: mvm: support TSO

This implementation is completely software based. Besides
the CSUM, nothing is done in driver.
The driver needs to allocate new skbs, copy and update the
IP / TCP header.

Signed-off-by: Emmanuel Grumbach <[email protected]>
---
drivers/net/wireless/iwlwifi/mvm/tx.c | 523 +++++++++++++++++++++++++++++++++-
1 file changed, 512 insertions(+), 11 deletions(-)

diff --git a/drivers/net/wireless/iwlwifi/mvm/tx.c b/drivers/net/wireless/iwlwifi/mvm/tx.c
index 3243d73..27b8491 100644
--- a/drivers/net/wireless/iwlwifi/mvm/tx.c
+++ b/drivers/net/wireless/iwlwifi/mvm/tx.c
@@ -64,6 +64,9 @@
*****************************************************************************/
#include <linux/ieee80211.h>
#include <linux/etherdevice.h>
+#include <linux/if_vlan.h>
+#include <net/tcp.h>
+#include <net/ip.h>

#include "iwl-trans.h"
#include "iwl-eeprom-parse.h"
@@ -422,14 +425,469 @@ int iwl_mvm_tx_skb_non_sta(struct iwl_mvm *mvm, struct sk_buff *skb)
}

/*
- * Sets the fields in the Tx cmd that are crypto related
+ * Update the IP / TCP headers and recomputes the IP header CSUM +
+ * pseudo header CSUM.
*/
-int iwl_mvm_tx_skb(struct iwl_mvm *mvm, struct sk_buff *skb,
- struct ieee80211_sta *sta)
+static void iwl_update_ip_tcph(void *iph, struct tcphdr *tcph, bool ipv6,
+ unsigned int len, unsigned int tcp_seq_offset,
+ u16 num_segment)
+{
+ be32_add_cpu(&tcph->seq, tcp_seq_offset);
+
+ if (ipv6) {
+ struct ipv6hdr *iphv6 = iph;
+
+ iphv6->payload_len = cpu_to_be16(len + tcph->doff * 4);
+
+ /* Compute CSUM on the the pseudo-header */
+ tcph->check = ~csum_ipv6_magic(&iphv6->saddr, &iphv6->daddr,
+ len + tcph->doff * 4,
+ IPPROTO_TCP, 0);
+ } else {
+ struct iphdr *iphv4 = iph;
+
+ iphv4->tot_len =
+ cpu_to_be16(len + tcph->doff * 4 + iphv4->ihl * 4);
+ be16_add_cpu(&iphv4->id, num_segment);
+ ip_send_check(iphv4);
+
+ /* Compute CSUM on the the pseudo-header */
+ tcph->check = ~csum_tcpudp_magic(iphv4->saddr, iphv4->daddr,
+ len + tcph->doff * 4,
+ IPPROTO_TCP, 0);
+ }
+}
+
+/**
+ * struct iwl_lso_splitter - state of the split.
+ * @linear_payload_len: The lenth of the payload inside the header of the
+ * original GSO skb.
+ * @gso_frag_num: The fragment number from which to take the data in the
+ * original GSO skb.
+ * @gso_payload_len: The length of the payload in the original GSO skb.
+ * @gso_payload_pos: The incrementing position in the payload of the original
+ * GSO skb.
+ * @gso_offset_in_page: The offset in the page of &gso_frag_num.
+ * @gso_current_frag_size: The size of &gso_frag_num.
+ * @gso_offset_in_frag: The offset in the &gso_frag_num.
+ * @frag_in_mpdu: The index of the frag inside the new (split) MPDU.
+ * @mss: The maximal segment size.
+ * @si: Points to the the shared info of the original GSO skb.
+ * @ieee80211_hdr *hdr: Points to the WiFi header.
+ * @gso_nr_frags: The number of frags in the original GSO skb.
+ * @wifi_hdr_iv_len: The length of the WiFi header including IV.
+ * @tcp_fin: True if TCP_FIN is set in the original GSO skb.
+ * @tcp_push: True if TCP_PSH is set in the original GSO skb.
+ */
+struct iwl_lso_splitter {
+ unsigned int linear_payload_len;
+ unsigned int gso_frag_num;
+ unsigned int gso_payload_len;
+ unsigned int gso_payload_pos;
+ unsigned int gso_offset_in_page;
+ unsigned int gso_current_frag_size;
+ unsigned int gso_offset_in_frag;
+ unsigned int frag_in_mpdu;
+ unsigned int mss;
+ struct skb_shared_info *si;
+ struct ieee80211_hdr *hdr;
+ u8 gso_nr_frags;
+ u8 wifi_hdr_iv_len;
+ bool tcp_fin;
+ bool tcp_push;
+};
+
+/*
+ * Adds a TCP segment from skb_gso to skb. All the state is taken from
+ * and fed back to p. This function takes care about the payload only.
+ * This MSDU might already have msdu_sz bytes of payload that come from
+ * the original GSO skb's header.
+ */
+static unsigned int
+iwl_add_tcp_segment(struct iwl_mvm *mvm, struct sk_buff *skb_gso,
+ struct sk_buff *skb, struct iwl_lso_splitter *p,
+ unsigned int msdu_sz)
+{
+ while (msdu_sz < p->mss) {
+ unsigned int frag_sz =
+ min_t(unsigned int, p->gso_current_frag_size,
+ p->mss - msdu_sz);
+
+ if (p->frag_in_mpdu >= mvm->trans->max_skb_frags) {
+ IWL_ERR(mvm, "Max_skb_frags reached %d\n",
+ p->frag_in_mpdu);
+ return msdu_sz;
+ }
+
+ skb_add_rx_frag(skb, p->frag_in_mpdu,
+ skb_frag_page(&p->si->frags[p->gso_frag_num]),
+ p->gso_offset_in_page, frag_sz, 0);
+
+ /* We just added one frag to the mpdu ... */
+ p->frag_in_mpdu++;
+
+ /* ... which is frag_sz byte long ... */
+ msdu_sz += frag_sz;
+
+ /* ... we progress inside the gso frag ... */
+ p->gso_offset_in_page += frag_sz;
+
+ /* ... which is now getting smaller ... */
+ p->gso_current_frag_size -= frag_sz;
+
+ /* ... and we also progress in the global pos counting. */
+ p->gso_payload_pos += frag_sz;
+
+ /*
+ * This frag has some more bytes that can't fit into this MSDU,
+ * get a new one, and take a ref since this frag will be
+ * attached to two different skbs.
+ */
+ if (p->gso_current_frag_size) {
+ skb_frag_ref(skb_gso, p->gso_frag_num);
+ break;
+ }
+
+ /* We exhausted this frag go the next one ... */
+ p->gso_frag_num++;
+
+ /* ... if it exists ... */
+ if (p->gso_frag_num == p->gso_nr_frags)
+ break;
+
+ /* ... consider its full size ... */
+ p->gso_current_frag_size =
+ skb_frag_size(&p->si->frags[p->gso_frag_num]);
+ /* ... and start from its beginning. */
+ p->gso_offset_in_page =
+ p->si->frags[p->gso_frag_num].page_offset;
+
+ /* If the MSDU is full, get a new one */
+ if (frag_sz == p->mss)
+ break;
+ }
+ return msdu_sz;
+}
+
+static __sum16
+iwl_sw_tcp_csum_offload(struct sk_buff *skb,
+ int tcph_offset, int len, int tcp_hdrlen)
+{
+ struct sk_buff *skb1;
+ __wsum csum;
+
+ len += tcp_hdrlen;
+ skb1 = alloc_skb(len, GFP_ATOMIC);
+ BUG_ON(!skb1);
+
+ skb_copy_bits(skb, tcph_offset, skb_put(skb1, len), len);
+
+ skb_set_transport_header(skb1, 0);
+ skb1->csum_start = (unsigned char *)tcp_hdr(skb1) - skb1->head;
+ csum = skb_checksum(skb1, skb_checksum_start_offset(skb1),
+ skb1->len - skb_checksum_start_offset(skb1),
+ 0);
+ dev_kfree_skb(skb1);
+
+ return csum_fold(csum);
+}
+
+static u8 *get_page_pos(struct page **page, u8 *page_pos, size_t len)
+{
+ if (!*page)
+ goto alloc;
+
+ if (page_pos + len < ((u8 *)page_address(*page)) + PAGE_SIZE)
+ return page_pos;
+
+ __free_pages(*page, 0);
+
+alloc:
+ *page = alloc_pages(GFP_ATOMIC, 0);
+ if (!*page)
+ return NULL;
+
+ return page_address(*page);
+}
+
+static int iwl_add_msdu(struct iwl_mvm *mvm, struct sk_buff *skb_gso,
+ struct sk_buff *skb, struct page **hdr_page,
+ u8 **hdr_page_pos, int ip_id,
+ struct iwl_lso_splitter *p)
+{
+
+ unsigned int tcp_seg_sz, snap_ip_tcp_len, copy_sz = 0;
+ bool ipv6 = p->si->gso_type & SKB_GSO_TCPV6;
+ u8 *start_hdr = *hdr_page_pos;
+ struct tcphdr *tcph;
+ struct iphdr *iph;
+
+ snap_ip_tcp_len =
+ 8 + skb_network_header_len(skb_gso) + tcp_hdrlen(skb_gso);
+ copy_sz = min_t(unsigned int, p->linear_payload_len, p->mss);
+
+ *hdr_page_pos = get_page_pos(hdr_page, *hdr_page_pos, snap_ip_tcp_len + copy_sz);
+ if (!*hdr_page_pos)
+ return -1;
+
+ /*
+ * Copy SNAP / IP / TCP headers from the original GSO skb to the
+ * header page.
+ */
+ skb_copy_bits(skb_gso, p->wifi_hdr_iv_len,
+ *hdr_page_pos, snap_ip_tcp_len);
+
+ *hdr_page_pos += 8;
+
+ iph = (void *)*hdr_page_pos;
+ *hdr_page_pos += skb_network_header_len(skb_gso);
+
+ tcph = (void *)*hdr_page_pos;
+ *hdr_page_pos += tcp_hdrlen(skb_gso);
+
+ /*
+ * If the original GSO skb had more than MSS bytes of payload in its
+ * header, consume it now.
+ */
+ if (copy_sz) {
+ /*
+ * Since we copy from the tail pointer and don't update it,
+ * we can't have more than 1 mss in the linear_payload_len
+ * at this stage. Hence the limitation of 2 MSS in
+ * iwl_mvm_tx_tso.
+ */
+ memcpy(*hdr_page_pos, skb_tail_pointer(skb_gso), copy_sz);
+ *hdr_page_pos += copy_sz;
+ p->gso_payload_pos += copy_sz;
+ p->linear_payload_len -= copy_sz;
+ }
+
+ /* Add frag for SNAP / IP / TCP headers and possibly some payload .. */
+ get_page(*hdr_page);
+ skb_add_rx_frag(skb, p->frag_in_mpdu, *hdr_page,
+ (u8 *)start_hdr - (u8 *)page_address(*hdr_page),
+ *hdr_page_pos - start_hdr, 0);
+ p->frag_in_mpdu++;
+
+ /* .. and now add the payload coming from the frags. */
+ tcp_seg_sz = iwl_add_tcp_segment(mvm, skb_gso, skb, p, copy_sz);
+
+ iwl_update_ip_tcph(iph, tcph, ipv6, tcp_seg_sz,
+ p->gso_payload_pos - tcp_seg_sz, ip_id);
+
+ /* Last segment, apply the TCP flags that may have been delayed */
+ if (p->gso_payload_pos == p->gso_payload_len) {
+ if (p->tcp_push)
+ tcph->psh = 1;
+ if (p->tcp_fin)
+ tcph->fin = 1;
+ }
+
+ if (IWL_MVM_SW_CSUM_OFFLOAD)
+ tcph->check =
+ iwl_sw_tcp_csum_offload(skb, skb->len -
+ tcp_seg_sz -
+ tcp_hdrlen(skb_gso),
+ tcp_seg_sz, tcp_hdrlen(skb_gso));
+ skb->ip_summed = CHECKSUM_COMPLETE;
+
+ return 0;
+}
+
+static int iwl_mvm_tx_tso(struct iwl_mvm *mvm, struct sk_buff* skb_gso,
+ struct ieee80211_sta *sta,
+ struct sk_buff_head *mpdus_skb)
+{
+ struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb_gso);
+ struct ieee80211_key_conf *keyconf = info->control.hw_key;
+ struct ieee80211_hdr *wifi_hdr = (void *)skb_gso->data;
+ bool ipv6 = skb_shinfo(skb_gso)->gso_type & SKB_GSO_TCPV6;
+ struct iwl_lso_splitter s = {};
+ struct page *hdr_page;
+ unsigned int mpdu_sz;
+ u8 *hdr_page_pos;
+ int i;
+
+ s.si = skb_shinfo(skb_gso);
+ s.mss = skb_shinfo(skb_gso)->gso_size;
+ s.gso_nr_frags = skb_shinfo(skb_gso)->nr_frags;
+ s.linear_payload_len = skb_tail_pointer(skb_gso) -
+ skb_transport_header(skb_gso) - tcp_hdrlen(skb_gso);
+ s.gso_payload_len = s.linear_payload_len + skb_gso->data_len;
+
+ /* more than 2 * mss in the header? segment w/o GSO */
+ if (s.linear_payload_len >= 2 * s.mss) {
+ struct sk_buff *tmp, *next;
+ char cb[sizeof(skb_gso->cb)];
+
+ memcpy(cb, skb_gso->cb, sizeof(cb));
+ next = skb_gso_segment(skb_gso, 0);
+ if (IS_ERR(next))
+ return -1;
+ else if (next)
+ consume_skb(skb_gso);
+
+ while (next) {
+ tmp = next;
+ next = tmp->next;
+ memcpy(tmp->cb, cb, sizeof(tmp->cb));
+
+ tmp->prev = NULL;
+ tmp->next = NULL;
+
+ __skb_queue_tail(mpdus_skb, tmp);
+ }
+ return 0;
+ }
+
+ /*
+ * We got an GSO skb that may or may not have payload in its header.
+ * Re-use the same skb and chop the payload that can't fit into 1 MSS.
+ * First take the payload from the header...
+ */
+ mpdu_sz = min_t(unsigned int, s.linear_payload_len, s.mss);
+ s.linear_payload_len -= mpdu_sz;
+ skb_gso->data_len = 0;
+
+ /*
+ * ... and add now payload from the frags to get up to 1 MSS.
+ * If we have one mss already, mpdu_sz will be s.mss and
+ * this loop won't do much.
+ * Make sure we don't go beyond the limit of the origin GSO
+ * skb.
+ */
+ s.gso_offset_in_frag = 0;
+ while (s.gso_frag_num < s.gso_nr_frags) {
+ unsigned int frag_sz =
+ skb_frag_size(&s.si->frags[s.gso_frag_num]);
+
+ if ((s.mss - mpdu_sz) >= frag_sz) {
+ /* there is enough room for this entire frag */
+ mpdu_sz += frag_sz;
+ skb_gso->data_len += frag_sz;
+ s.gso_frag_num++;
+ continue;
+ }
+
+ /*
+ * Only part of this frag will fit and
+ * we now have a complete mss
+ */
+ s.gso_offset_in_frag = s.mss - mpdu_sz;
+ skb_gso->data_len += (s.mss - mpdu_sz);
+ mpdu_sz = s.mss;
+ break;
+ }
+
+ /* We have only one mss or even less? */
+ if (WARN_ON(s.gso_frag_num == s.gso_nr_frags))
+ goto err;
+
+ /* remember the size of the remainder of the frag */
+ s.gso_current_frag_size = skb_frag_size(&s.si->frags[s.gso_frag_num]) -
+ s.gso_offset_in_frag;
+
+ /* shorten the skb_gso's frag to fit the mss */
+ skb_frag_size_set(&s.si->frags[s.gso_frag_num], s.gso_offset_in_frag);
+
+ /* translate to offset from the start of the page */
+ s.gso_offset_in_page =
+ s.gso_offset_in_frag + s.si->frags[s.gso_frag_num].page_offset;
+
+ /* remove all the other frags from skb_gso */
+ skb_shinfo(skb_gso)->nr_frags = s.gso_frag_num + 1;
+
+ /* take a temp ref to the last frag */
+ skb_frag_ref(skb_gso, s.gso_frag_num);
+
+ /* update the skb_gso's length fields */
+ skb_gso->len -= (s.gso_payload_len - s.mss);
+
+ /*
+ * If there is still some payload in the header, update the tail to the
+ * end of MSS.
+ */
+ if (s.linear_payload_len)
+ skb_gso->tail -= s.linear_payload_len;
+
+ /*
+ * Clear the bits in the TCP header that we delay for
+ * the last segment.
+ */
+ s.tcp_push = tcp_hdr(skb_gso)->psh;
+ s.tcp_fin = tcp_hdr(skb_gso)->fin;
+ tcp_hdr(skb_gso)->psh = 0;
+ tcp_hdr(skb_gso)->fin = 0;
+
+ /* update the IP / TCP header with the new length */
+ iwl_update_ip_tcph(ip_hdr(skb_gso), tcp_hdr(skb_gso),
+ ipv6, s.mss, 0, 0);
+
+ if (IWL_MVM_SW_CSUM_OFFLOAD)
+ tcp_hdr(skb_gso)->check =
+ iwl_sw_tcp_csum_offload(skb_gso,
+ (u8 *)tcp_hdr(skb_gso) -
+ (u8 *)skb_gso->data,
+ s.mss, tcp_hdrlen(skb_gso));
+
+ skb_gso->ip_summed = CHECKSUM_COMPLETE;
+ __skb_queue_tail(mpdus_skb, skb_gso);
+
+ /* mss bytes have been consumed from the data */
+ s.gso_payload_pos = s.mss;
+ i = 0;
+
+ hdr_page = NULL;
+ hdr_page_pos = get_page_pos(&hdr_page, NULL, 1);
+ if (!hdr_page_pos)
+ return -1;
+
+ s.hdr = wifi_hdr;
+ s.wifi_hdr_iv_len = ieee80211_hdrlen(wifi_hdr->frame_control);
+ if (keyconf && keyconf->cipher == WLAN_CIPHER_SUITE_CCMP)
+ s.wifi_hdr_iv_len += IEEE80211_CCMP_HDR_LEN;
+
+ while (s.gso_payload_pos < s.gso_payload_len) {
+ struct sk_buff *skb = dev_alloc_skb(s.wifi_hdr_iv_len);
+ int ret;
+ s.frag_in_mpdu = 0;
+
+ if (WARN_ON(s.gso_frag_num >= s.gso_nr_frags))
+ break;
+
+ if (!skb)
+ goto err;
+
+ memcpy(skb->cb, skb_gso->cb, sizeof(skb->cb));
+
+ /* new MPDU - add WiFi header + IV */
+ memcpy(skb_put(skb, s.wifi_hdr_iv_len),
+ wifi_hdr, s.wifi_hdr_iv_len);
+
+ ret = iwl_add_msdu(mvm, skb_gso, skb, &hdr_page,
+ &hdr_page_pos, i++, &s);
+ if (ret < 0) {
+ skb_queue_purge(mpdus_skb);
+ goto err;
+ }
+
+ __skb_queue_tail(mpdus_skb, skb);
+ }
+
+ return 0;
+
+err:
+ if (hdr_page)
+ __free_pages(hdr_page, 0);
+ return -1;
+}
+
+static int iwl_mvm_tx_mpdu(struct iwl_mvm *mvm, struct sk_buff *skb,
+ struct ieee80211_sta *sta)
{
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+ struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta);
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
- struct iwl_mvm_sta *mvmsta;
struct iwl_device_cmd *dev_cmd;
struct iwl_tx_cmd *tx_cmd;
__le16 fc;
@@ -439,16 +897,9 @@ int iwl_mvm_tx_skb(struct iwl_mvm *mvm, struct sk_buff *skb,
bool is_data_qos = false, is_ampdu = false;
int hdrlen;

- mvmsta = iwl_mvm_sta_from_mac80211(sta);
fc = hdr->frame_control;
hdrlen = ieee80211_hdrlen(fc);

- if (WARN_ON_ONCE(!mvmsta))
- return -1;
-
- if (WARN_ON_ONCE(mvmsta->sta_id == IWL_MVM_STATION_COUNT))
- return -1;
-
dev_cmd = iwl_mvm_set_tx_params(mvm, skb, hdrlen, sta, mvmsta->sta_id);
if (!dev_cmd)
goto drop;
@@ -521,6 +972,56 @@ drop:
return -1;
}

+/*
+ * Sets the fields in the Tx cmd that are crypto related
+ */
+int iwl_mvm_tx_skb(struct iwl_mvm *mvm, struct sk_buff *skb,
+ struct ieee80211_sta *sta)
+{
+ struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta);
+ struct sk_buff_head mpdus_skbs;
+ unsigned int payload_len;
+ int ret = -1;
+
+ if (WARN_ON_ONCE(!mvmsta))
+ return -1;
+
+ if (WARN_ON_ONCE(mvmsta->sta_id == IWL_MVM_STATION_COUNT))
+ return -1;
+
+ payload_len = skb_tail_pointer(skb) - skb_transport_header(skb) -
+ tcp_hdrlen(skb) + skb->data_len;
+
+ /* This packet is smaller than MSS, one MPDU is enough */
+ if (!skb_is_gso(skb) || payload_len <= skb_shinfo(skb)->gso_size)
+ return iwl_mvm_tx_mpdu(mvm, skb, sta);
+
+ __skb_queue_head_init(&mpdus_skbs);
+
+ ret = iwl_mvm_tx_tso(mvm, skb, sta, &mpdus_skbs);
+ if (ret)
+ goto out;
+
+ if (WARN_ON(skb_queue_empty(&mpdus_skbs)))
+ goto out;
+
+ while (!skb_queue_empty(&mpdus_skbs)) {
+ struct sk_buff *skb = __skb_dequeue(&mpdus_skbs);
+
+ ret = iwl_mvm_tx_mpdu(mvm, skb, sta);
+
+ if (ret) {
+ __skb_queue_purge(&mpdus_skbs);
+ goto out;
+ }
+ }
+
+ ret = 0;
+
+out:
+ return ret ? -1 : 0;
+}
+
static void iwl_mvm_check_ratid_empty(struct iwl_mvm *mvm,
struct ieee80211_sta *sta, u8 tid)
{
--
2.1.4


2015-07-20 19:43:32

by Grumbach, Emmanuel

[permalink] [raw]
Subject: [RFC 09/10] iwlwifi: mvm: allow to configure the maximal size of an A-MSDU

Since an A-MSDU has one single CRC for the whole
aggregation, it is tricky to know what size to allow.
For now, add a debugfs hook to be able to tune the
algorightm.

Signed-off-by: Emmanuel Grumbach <[email protected]>
---
drivers/net/wireless/iwlwifi/mvm/debugfs.c | 19 +++++++++++++++++++
drivers/net/wireless/iwlwifi/mvm/mvm.h | 1 +
drivers/net/wireless/iwlwifi/mvm/tx.c | 7 ++++++-
3 files changed, 26 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/iwlwifi/mvm/debugfs.c b/drivers/net/wireless/iwlwifi/mvm/debugfs.c
index ffb4b5c..621792c 100644
--- a/drivers/net/wireless/iwlwifi/mvm/debugfs.c
+++ b/drivers/net/wireless/iwlwifi/mvm/debugfs.c
@@ -981,6 +981,23 @@ static ssize_t iwl_dbgfs_fw_dbg_collect_write(struct iwl_mvm *mvm,
return count;
}

+static ssize_t iwl_dbgfs_max_amsdu_sz_write(struct iwl_mvm *mvm,
+ char *buf, size_t count,
+ loff_t *ppos)
+{
+ unsigned int max_amsdu_sz;
+ int ret;
+
+ ret = kstrtoint(buf, 0, &max_amsdu_sz);
+
+ if (max_amsdu_sz > 11454 || max_amsdu_sz < IEEE80211_MAX_DATA_LEN)
+ return -EINVAL;
+
+ mvm->max_amsdu_sz = max_amsdu_sz;
+
+ return count;
+}
+
#define ADD_TEXT(...) pos += scnprintf(buf + pos, bufsz - pos, __VA_ARGS__)
#ifdef CONFIG_IWLWIFI_BCAST_FILTERING
static ssize_t iwl_dbgfs_bcast_filters_read(struct file *file,
@@ -1499,6 +1516,7 @@ MVM_DEBUGFS_READ_WRITE_FILE_OPS(scan_ant_rxchain, 8);
MVM_DEBUGFS_READ_WRITE_FILE_OPS(d0i3_refs, 8);
MVM_DEBUGFS_READ_WRITE_FILE_OPS(fw_dbg_conf, 8);
MVM_DEBUGFS_WRITE_FILE_OPS(fw_dbg_collect, 8);
+MVM_DEBUGFS_WRITE_FILE_OPS(max_amsdu_sz, 10);

#ifdef CONFIG_IWLWIFI_BCAST_FILTERING
MVM_DEBUGFS_READ_WRITE_FILE_OPS(bcast_filters, 256);
@@ -1542,6 +1560,7 @@ int iwl_mvm_dbgfs_register(struct iwl_mvm *mvm, struct dentry *dbgfs_dir)
MVM_DEBUGFS_ADD_FILE(d0i3_refs, mvm->debugfs_dir, S_IRUSR | S_IWUSR);
MVM_DEBUGFS_ADD_FILE(fw_dbg_conf, mvm->debugfs_dir, S_IRUSR | S_IWUSR);
MVM_DEBUGFS_ADD_FILE(fw_dbg_collect, mvm->debugfs_dir, S_IWUSR);
+ MVM_DEBUGFS_ADD_FILE(max_amsdu_sz, mvm->debugfs_dir, S_IWUSR);
if (!debugfs_create_bool("enable_scan_iteration_notif",
S_IRUSR | S_IWUSR,
mvm->debugfs_dir,
diff --git a/drivers/net/wireless/iwlwifi/mvm/mvm.h b/drivers/net/wireless/iwlwifi/mvm/mvm.h
index c8b9d88..82a0c50 100644
--- a/drivers/net/wireless/iwlwifi/mvm/mvm.h
+++ b/drivers/net/wireless/iwlwifi/mvm/mvm.h
@@ -663,6 +663,7 @@ struct iwl_mvm {
u32 dbgfs_prph_reg_addr;
bool disable_power_off;
bool disable_power_off_d3;
+ unsigned int max_amsdu_sz;

u32 scan_iter_notif_enabled; /* must be u32 for debugfs_create_bool */

diff --git a/drivers/net/wireless/iwlwifi/mvm/tx.c b/drivers/net/wireless/iwlwifi/mvm/tx.c
index 3f89d71..cddc296 100644
--- a/drivers/net/wireless/iwlwifi/mvm/tx.c
+++ b/drivers/net/wireless/iwlwifi/mvm/tx.c
@@ -903,7 +903,12 @@ static int iwl_mvm_tx_tso(struct iwl_mvm *mvm, struct sk_buff* skb_gso,
if (s.gso_payload_len - s.gso_payload_pos <= s.mss)
s.amsdu = false;

- max_amsdu_sz = sta->max_amsdu_sz;
+ max_amsdu_sz = ACCESS_ONCE(mvm->max_amsdu_sz);
+ if (max_amsdu_sz)
+ max_amsdu_sz = min_t(unsigned int, sta->max_amsdu_sz,
+ max_amsdu_sz);
+ else
+ max_amsdu_sz = sta->max_amsdu_sz;

if (!max_amsdu_sz || !s.amsdu) {
int l;
--
2.1.4


2015-07-20 19:43:21

by Grumbach, Emmanuel

[permalink] [raw]
Subject: [RFC 05/10] iwlwifi: mvm: pretend to have CSUM OFFLOAD

From: Johannes Berg <[email protected]>

This allows to debug the LSO flows.

Signed-off-by: Johannes Berg <[email protected]>
Signed-off-by: Emmanuel Grumbach <[email protected]>
---
drivers/net/wireless/iwlwifi/mvm/constants.h | 1 +
drivers/net/wireless/iwlwifi/mvm/mac80211.c | 6 ++++++
2 files changed, 7 insertions(+)

diff --git a/drivers/net/wireless/iwlwifi/mvm/constants.h b/drivers/net/wireless/iwlwifi/mvm/constants.h
index b8ee312..70d4697 100644
--- a/drivers/net/wireless/iwlwifi/mvm/constants.h
+++ b/drivers/net/wireless/iwlwifi/mvm/constants.h
@@ -103,6 +103,7 @@
#define IWL_MVM_RS_RSSI_BASED_INIT_RATE 0
#define IWL_MVM_RS_DISABLE_P2P_MIMO 0
#define IWL_MVM_TOF_IS_RESPONDER 0
+#define IWL_MVM_SW_CSUM_OFFLOAD 0
#define IWL_MVM_RS_NUM_TRY_BEFORE_ANT_TOGGLE 1
#define IWL_MVM_RS_HT_VHT_RETRIES_PER_RATE 2
#define IWL_MVM_RS_HT_VHT_RETRIES_PER_RATE_TW 1
diff --git a/drivers/net/wireless/iwlwifi/mvm/mac80211.c b/drivers/net/wireless/iwlwifi/mvm/mac80211.c
index a5bebbb..2be6f32 100644
--- a/drivers/net/wireless/iwlwifi/mvm/mac80211.c
+++ b/drivers/net/wireless/iwlwifi/mvm/mac80211.c
@@ -436,6 +436,12 @@ int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm)
ieee80211_hw_set(hw, CHANCTX_STA_CSA);
ieee80211_hw_set(hw, SUPPORT_FAST_XMIT);

+ if (IWL_MVM_SW_CSUM_OFFLOAD)
+ hw->netdev_features |= NETIF_F_IP_CSUM | NETIF_F_TSO |
+ NETIF_F_TSO6 | NETIF_F_IPV6_CSUM;
+ /* allow these to be toggled by user */
+ hw->netdev_hw_features = hw->netdev_features;
+
hw->queues = mvm->first_agg_queue;
hw->offchannel_tx_hw_queue = IWL_MVM_OFFCHANNEL_QUEUE;
hw->radiotap_mcs_details |= IEEE80211_RADIOTAP_MCS_HAVE_FEC |
--
2.1.4


2015-07-28 11:24:55

by Krishna Chaitanya

[permalink] [raw]
Subject: Re: [RFC 02/10] mac80211: allow to transmit A-MSDU within A-MPDU

On Tue, Jul 21, 2015 at 1:12 AM, Emmanuel Grumbach
<[email protected]> wrote:
>
> diff --git a/net/mac80211/agg-tx.c b/net/mac80211/agg-tx.c
> index c8ba2e7..a758eb84 100644
> --- a/net/mac80211/agg-tx.c
> +++ b/net/mac80211/agg-tx.c
> @@ -97,7 +97,8 @@ static void ieee80211_send_addba_request(struct ieee80211_sub_if_data *sdata,
> mgmt->u.action.u.addba_req.action_code = WLAN_ACTION_ADDBA_REQ;
>
> mgmt->u.action.u.addba_req.dialog_token = dialog_token;
> - capab = (u16)(1 << 1); /* bit 1 aggregation policy */
> + capab = (u16)(1 << 0); /* bit 0 A-MSDU support */

Shouldn't this be based on HW capability? We can add couple of more
_HW_ flags for TX and RX
and populate this based on that?

>
> + capab |= (u16)(1 << 1); /* bit 1 aggregation policy */
> capab |= (u16)(tid << 2); /* bit 5:2 TID number */
> capab |= (u16)(agg_size << 6); /* bit 15:6 max size of aggergation */