Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/jkirsher/next...
authorDavid S. Miller <davem@davemloft.net>
Mon, 13 Apr 2015 01:36:57 +0000 (21:36 -0400)
committerDavid S. Miller <davem@davemloft.net>
Mon, 13 Apr 2015 01:36:57 +0000 (21:36 -0400)
Jeff Kirsher says:

====================
Intel Wired LAN Driver Updates 2015-04-11

This series contains updates to iflink, ixgbe and ixgbevf.

The entire set of changes come from Vlad Zolotarov to ultimately add
the ethtool ops to VF driver to allow querying the RSS indirection table
and RSS random key.

Currently we support only 82599 and x540 devices.  On those devices, VFs
share the RSS redirection table and hash key with a PF.  Letting the VF
query this information may introduce some security risks, therefore this
feature will be disabled by default.

The new netdev op allows a system administrator to change the default
behaviour with "ip link set" command.  The relevant iproute2 patch has
already been sent and awaits for this series upstream.
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
32 files changed:
drivers/net/ethernet/cadence/macb.c
include/net/cfg80211.h
include/net/mac80211.h
include/uapi/linux/fou.h
net/core/sock.c
net/ipv4/fou.c
net/mac80211/agg-tx.c
net/mac80211/driver-ops.h
net/mac80211/ieee80211_i.h
net/mac80211/iface.c
net/mac80211/main.c
net/mac80211/mlme.c
net/mac80211/rc80211_minstrel.c
net/mac80211/rc80211_minstrel.h
net/mac80211/rc80211_minstrel_debugfs.c
net/mac80211/rc80211_minstrel_ht.c
net/mac80211/rc80211_minstrel_ht.h
net/mac80211/rc80211_minstrel_ht_debugfs.c
net/mac80211/rx.c
net/mac80211/sta_info.c
net/mac80211/sta_info.h
net/mac80211/status.c
net/mac80211/trace.h
net/mac80211/tx.c
net/mac80211/util.c
net/wireless/Kconfig
net/wireless/nl80211.c
net/wireless/reg.c
net/wireless/reg.h
net/wireless/sme.c
net/wireless/util.c
security/selinux/nlmsgtab.c

index 448a32309dd08c79c99bca7692fea10d429c1b41..9f5387249f242374437581e6c2df7c037917f83a 100644 (file)
@@ -1956,12 +1956,12 @@ static struct net_device_stats *macb_get_stats(struct net_device *dev)
                            hwstat->rx_oversize_pkts +
                            hwstat->rx_jabbers +
                            hwstat->rx_undersize_pkts +
-                           hwstat->sqe_test_errors +
                            hwstat->rx_length_mismatch);
        nstat->tx_errors = (hwstat->tx_late_cols +
                            hwstat->tx_excessive_cols +
                            hwstat->tx_underruns +
-                           hwstat->tx_carrier_errors);
+                           hwstat->tx_carrier_errors +
+                           hwstat->sqe_test_errors);
        nstat->collisions = (hwstat->tx_single_cols +
                             hwstat->tx_multiple_cols +
                             hwstat->tx_excessive_cols);
index 44130643656933783d77e356878651d7d10db4b5..f8d6813cd5b2c05eb5f7bea0595a12ad4c02dca8 100644 (file)
@@ -5000,6 +5000,64 @@ int cfg80211_get_p2p_attr(const u8 *ies, unsigned int len,
                          enum ieee80211_p2p_attr_id attr,
                          u8 *buf, unsigned int bufsize);
 
+/**
+ * ieee80211_ie_split_ric - split an IE buffer according to ordering (with RIC)
+ * @ies: the IE buffer
+ * @ielen: the length of the IE buffer
+ * @ids: an array with element IDs that are allowed before
+ *     the split
+ * @n_ids: the size of the element ID array
+ * @after_ric: array IE types that come after the RIC element
+ * @n_after_ric: size of the @after_ric array
+ * @offset: offset where to start splitting in the buffer
+ *
+ * This function splits an IE buffer by updating the @offset
+ * variable to point to the location where the buffer should be
+ * split.
+ *
+ * It assumes that the given IE buffer is well-formed, this
+ * has to be guaranteed by the caller!
+ *
+ * It also assumes that the IEs in the buffer are ordered
+ * correctly, if not the result of using this function will not
+ * be ordered correctly either, i.e. it does no reordering.
+ *
+ * The function returns the offset where the next part of the
+ * buffer starts, which may be @ielen if the entire (remainder)
+ * of the buffer should be used.
+ */
+size_t ieee80211_ie_split_ric(const u8 *ies, size_t ielen,
+                             const u8 *ids, int n_ids,
+                             const u8 *after_ric, int n_after_ric,
+                             size_t offset);
+
+/**
+ * ieee80211_ie_split - split an IE buffer according to ordering
+ * @ies: the IE buffer
+ * @ielen: the length of the IE buffer
+ * @ids: an array with element IDs that are allowed before
+ *     the split
+ * @n_ids: the size of the element ID array
+ * @offset: offset where to start splitting in the buffer
+ *
+ * This function splits an IE buffer by updating the @offset
+ * variable to point to the location where the buffer should be
+ * split.
+ *
+ * It assumes that the given IE buffer is well-formed, this
+ * has to be guaranteed by the caller!
+ *
+ * It also assumes that the IEs in the buffer are ordered
+ * correctly, if not the result of using this function will not
+ * be ordered correctly either, i.e. it does no reordering.
+ *
+ * The function returns the offset where the next part of the
+ * buffer starts, which may be @ielen if the entire (remainder)
+ * of the buffer should be used.
+ */
+size_t ieee80211_ie_split(const u8 *ies, size_t ielen,
+                         const u8 *ids, int n_ids, size_t offset);
+
 /**
  * cfg80211_report_wowlan_wakeup - report wakeup from WoWLAN
  * @wdev: the wireless device reporting the wakeup
index 201bc68e0cffd19766c31e983c6e652d50c681f9..b4bef1152c05c52b87dc877e9d4e8b09050842f7 100644 (file)
  *
  */
 
+/**
+ * DOC: mac80211 software tx queueing
+ *
+ * mac80211 provides an optional intermediate queueing implementation designed
+ * to allow the driver to keep hardware queues short and provide some fairness
+ * between different stations/interfaces.
+ * In this model, the driver pulls data frames from the mac80211 queue instead
+ * of letting mac80211 push them via drv_tx().
+ * Other frames (e.g. control or management) are still pushed using drv_tx().
+ *
+ * Drivers indicate that they use this model by implementing the .wake_tx_queue
+ * driver operation.
+ *
+ * Intermediate queues (struct ieee80211_txq) are kept per-sta per-tid, with a
+ * single per-vif queue for multicast data frames.
+ *
+ * The driver is expected to initialize its private per-queue data for stations
+ * and interfaces in the .add_interface and .sta_add ops.
+ *
+ * The driver can't access the queue directly. To dequeue a frame, it calls
+ * ieee80211_tx_dequeue(). Whenever mac80211 adds a new frame to a queue, it
+ * calls the .wake_tx_queue driver op.
+ *
+ * For AP powersave TIM handling, the driver only needs to indicate if it has
+ * buffered packets in the driver specific data structures by calling
+ * ieee80211_sta_set_buffered(). For frames buffered in the ieee80211_txq
+ * struct, mac80211 sets the appropriate TIM PVB bits and calls
+ * .release_buffered_frames().
+ * In that callback the driver is therefore expected to release its own
+ * buffered frames and afterwards also frames from the ieee80211_txq (obtained
+ * via the usual ieee80211_tx_dequeue).
+ */
+
 struct device;
 
 /**
@@ -1306,6 +1339,7 @@ enum ieee80211_vif_flags {
  *     monitor interface (if that is requested.)
  * @drv_priv: data area for driver use, will always be aligned to
  *     sizeof(void *).
+ * @txq: the multicast data TX queue (if driver uses the TXQ abstraction)
  */
 struct ieee80211_vif {
        enum nl80211_iftype type;
@@ -1317,6 +1351,8 @@ struct ieee80211_vif {
        u8 cab_queue;
        u8 hw_queue[IEEE80211_NUM_ACS];
 
+       struct ieee80211_txq *txq;
+
        struct ieee80211_chanctx_conf __rcu *chanctx_conf;
 
        u32 driver_flags;
@@ -1575,6 +1611,7 @@ struct ieee80211_sta_rates {
  * @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.
+ * @txq: per-TID data TX queues (if driver uses the TXQ abstraction)
  */
 struct ieee80211_sta {
        u32 supp_rates[IEEE80211_NUM_BANDS];
@@ -1593,6 +1630,8 @@ struct ieee80211_sta {
        bool tdls_initiator;
        bool mfp;
 
+       struct ieee80211_txq *txq[IEEE80211_NUM_TIDS];
+
        /* must be last */
        u8 drv_priv[0] __aligned(sizeof(void *));
 };
@@ -1620,6 +1659,27 @@ struct ieee80211_tx_control {
        struct ieee80211_sta *sta;
 };
 
+/**
+ * struct ieee80211_txq - Software intermediate tx queue
+ *
+ * @vif: &struct ieee80211_vif pointer from the add_interface callback.
+ * @sta: station table entry, %NULL for per-vif queue
+ * @tid: the TID for this queue (unused for per-vif queue)
+ * @ac: the AC for this queue
+ *
+ * The driver can obtain packets from this queue by calling
+ * ieee80211_tx_dequeue().
+ */
+struct ieee80211_txq {
+       struct ieee80211_vif *vif;
+       struct ieee80211_sta *sta;
+       u8 tid;
+       u8 ac;
+
+       /* must be last */
+       u8 drv_priv[0] __aligned(sizeof(void *));
+};
+
 /**
  * enum ieee80211_hw_flags - hardware flags
  *
@@ -1844,6 +1904,8 @@ enum ieee80211_hw_flags {
  *     within &struct ieee80211_sta.
  * @chanctx_data_size: size (in bytes) of the drv_priv data area
  *     within &struct ieee80211_chanctx_conf.
+ * @txq_data_size: size (in bytes) of the drv_priv data area
+ *     within @struct ieee80211_txq.
  *
  * @max_rates: maximum number of alternate rate retry stages the hw
  *     can handle.
@@ -1892,6 +1954,9 @@ enum ieee80211_hw_flags {
  * @n_cipher_schemes: a size of an array of cipher schemes definitions.
  * @cipher_schemes: a pointer to an array of cipher scheme definitions
  *     supported by HW.
+ *
+ * @txq_ac_max_pending: maximum number of frames per AC pending in all txq
+ *     entries for a vif.
  */
 struct ieee80211_hw {
        struct ieee80211_conf conf;
@@ -1904,6 +1969,7 @@ struct ieee80211_hw {
        int vif_data_size;
        int sta_data_size;
        int chanctx_data_size;
+       int txq_data_size;
        u16 queues;
        u16 max_listen_interval;
        s8 max_signal;
@@ -1920,6 +1986,7 @@ struct ieee80211_hw {
        u8 uapsd_max_sp_len;
        u8 n_cipher_schemes;
        const struct ieee80211_cipher_scheme *cipher_schemes;
+       int txq_ac_max_pending;
 };
 
 /**
@@ -3082,6 +3149,8 @@ enum ieee80211_reconfig_type {
  *     response template is provided, together with the location of the
  *     switch-timing IE within the template. The skb can only be used within
  *     the function call.
+ *
+ * @wake_tx_queue: Called when new packets have been added to the queue.
  */
 struct ieee80211_ops {
        void (*tx)(struct ieee80211_hw *hw,
@@ -3313,6 +3382,9 @@ struct ieee80211_ops {
        void (*tdls_recv_channel_switch)(struct ieee80211_hw *hw,
                                         struct ieee80211_vif *vif,
                                         struct ieee80211_tdls_ch_sw_params *params);
+
+       void (*wake_tx_queue)(struct ieee80211_hw *hw,
+                             struct ieee80211_txq *txq);
 };
 
 /**
@@ -5308,30 +5380,13 @@ int ieee80211_reserve_tid(struct ieee80211_sta *sta, u8 tid);
 void ieee80211_unreserve_tid(struct ieee80211_sta *sta, u8 tid);
 
 /**
- * ieee80211_ie_split - split an IE buffer according to ordering
- *
- * @ies: the IE buffer
- * @ielen: the length of the IE buffer
- * @ids: an array with element IDs that are allowed before
- *     the split
- * @n_ids: the size of the element ID array
- * @offset: offset where to start splitting in the buffer
+ * ieee80211_tx_dequeue - dequeue a packet from a software tx queue
  *
- * This function splits an IE buffer by updating the @offset
- * variable to point to the location where the buffer should be
- * split.
- *
- * It assumes that the given IE buffer is well-formed, this
- * has to be guaranteed by the caller!
- *
- * It also assumes that the IEs in the buffer are ordered
- * correctly, if not the result of using this function will not
- * be ordered correctly either, i.e. it does no reordering.
+ * @hw: pointer as obtained from ieee80211_alloc_hw()
+ * @txq: pointer obtained from station or virtual interface
  *
- * The function returns the offset where the next part of the
- * buffer starts, which may be @ielen if the entire (remainder)
- * of the buffer should be used.
+ * Returns the skb if successful, %NULL if no frame was available.
  */
-size_t ieee80211_ie_split(const u8 *ies, size_t ielen,
-                         const u8 *ids, int n_ids, size_t offset);
+struct sk_buff *ieee80211_tx_dequeue(struct ieee80211_hw *hw,
+                                    struct ieee80211_txq *txq);
 #endif /* MAC80211_H */
index c303588bb7670dcf3f90edf4fa0427f27ff1d1de..d2947c52dc670f817a661ad1c8ad7925c575c61a 100644 (file)
@@ -25,6 +25,7 @@ enum {
        FOU_CMD_UNSPEC,
        FOU_CMD_ADD,
        FOU_CMD_DEL,
+       FOU_CMD_GET,
 
        __FOU_CMD_MAX,
 };
index 654e38a9975948f981f35716e7b9eac8569f0f93..e891bcf325ca759c9b7498f29ec76aa946198d5e 100644 (file)
@@ -2799,8 +2799,7 @@ int proto_register(struct proto *prot, int alloc_slab)
                                kmem_cache_create(prot->twsk_prot->twsk_slab_name,
                                                  prot->twsk_prot->twsk_obj_size,
                                                  0,
-                                                 SLAB_HWCACHE_ALIGN |
-                                                       prot->slab_flags,
+                                                 prot->slab_flags,
                                                  NULL);
                        if (prot->twsk_prot->twsk_slab == NULL)
                                goto out_free_timewait_sock_slab_name;
index ff069f6597ace6302b46add285c787942645aac2..263710259774151e40fa67ba3aa9652d4a1e2955 100644 (file)
 #include <uapi/linux/fou.h>
 #include <uapi/linux/genetlink.h>
 
-static DEFINE_SPINLOCK(fou_lock);
-static LIST_HEAD(fou_list);
-
 struct fou {
        struct socket *sock;
        u8 protocol;
        u8 flags;
-       u16 port;
+       __be16 port;
+       u16 type;
        struct udp_offload udp_offloads;
        struct list_head list;
 };
@@ -37,6 +35,13 @@ struct fou_cfg {
        struct udp_port_cfg udp_config;
 };
 
+static unsigned int fou_net_id;
+
+struct fou_net {
+       struct list_head fou_list;
+       struct mutex fou_lock;
+};
+
 static inline struct fou *fou_from_sock(struct sock *sk)
 {
        return sk->sk_user_data;
@@ -387,20 +392,21 @@ out_unlock:
        return err;
 }
 
-static int fou_add_to_port_list(struct fou *fou)
+static int fou_add_to_port_list(struct net *net, struct fou *fou)
 {
+       struct fou_net *fn = net_generic(net, fou_net_id);
        struct fou *fout;
 
-       spin_lock(&fou_lock);
-       list_for_each_entry(fout, &fou_list, list) {
+       mutex_lock(&fn->fou_lock);
+       list_for_each_entry(fout, &fn->fou_list, list) {
                if (fou->port == fout->port) {
-                       spin_unlock(&fou_lock);
+                       mutex_unlock(&fn->fou_lock);
                        return -EALREADY;
                }
        }
 
-       list_add(&fou->list, &fou_list);
-       spin_unlock(&fou_lock);
+       list_add(&fou->list, &fn->fou_list);
+       mutex_unlock(&fn->fou_lock);
 
        return 0;
 }
@@ -410,14 +416,10 @@ static void fou_release(struct fou *fou)
        struct socket *sock = fou->sock;
        struct sock *sk = sock->sk;
 
-       udp_del_offload(&fou->udp_offloads);
-
+       if (sk->sk_family == AF_INET)
+               udp_del_offload(&fou->udp_offloads);
        list_del(&fou->list);
-
-       /* Remove hooks into tunnel socket */
-       sk->sk_user_data = NULL;
-
-       sock_release(sock);
+       udp_tunnel_sock_release(sock);
 
        kfree(fou);
 }
@@ -447,10 +449,10 @@ static int gue_encap_init(struct sock *sk, struct fou *fou, struct fou_cfg *cfg)
 static int fou_create(struct net *net, struct fou_cfg *cfg,
                      struct socket **sockp)
 {
-       struct fou *fou = NULL;
-       int err;
        struct socket *sock = NULL;
+       struct fou *fou = NULL;
        struct sock *sk;
+       int err;
 
        /* Open UDP socket */
        err = udp_sock_create(net, &cfg->udp_config, &sock);
@@ -486,6 +488,8 @@ static int fou_create(struct net *net, struct fou_cfg *cfg,
                goto error;
        }
 
+       fou->type = cfg->type;
+
        udp_sk(sk)->encap_type = 1;
        udp_encap_enable();
 
@@ -502,7 +506,7 @@ static int fou_create(struct net *net, struct fou_cfg *cfg,
                        goto error;
        }
 
-       err = fou_add_to_port_list(fou);
+       err = fou_add_to_port_list(net, fou);
        if (err)
                goto error;
 
@@ -514,27 +518,27 @@ static int fou_create(struct net *net, struct fou_cfg *cfg,
 error:
        kfree(fou);
        if (sock)
-               sock_release(sock);
+               udp_tunnel_sock_release(sock);
 
        return err;
 }
 
 static int fou_destroy(struct net *net, struct fou_cfg *cfg)
 {
-       struct fou *fou;
-       u16 port = cfg->udp_config.local_udp_port;
+       struct fou_net *fn = net_generic(net, fou_net_id);
+       __be16 port = cfg->udp_config.local_udp_port;
        int err = -EINVAL;
+       struct fou *fou;
 
-       spin_lock(&fou_lock);
-       list_for_each_entry(fou, &fou_list, list) {
+       mutex_lock(&fn->fou_lock);
+       list_for_each_entry(fou, &fn->fou_list, list) {
                if (fou->port == port) {
-                       udp_del_offload(&fou->udp_offloads);
                        fou_release(fou);
                        err = 0;
                        break;
                }
        }
-       spin_unlock(&fou_lock);
+       mutex_unlock(&fn->fou_lock);
 
        return err;
 }
@@ -573,7 +577,7 @@ static int parse_nl_config(struct genl_info *info,
        }
 
        if (info->attrs[FOU_ATTR_PORT]) {
-               u16 port = nla_get_u16(info->attrs[FOU_ATTR_PORT]);
+               __be16 port = nla_get_be16(info->attrs[FOU_ATTR_PORT]);
 
                cfg->udp_config.local_udp_port = port;
        }
@@ -592,6 +596,7 @@ static int parse_nl_config(struct genl_info *info,
 
 static int fou_nl_cmd_add_port(struct sk_buff *skb, struct genl_info *info)
 {
+       struct net *net = genl_info_net(info);
        struct fou_cfg cfg;
        int err;
 
@@ -599,16 +604,120 @@ static int fou_nl_cmd_add_port(struct sk_buff *skb, struct genl_info *info)
        if (err)
                return err;
 
-       return fou_create(&init_net, &cfg, NULL);
+       return fou_create(net, &cfg, NULL);
 }
 
 static int fou_nl_cmd_rm_port(struct sk_buff *skb, struct genl_info *info)
 {
+       struct net *net = genl_info_net(info);
+       struct fou_cfg cfg;
+       int err;
+
+       err = parse_nl_config(info, &cfg);
+       if (err)
+               return err;
+
+       return fou_destroy(net, &cfg);
+}
+
+static int fou_fill_info(struct fou *fou, struct sk_buff *msg)
+{
+       if (nla_put_u8(msg, FOU_ATTR_AF, fou->sock->sk->sk_family) ||
+           nla_put_be16(msg, FOU_ATTR_PORT, fou->port) ||
+           nla_put_u8(msg, FOU_ATTR_IPPROTO, fou->protocol) ||
+           nla_put_u8(msg, FOU_ATTR_TYPE, fou->type))
+               return -1;
+
+       if (fou->flags & FOU_F_REMCSUM_NOPARTIAL)
+               if (nla_put_flag(msg, FOU_ATTR_REMCSUM_NOPARTIAL))
+                       return -1;
+       return 0;
+}
+
+static int fou_dump_info(struct fou *fou, u32 portid, u32 seq,
+                        u32 flags, struct sk_buff *skb, u8 cmd)
+{
+       void *hdr;
+
+       hdr = genlmsg_put(skb, portid, seq, &fou_nl_family, flags, cmd);
+       if (!hdr)
+               return -ENOMEM;
+
+       if (fou_fill_info(fou, skb) < 0)
+               goto nla_put_failure;
+
+       genlmsg_end(skb, hdr);
+       return 0;
+
+nla_put_failure:
+       genlmsg_cancel(skb, hdr);
+       return -EMSGSIZE;
+}
+
+static int fou_nl_cmd_get_port(struct sk_buff *skb, struct genl_info *info)
+{
+       struct net *net = genl_info_net(info);
+       struct fou_net *fn = net_generic(net, fou_net_id);
+       struct sk_buff *msg;
        struct fou_cfg cfg;
+       struct fou *fout;
+       __be16 port;
+       int ret;
+
+       ret = parse_nl_config(info, &cfg);
+       if (ret)
+               return ret;
+       port = cfg.udp_config.local_udp_port;
+       if (port == 0)
+               return -EINVAL;
+
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+       if (!msg)
+               return -ENOMEM;
+
+       ret = -ESRCH;
+       mutex_lock(&fn->fou_lock);
+       list_for_each_entry(fout, &fn->fou_list, list) {
+               if (port == fout->port) {
+                       ret = fou_dump_info(fout, info->snd_portid,
+                                           info->snd_seq, 0, msg,
+                                           info->genlhdr->cmd);
+                       break;
+               }
+       }
+       mutex_unlock(&fn->fou_lock);
+       if (ret < 0)
+               goto out_free;
 
-       parse_nl_config(info, &cfg);
+       return genlmsg_reply(msg, info);
 
-       return fou_destroy(&init_net, &cfg);
+out_free:
+       nlmsg_free(msg);
+       return ret;
+}
+
+static int fou_nl_dump(struct sk_buff *skb, struct netlink_callback *cb)
+{
+       struct net *net = sock_net(skb->sk);
+       struct fou_net *fn = net_generic(net, fou_net_id);
+       struct fou *fout;
+       int idx = 0, ret;
+
+       mutex_lock(&fn->fou_lock);
+       list_for_each_entry(fout, &fn->fou_list, list) {
+               if (idx++ < cb->args[0])
+                       continue;
+               ret = fou_dump_info(fout, NETLINK_CB(cb->skb).portid,
+                                   cb->nlh->nlmsg_seq, NLM_F_MULTI,
+                                   skb, FOU_CMD_GET);
+               if (ret)
+                       goto done;
+       }
+       mutex_unlock(&fn->fou_lock);
+
+done:
+       cb->args[0] = idx;
+       return skb->len;
 }
 
 static const struct genl_ops fou_nl_ops[] = {
@@ -624,6 +733,12 @@ static const struct genl_ops fou_nl_ops[] = {
                .policy = fou_nl_policy,
                .flags = GENL_ADMIN_PERM,
        },
+       {
+               .cmd = FOU_CMD_GET,
+               .doit = fou_nl_cmd_get_port,
+               .dumpit = fou_nl_dump,
+               .policy = fou_nl_policy,
+       },
 };
 
 size_t fou_encap_hlen(struct ip_tunnel_encap *e)
@@ -820,38 +935,63 @@ static void ip_tunnel_encap_del_fou_ops(void)
 
 #endif
 
+static __net_init int fou_init_net(struct net *net)
+{
+       struct fou_net *fn = net_generic(net, fou_net_id);
+
+       INIT_LIST_HEAD(&fn->fou_list);
+       mutex_init(&fn->fou_lock);
+       return 0;
+}
+
+static __net_exit void fou_exit_net(struct net *net)
+{
+       struct fou_net *fn = net_generic(net, fou_net_id);
+       struct fou *fou, *next;
+
+       /* Close all the FOU sockets */
+       mutex_lock(&fn->fou_lock);
+       list_for_each_entry_safe(fou, next, &fn->fou_list, list)
+               fou_release(fou);
+       mutex_unlock(&fn->fou_lock);
+}
+
+static struct pernet_operations fou_net_ops = {
+       .init = fou_init_net,
+       .exit = fou_exit_net,
+       .id   = &fou_net_id,
+       .size = sizeof(struct fou_net),
+};
+
 static int __init fou_init(void)
 {
        int ret;
 
+       ret = register_pernet_device(&fou_net_ops);
+       if (ret)
+               goto exit;
+
        ret = genl_register_family_with_ops(&fou_nl_family,
                                            fou_nl_ops);
-
        if (ret < 0)
-               goto exit;
+               goto unregister;
 
        ret = ip_tunnel_encap_add_fou_ops();
-       if (ret < 0)
-               genl_unregister_family(&fou_nl_family);
+       if (ret == 0)
+               return 0;
 
+       genl_unregister_family(&fou_nl_family);
+unregister:
+       unregister_pernet_device(&fou_net_ops);
 exit:
        return ret;
 }
 
 static void __exit fou_fini(void)
 {
-       struct fou *fou, *next;
-
        ip_tunnel_encap_del_fou_ops();
-
        genl_unregister_family(&fou_nl_family);
-
-       /* Close all the FOU sockets */
-
-       spin_lock(&fou_lock);
-       list_for_each_entry_safe(fou, next, &fou_list, list)
-               fou_release(fou);
-       spin_unlock(&fou_lock);
+       unregister_pernet_device(&fou_net_ops);
 }
 
 module_init(fou_init);
index 20522492d8cc80028b81dc772d32468951402ac6..cce9d425c71812f0f6184a0c953d2d39f6d7eb83 100644 (file)
@@ -188,6 +188,43 @@ ieee80211_wake_queue_agg(struct ieee80211_sub_if_data *sdata, int tid)
        __release(agg_queue);
 }
 
+static void
+ieee80211_agg_stop_txq(struct sta_info *sta, int tid)
+{
+       struct ieee80211_txq *txq = sta->sta.txq[tid];
+       struct txq_info *txqi;
+
+       if (!txq)
+               return;
+
+       txqi = to_txq_info(txq);
+
+       /* Lock here to protect against further seqno updates on dequeue */
+       spin_lock_bh(&txqi->queue.lock);
+       set_bit(IEEE80211_TXQ_STOP, &txqi->flags);
+       spin_unlock_bh(&txqi->queue.lock);
+}
+
+static void
+ieee80211_agg_start_txq(struct sta_info *sta, int tid, bool enable)
+{
+       struct ieee80211_txq *txq = sta->sta.txq[tid];
+       struct txq_info *txqi;
+
+       if (!txq)
+               return;
+
+       txqi = to_txq_info(txq);
+
+       if (enable)
+               set_bit(IEEE80211_TXQ_AMPDU, &txqi->flags);
+       else
+               clear_bit(IEEE80211_TXQ_AMPDU, &txqi->flags);
+
+       clear_bit(IEEE80211_TXQ_STOP, &txqi->flags);
+       drv_wake_tx_queue(sta->sdata->local, txqi);
+}
+
 /*
  * splice packets from the STA's pending to the local pending,
  * requires a call to ieee80211_agg_splice_finish later
@@ -247,6 +284,7 @@ static void ieee80211_remove_tid_tx(struct sta_info *sta, int tid)
        ieee80211_assign_tid_tx(sta, tid, NULL);
 
        ieee80211_agg_splice_finish(sta->sdata, tid);
+       ieee80211_agg_start_txq(sta, tid, false);
 
        kfree_rcu(tid_tx, rcu_head);
 }
@@ -418,6 +456,8 @@ void ieee80211_tx_ba_session_handle_start(struct sta_info *sta, int tid)
         */
        clear_bit(HT_AGG_STATE_WANT_START, &tid_tx->state);
 
+       ieee80211_agg_stop_txq(sta, tid);
+
        /*
         * Make sure no packets are being processed. This ensures that
         * we have a valid starting sequence number and that in-flight
@@ -440,6 +480,8 @@ void ieee80211_tx_ba_session_handle_start(struct sta_info *sta, int tid)
                ieee80211_agg_splice_finish(sdata, tid);
                spin_unlock_bh(&sta->lock);
 
+               ieee80211_agg_start_txq(sta, tid, false);
+
                kfree_rcu(tid_tx, rcu_head);
                return;
        }
@@ -669,6 +711,8 @@ static void ieee80211_agg_tx_operational(struct ieee80211_local *local,
        ieee80211_agg_splice_finish(sta->sdata, tid);
 
        spin_unlock_bh(&sta->lock);
+
+       ieee80211_agg_start_txq(sta, tid, true);
 }
 
 void ieee80211_start_tx_ba_cb(struct ieee80211_vif *vif, u8 *ra, u16 tid)
index 0a39d3db951a4a411448039c0d80190d50f299e2..26e1ca8a474af338685debf3d89b3cc3ad6d9986 100644 (file)
@@ -1367,4 +1367,16 @@ drv_tdls_recv_channel_switch(struct ieee80211_local *local,
        trace_drv_return_void(local);
 }
 
+static inline void drv_wake_tx_queue(struct ieee80211_local *local,
+                                    struct txq_info *txq)
+{
+       struct ieee80211_sub_if_data *sdata = vif_to_sdata(txq->txq.vif);
+
+       if (!check_sdata_in_driver(sdata))
+               return;
+
+       trace_drv_wake_tx_queue(local, sdata, txq);
+       local->ops->wake_tx_queue(&local->hw, &txq->txq);
+}
+
 #endif /* __MAC80211_DRIVER_OPS */
index 487f5e2a9283bd1819adb0f61fead485f7fa6b0f..ab46ab4a72498fd04f1c12ac6bb44f867d86869b 100644 (file)
@@ -26,6 +26,7 @@
 #include <linux/etherdevice.h>
 #include <linux/leds.h>
 #include <linux/idr.h>
+#include <linux/rhashtable.h>
 #include <net/ieee80211_radiotap.h>
 #include <net/cfg80211.h>
 #include <net/mac80211.h>
@@ -810,6 +811,19 @@ struct mac80211_qos_map {
        struct rcu_head rcu_head;
 };
 
+enum txq_info_flags {
+       IEEE80211_TXQ_STOP,
+       IEEE80211_TXQ_AMPDU,
+};
+
+struct txq_info {
+       struct sk_buff_head queue;
+       unsigned long flags;
+
+       /* keep last! */
+       struct ieee80211_txq txq;
+};
+
 struct ieee80211_sub_if_data {
        struct list_head list;
 
@@ -852,6 +866,7 @@ struct ieee80211_sub_if_data {
        bool control_port_no_encrypt;
        int encrypt_headroom;
 
+       atomic_t txqs_len[IEEE80211_NUM_ACS];
        struct ieee80211_tx_queue_params tx_conf[IEEE80211_NUM_ACS];
        struct mac80211_qos_map __rcu *qos_map;
 
@@ -1187,7 +1202,7 @@ struct ieee80211_local {
        spinlock_t tim_lock;
        unsigned long num_sta;
        struct list_head sta_list;
-       struct sta_info __rcu *sta_hash[STA_HASH_SIZE];
+       struct rhashtable sta_hash;
        struct timer_list sta_cleanup;
        int sta_generation;
 
@@ -1449,6 +1464,10 @@ static inline struct ieee80211_local *hw_to_local(
        return container_of(hw, struct ieee80211_local, hw);
 }
 
+static inline struct txq_info *to_txq_info(struct ieee80211_txq *txq)
+{
+       return container_of(txq, struct txq_info, txq);
+}
 
 static inline int ieee80211_bssid_match(const u8 *raddr, const u8 *addr)
 {
@@ -1905,6 +1924,9 @@ static inline bool ieee80211_can_run_worker(struct ieee80211_local *local)
        return true;
 }
 
+void ieee80211_init_tx_queue(struct ieee80211_sub_if_data *sdata,
+                            struct sta_info *sta,
+                            struct txq_info *txq, int tid);
 void ieee80211_send_auth(struct ieee80211_sub_if_data *sdata,
                         u16 transaction, u16 auth_alg, u16 status,
                         const u8 *extra, size_t extra_len, const u8 *bssid,
@@ -1943,10 +1965,6 @@ int __ieee80211_request_smps_ap(struct ieee80211_sub_if_data *sdata,
 void ieee80211_recalc_smps(struct ieee80211_sub_if_data *sdata);
 void ieee80211_recalc_min_chandef(struct ieee80211_sub_if_data *sdata);
 
-size_t ieee80211_ie_split_ric(const u8 *ies, size_t ielen,
-                             const u8 *ids, int n_ids,
-                             const u8 *after_ric, int n_after_ric,
-                             size_t offset);
 size_t ieee80211_ie_split_vendor(const u8 *ies, size_t ielen, size_t offset);
 u8 *ieee80211_ie_build_ht_cap(u8 *pos, struct ieee80211_sta_ht_cap *ht_cap,
                              u16 cap);
index a0cd97fd0c49075d8682fadf734c18f3c018f13f..b4ac596a7cb76205cf39d935708d3383490c2b73 100644 (file)
@@ -969,6 +969,13 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata,
        }
        spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
 
+       if (sdata->vif.txq) {
+               struct txq_info *txqi = to_txq_info(sdata->vif.txq);
+
+               ieee80211_purge_tx_queue(&local->hw, &txqi->queue);
+               atomic_set(&sdata->txqs_len[txqi->txq.ac], 0);
+       }
+
        if (local->open_count == 0)
                ieee80211_clear_tx_pending(local);
 
@@ -1654,6 +1661,7 @@ int ieee80211_if_add(struct ieee80211_local *local, const char *name,
 {
        struct net_device *ndev = NULL;
        struct ieee80211_sub_if_data *sdata = NULL;
+       struct txq_info *txqi;
        int ret, i;
        int txqs = 1;
 
@@ -1673,10 +1681,18 @@ int ieee80211_if_add(struct ieee80211_local *local, const char *name,
                ieee80211_assign_perm_addr(local, wdev->address, type);
                memcpy(sdata->vif.addr, wdev->address, ETH_ALEN);
        } else {
+               int size = ALIGN(sizeof(*sdata) + local->hw.vif_data_size,
+                                sizeof(void *));
+               int txq_size = 0;
+
+               if (local->ops->wake_tx_queue)
+                       txq_size += sizeof(struct txq_info) +
+                                   local->hw.txq_data_size;
+
                if (local->hw.queues >= IEEE80211_NUM_ACS)
                        txqs = IEEE80211_NUM_ACS;
 
-               ndev = alloc_netdev_mqs(sizeof(*sdata) + local->hw.vif_data_size,
+               ndev = alloc_netdev_mqs(size + txq_size,
                                        name, name_assign_type,
                                        ieee80211_if_setup, txqs, 1);
                if (!ndev)
@@ -1711,6 +1727,11 @@ int ieee80211_if_add(struct ieee80211_local *local, const char *name,
                memcpy(sdata->vif.addr, ndev->dev_addr, ETH_ALEN);
                memcpy(sdata->name, ndev->name, IFNAMSIZ);
 
+               if (txq_size) {
+                       txqi = netdev_priv(ndev) + size;
+                       ieee80211_init_tx_queue(sdata, NULL, txqi, 0);
+               }
+
                sdata->dev = ndev;
        }
 
index 4977967c8b0076a5960f3fc74e37271583d983fc..df3051d96afffbd1257442a16c3a2f198629fbcc 100644 (file)
@@ -557,6 +557,9 @@ struct ieee80211_hw *ieee80211_alloc_hw_nm(size_t priv_data_len,
 
        local = wiphy_priv(wiphy);
 
+       if (sta_info_init(local))
+               goto err_free;
+
        local->hw.wiphy = wiphy;
 
        local->hw.priv = (char *)local + ALIGN(sizeof(*local), NETDEV_ALIGN);
@@ -629,8 +632,6 @@ struct ieee80211_hw *ieee80211_alloc_hw_nm(size_t priv_data_len,
        spin_lock_init(&local->ack_status_lock);
        idr_init(&local->ack_status_frames);
 
-       sta_info_init(local);
-
        for (i = 0; i < IEEE80211_MAX_QUEUES; i++) {
                skb_queue_head_init(&local->pending[i]);
                atomic_set(&local->agg_queue_stop[i], 0);
@@ -650,6 +651,9 @@ struct ieee80211_hw *ieee80211_alloc_hw_nm(size_t priv_data_len,
        ieee80211_roc_setup(local);
 
        return &local->hw;
+ err_free:
+       wiphy_free(wiphy);
+       return NULL;
 }
 EXPORT_SYMBOL(ieee80211_alloc_hw_nm);
 
@@ -1035,6 +1039,9 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
 
        local->dynamic_ps_forced_timeout = -1;
 
+       if (!local->hw.txq_ac_max_pending)
+               local->hw.txq_ac_max_pending = 64;
+
        result = ieee80211_wep_init(local);
        if (result < 0)
                wiphy_debug(local->hw.wiphy, "Failed to initialize wep: %d\n",
@@ -1173,7 +1180,6 @@ void ieee80211_unregister_hw(struct ieee80211_hw *hw)
 
        destroy_workqueue(local->workqueue);
        wiphy_unregister(local->hw.wiphy);
-       sta_info_stop(local);
        ieee80211_wep_free(local);
        ieee80211_led_exit(local);
        kfree(local->int_scan_req);
index 00103f36dcbf4f5866c40892cd76882ce8022be8..26053bf2faa8ff441d0ff0c9905536bccf914281 100644 (file)
@@ -1348,15 +1348,15 @@ static u32 ieee80211_handle_pwr_constr(struct ieee80211_sub_if_data *sdata,
         */
        if (has_80211h_pwr &&
            (!has_cisco_pwr || pwr_level_80211h <= pwr_level_cisco)) {
-               sdata_info(sdata,
-                          "Limiting TX power to %d (%d - %d) dBm as advertised by %pM\n",
-                          pwr_level_80211h, chan_pwr, pwr_reduction_80211h,
-                          sdata->u.mgd.bssid);
+               sdata_dbg(sdata,
+                         "Limiting TX power to %d (%d - %d) dBm as advertised by %pM\n",
+                         pwr_level_80211h, chan_pwr, pwr_reduction_80211h,
+                         sdata->u.mgd.bssid);
                new_ap_level = pwr_level_80211h;
        } else {  /* has_cisco_pwr is always true here. */
-               sdata_info(sdata,
-                          "Limiting TX power to %d dBm as advertised by %pM\n",
-                          pwr_level_cisco, sdata->u.mgd.bssid);
+               sdata_dbg(sdata,
+                         "Limiting TX power to %d dBm as advertised by %pM\n",
+                         pwr_level_cisco, sdata->u.mgd.bssid);
                new_ap_level = pwr_level_cisco;
        }
 
index ef6e8a6c4253c72f6f2398b733e8c427f5b59953..247552a7f6c2f23a1e4bc89b647d8d37680bf2c3 100644 (file)
@@ -69,14 +69,39 @@ rix_to_ndx(struct minstrel_sta_info *mi, int rix)
        return i;
 }
 
+/* return current EMWA throughput */
+int minstrel_get_tp_avg(struct minstrel_rate *mr, int prob_ewma)
+{
+       int usecs;
+
+       usecs = mr->perfect_tx_time;
+       if (!usecs)
+               usecs = 1000000;
+
+       /* reset thr. below 10% success */
+       if (mr->stats.prob_ewma < MINSTREL_FRAC(10, 100))
+               return 0;
+
+       if (prob_ewma > MINSTREL_FRAC(90, 100))
+               return MINSTREL_TRUNC(100000 * (MINSTREL_FRAC(90, 100) / usecs));
+       else
+               return MINSTREL_TRUNC(100000 * (prob_ewma / usecs));
+}
+
 /* find & sort topmost throughput rates */
 static inline void
 minstrel_sort_best_tp_rates(struct minstrel_sta_info *mi, int i, u8 *tp_list)
 {
        int j = MAX_THR_RATES;
+       struct minstrel_rate_stats *tmp_mrs = &mi->r[j - 1].stats;
+       struct minstrel_rate_stats *cur_mrs = &mi->r[i].stats;
 
-       while (j > 0 && mi->r[i].stats.cur_tp > mi->r[tp_list[j - 1]].stats.cur_tp)
+       while (j > 0 && (minstrel_get_tp_avg(&mi->r[i], cur_mrs->prob_ewma) >
+              minstrel_get_tp_avg(&mi->r[tp_list[j - 1]], tmp_mrs->prob_ewma))) {
                j--;
+               tmp_mrs = &mi->r[tp_list[j - 1]].stats;
+       }
+
        if (j < MAX_THR_RATES - 1)
                memmove(&tp_list[j + 1], &tp_list[j], MAX_THR_RATES - (j + 1));
        if (j < MAX_THR_RATES)
@@ -127,13 +152,47 @@ minstrel_update_rates(struct minstrel_priv *mp, struct minstrel_sta_info *mi)
        rate_control_set_rates(mp->hw, mi->sta, ratetbl);
 }
 
+/*
+* Recalculate statistics and counters of a given rate
+*/
+void
+minstrel_calc_rate_stats(struct minstrel_rate_stats *mrs)
+{
+       if (unlikely(mrs->attempts > 0)) {
+               mrs->sample_skipped = 0;
+               mrs->cur_prob = MINSTREL_FRAC(mrs->success, mrs->attempts);
+               if (unlikely(!mrs->att_hist)) {
+                       mrs->prob_ewma = mrs->cur_prob;
+               } else {
+                       /* update exponential weighted moving variance */
+                       mrs->prob_ewmsd = minstrel_ewmsd(mrs->prob_ewmsd,
+                                                        mrs->cur_prob,
+                                                        mrs->prob_ewma,
+                                                        EWMA_LEVEL);
+
+                       /*update exponential weighted moving avarage */
+                       mrs->prob_ewma = minstrel_ewma(mrs->prob_ewma,
+                                                      mrs->cur_prob,
+                                                      EWMA_LEVEL);
+               }
+               mrs->att_hist += mrs->attempts;
+               mrs->succ_hist += mrs->success;
+       } else {
+               mrs->sample_skipped++;
+       }
+
+       mrs->last_success = mrs->success;
+       mrs->last_attempts = mrs->attempts;
+       mrs->success = 0;
+       mrs->attempts = 0;
+}
+
 static void
 minstrel_update_stats(struct minstrel_priv *mp, struct minstrel_sta_info *mi)
 {
        u8 tmp_tp_rate[MAX_THR_RATES];
        u8 tmp_prob_rate = 0;
-       u32 usecs;
-       int i;
+       int i, tmp_cur_tp, tmp_prob_tp;
 
        for (i = 0; i < MAX_THR_RATES; i++)
            tmp_tp_rate[i] = 0;
@@ -141,38 +200,15 @@ minstrel_update_stats(struct minstrel_priv *mp, struct minstrel_sta_info *mi)
        for (i = 0; i < mi->n_rates; i++) {
                struct minstrel_rate *mr = &mi->r[i];
                struct minstrel_rate_stats *mrs = &mi->r[i].stats;
+               struct minstrel_rate_stats *tmp_mrs = &mi->r[tmp_prob_rate].stats;
 
-               usecs = mr->perfect_tx_time;
-               if (!usecs)
-                       usecs = 1000000;
-
-               if (unlikely(mrs->attempts > 0)) {
-                       mrs->sample_skipped = 0;
-                       mrs->cur_prob = MINSTREL_FRAC(mrs->success,
-                                                     mrs->attempts);
-                       mrs->succ_hist += mrs->success;
-                       mrs->att_hist += mrs->attempts;
-                       mrs->probability = minstrel_ewma(mrs->probability,
-                                                        mrs->cur_prob,
-                                                        EWMA_LEVEL);
-               } else
-                       mrs->sample_skipped++;
-
-               mrs->last_success = mrs->success;
-               mrs->last_attempts = mrs->attempts;
-               mrs->success = 0;
-               mrs->attempts = 0;
-
-               /* Update throughput per rate, reset thr. below 10% success */
-               if (mrs->probability < MINSTREL_FRAC(10, 100))
-                       mrs->cur_tp = 0;
-               else
-                       mrs->cur_tp = mrs->probability * (1000000 / usecs);
+               /* Update statistics of success probability per rate */
+               minstrel_calc_rate_stats(mrs);
 
                /* Sample less often below the 10% chance of success.
                 * Sample less often above the 95% chance of success. */
-               if (mrs->probability > MINSTREL_FRAC(95, 100) ||
-                   mrs->probability < MINSTREL_FRAC(10, 100)) {
+               if (mrs->prob_ewma > MINSTREL_FRAC(95, 100) ||
+                   mrs->prob_ewma < MINSTREL_FRAC(10, 100)) {
                        mr->adjusted_retry_count = mrs->retry_count >> 1;
                        if (mr->adjusted_retry_count > 2)
                                mr->adjusted_retry_count = 2;
@@ -192,11 +228,14 @@ minstrel_update_stats(struct minstrel_priv *mp, struct minstrel_sta_info *mi)
                 * choose the maximum throughput rate as max_prob_rate
                 * (2) if all success probabilities < 95%, the rate with
                 * highest success probability is chosen as max_prob_rate */
-               if (mrs->probability >= MINSTREL_FRAC(95, 100)) {
-                       if (mrs->cur_tp >= mi->r[tmp_prob_rate].stats.cur_tp)
+               if (mrs->prob_ewma >= MINSTREL_FRAC(95, 100)) {
+                       tmp_cur_tp = minstrel_get_tp_avg(mr, mrs->prob_ewma);
+                       tmp_prob_tp = minstrel_get_tp_avg(&mi->r[tmp_prob_rate],
+                                                         tmp_mrs->prob_ewma);
+                       if (tmp_cur_tp >= tmp_prob_tp)
                                tmp_prob_rate = i;
                } else {
-                       if (mrs->probability >= mi->r[tmp_prob_rate].stats.probability)
+                       if (mrs->prob_ewma >= tmp_mrs->prob_ewma)
                                tmp_prob_rate = i;
                }
        }
@@ -215,7 +254,7 @@ minstrel_update_stats(struct minstrel_priv *mp, struct minstrel_sta_info *mi)
 #endif
 
        /* Reset update timer */
-       mi->stats_update = jiffies;
+       mi->last_stats_update = jiffies;
 
        minstrel_update_rates(mp, mi);
 }
@@ -253,7 +292,7 @@ minstrel_tx_status(void *priv, struct ieee80211_supported_band *sband,
        if (mi->sample_deferred > 0)
                mi->sample_deferred--;
 
-       if (time_after(jiffies, mi->stats_update +
+       if (time_after(jiffies, mi->last_stats_update +
                                (mp->update_interval * HZ) / 1000))
                minstrel_update_stats(mp, mi);
 }
@@ -385,7 +424,7 @@ minstrel_get_rate(void *priv, struct ieee80211_sta *sta,
         * has a probability of >95%, we shouldn't be attempting
         * to use it, as this only wastes precious airtime */
        if (!mrr_capable &&
-          (mi->r[ndx].stats.probability > MINSTREL_FRAC(95, 100)))
+          (mi->r[ndx].stats.prob_ewma > MINSTREL_FRAC(95, 100)))
                return;
 
        mi->prev_sample = true;
@@ -519,7 +558,7 @@ minstrel_rate_init(void *priv, struct ieee80211_supported_band *sband,
        }
 
        mi->n_rates = n;
-       mi->stats_update = jiffies;
+       mi->last_stats_update = jiffies;
 
        init_sample_table(mi);
        minstrel_update_rates(mp, mi);
@@ -553,7 +592,7 @@ minstrel_alloc_sta(void *priv, struct ieee80211_sta *sta, gfp_t gfp)
        if (!mi->sample_table)
                goto error1;
 
-       mi->stats_update = jiffies;
+       mi->last_stats_update = jiffies;
        return mi;
 
 error1:
@@ -663,12 +702,18 @@ minstrel_free(void *priv)
 static u32 minstrel_get_expected_throughput(void *priv_sta)
 {
        struct minstrel_sta_info *mi = priv_sta;
+       struct minstrel_rate_stats *tmp_mrs;
        int idx = mi->max_tp_rate[0];
+       int tmp_cur_tp;
 
        /* convert pkt per sec in kbps (1200 is the average pkt size used for
         * computing cur_tp
         */
-       return MINSTREL_TRUNC(mi->r[idx].stats.cur_tp) * 1200 * 8 / 1024;
+       tmp_mrs = &mi->r[idx].stats;
+       tmp_cur_tp = minstrel_get_tp_avg(&mi->r[idx], tmp_mrs->prob_ewma);
+       tmp_cur_tp = tmp_cur_tp * 1200 * 8 / 1024;
+
+       return tmp_cur_tp;
 }
 
 const struct rate_control_ops mac80211_minstrel = {
index 410efe620c579cb2d3eaa279384b7d0bf3907846..c230bbe93262b0affc476c01b8d1db9ab7dfe054 100644 (file)
@@ -13,7 +13,6 @@
 #define EWMA_DIV       128
 #define SAMPLE_COLUMNS 10      /* number of columns in sample table */
 
-
 /* scaled fraction values */
 #define MINSTREL_SCALE  16
 #define MINSTREL_FRAC(val, div) (((val) << MINSTREL_SCALE) / div)
 
 /*
  * Perform EWMA (Exponentially Weighted Moving Average) calculation
 */
+ */
 static inline int
 minstrel_ewma(int old, int new, int weight)
 {
-       return (new * (EWMA_DIV - weight) + old * weight) / EWMA_DIV;
+       int diff, incr;
+
+       diff = new - old;
+       incr = (EWMA_DIV - weight) * diff / EWMA_DIV;
+
+       return old + incr;
+}
+
+/*
+ * Perform EWMSD (Exponentially Weighted Moving Standard Deviation) calculation
+ */
+static inline int
+minstrel_ewmsd(int old_ewmsd, int cur_prob, int prob_ewma, int weight)
+{
+       int diff, incr, tmp_var;
+
+       /* calculate exponential weighted moving variance */
+       diff = MINSTREL_TRUNC((cur_prob - prob_ewma) * 1000000);
+       incr = (EWMA_DIV - weight) * diff / EWMA_DIV;
+       tmp_var = old_ewmsd * old_ewmsd;
+       tmp_var = weight * (tmp_var + diff * incr / 1000000) / EWMA_DIV;
+
+       /* return standard deviation */
+       return (u16) int_sqrt(tmp_var);
 }
 
 struct minstrel_rate_stats {
@@ -39,11 +61,13 @@ struct minstrel_rate_stats {
        /* total attempts/success counters */
        u64 att_hist, succ_hist;
 
-       /* current throughput */
-       unsigned int cur_tp;
-
-       /* packet delivery probabilities */
-       unsigned int cur_prob, probability;
+       /* statistis of packet delivery probability
+        *  cur_prob  - current prob within last update intervall
+        *  prob_ewma - exponential weighted moving average of prob
+        *  prob_ewmsd - exp. weighted moving standard deviation of prob */
+       unsigned int cur_prob;
+       unsigned int prob_ewma;
+       u16 prob_ewmsd;
 
        /* maximum retry counts */
        u8 retry_count;
@@ -71,7 +95,7 @@ struct minstrel_rate {
 struct minstrel_sta_info {
        struct ieee80211_sta *sta;
 
-       unsigned long stats_update;
+       unsigned long last_stats_update;
        unsigned int sp_ack_dur;
        unsigned int rate_avg;
 
@@ -95,6 +119,7 @@ struct minstrel_sta_info {
 
 #ifdef CONFIG_MAC80211_DEBUGFS
        struct dentry *dbg_stats;
+       struct dentry *dbg_stats_csv;
 #endif
 };
 
@@ -121,7 +146,6 @@ struct minstrel_priv {
        u32 fixed_rate_idx;
        struct dentry *dbg_fixed_rate;
 #endif
-
 };
 
 struct minstrel_debugfs_info {
@@ -133,8 +157,13 @@ extern const struct rate_control_ops mac80211_minstrel;
 void minstrel_add_sta_debugfs(void *priv, void *priv_sta, struct dentry *dir);
 void minstrel_remove_sta_debugfs(void *priv, void *priv_sta);
 
+/* Recalculate success probabilities and counters for a given rate using EWMA */
+void minstrel_calc_rate_stats(struct minstrel_rate_stats *mrs);
+int minstrel_get_tp_avg(struct minstrel_rate *mr, int prob_ewma);
+
 /* debugfs */
 int minstrel_stats_open(struct inode *inode, struct file *file);
+int minstrel_stats_csv_open(struct inode *inode, struct file *file);
 ssize_t minstrel_stats_read(struct file *file, char __user *buf, size_t len, loff_t *ppos);
 int minstrel_stats_release(struct inode *inode, struct file *file);
 
index 2acab1bcaa4b377012a31c6354ceeb61fadbb171..1db5f7c3318ada6a1a1aede778dee49ccc99e824 100644 (file)
 #include <net/mac80211.h>
 #include "rc80211_minstrel.h"
 
+ssize_t
+minstrel_stats_read(struct file *file, char __user *buf, size_t len, loff_t *ppos)
+{
+       struct minstrel_debugfs_info *ms;
+
+       ms = file->private_data;
+       return simple_read_from_buffer(buf, len, ppos, ms->buf, ms->len);
+}
+
+int
+minstrel_stats_release(struct inode *inode, struct file *file)
+{
+       kfree(file->private_data);
+       return 0;
+}
+
 int
 minstrel_stats_open(struct inode *inode, struct file *file)
 {
        struct minstrel_sta_info *mi = inode->i_private;
        struct minstrel_debugfs_info *ms;
-       unsigned int i, tp, prob, eprob;
+       unsigned int i, tp_max, tp_avg, prob, eprob;
        char *p;
 
        ms = kmalloc(2048, GFP_KERNEL);
@@ -68,8 +84,14 @@ minstrel_stats_open(struct inode *inode, struct file *file)
 
        file->private_data = ms;
        p = ms->buf;
-       p += sprintf(p, "rate          tpt eprob *prob"
-                       "  *ok(*cum)        ok(      cum)\n");
+       p += sprintf(p, "\n");
+       p += sprintf(p, "best   __________rate_________    ______"
+                       "statistics______    ________last_______    "
+                       "______sum-of________\n");
+       p += sprintf(p, "rate  [name idx airtime max_tp]  [ Ã¸(tp) Ã¸(prob) "
+                       "sd(prob)]  [prob.|retry|suc|att]  "
+                       "[#success | #attempts]\n");
+
        for (i = 0; i < mi->n_rates; i++) {
                struct minstrel_rate *mr = &mi->r[i];
                struct minstrel_rate_stats *mrs = &mi->r[i].stats;
@@ -79,18 +101,26 @@ minstrel_stats_open(struct inode *inode, struct file *file)
                *(p++) = (i == mi->max_tp_rate[2]) ? 'C' : ' ';
                *(p++) = (i == mi->max_tp_rate[3]) ? 'D' : ' ';
                *(p++) = (i == mi->max_prob_rate) ? 'P' : ' ';
-               p += sprintf(p, "%3u%s", mr->bitrate / 2,
+
+               p += sprintf(p, " %3u%s ", mr->bitrate / 2,
                                (mr->bitrate & 1 ? ".5" : "  "));
+               p += sprintf(p, "%3u  ", i);
+               p += sprintf(p, "%6u ", mr->perfect_tx_time);
 
-               tp = MINSTREL_TRUNC(mrs->cur_tp / 10);
+               tp_max = minstrel_get_tp_avg(mr, MINSTREL_FRAC(100,100));
+               tp_avg = minstrel_get_tp_avg(mr, mrs->prob_ewma);
                prob = MINSTREL_TRUNC(mrs->cur_prob * 1000);
-               eprob = MINSTREL_TRUNC(mrs->probability * 1000);
+               eprob = MINSTREL_TRUNC(mrs->prob_ewma * 1000);
 
-               p += sprintf(p, " %4u.%1u %3u.%1u %3u.%1u"
-                               " %4u(%4u) %9llu(%9llu)\n",
-                               tp / 10, tp % 10,
+               p += sprintf(p, "%4u.%1u   %4u.%1u   %3u.%1u    %3u.%1u"
+                               "     %3u.%1u %3u   %3u %-3u   "
+                               "%9llu   %-9llu\n",
+                               tp_max / 10, tp_max % 10,
+                               tp_avg / 10, tp_avg % 10,
                                eprob / 10, eprob % 10,
+                               mrs->prob_ewmsd / 10, mrs->prob_ewmsd % 10,
                                prob / 10, prob % 10,
+                               mrs->retry_count,
                                mrs->last_success,
                                mrs->last_attempts,
                                (unsigned long long)mrs->succ_hist,
@@ -107,25 +137,75 @@ minstrel_stats_open(struct inode *inode, struct file *file)
        return 0;
 }
 
-ssize_t
-minstrel_stats_read(struct file *file, char __user *buf, size_t len, loff_t *ppos)
+static const struct file_operations minstrel_stat_fops = {
+       .owner = THIS_MODULE,
+       .open = minstrel_stats_open,
+       .read = minstrel_stats_read,
+       .release = minstrel_stats_release,
+       .llseek = default_llseek,
+};
+
+int
+minstrel_stats_csv_open(struct inode *inode, struct file *file)
 {
+       struct minstrel_sta_info *mi = inode->i_private;
        struct minstrel_debugfs_info *ms;
+       unsigned int i, tp_max, tp_avg, prob, eprob;
+       char *p;
 
-       ms = file->private_data;
-       return simple_read_from_buffer(buf, len, ppos, ms->buf, ms->len);
-}
+       ms = kmalloc(2048, GFP_KERNEL);
+       if (!ms)
+               return -ENOMEM;
+
+       file->private_data = ms;
+       p = ms->buf;
+
+       for (i = 0; i < mi->n_rates; i++) {
+               struct minstrel_rate *mr = &mi->r[i];
+               struct minstrel_rate_stats *mrs = &mi->r[i].stats;
+
+               p += sprintf(p, "%s" ,((i == mi->max_tp_rate[0]) ? "A" : ""));
+               p += sprintf(p, "%s" ,((i == mi->max_tp_rate[1]) ? "B" : ""));
+               p += sprintf(p, "%s" ,((i == mi->max_tp_rate[2]) ? "C" : ""));
+               p += sprintf(p, "%s" ,((i == mi->max_tp_rate[3]) ? "D" : ""));
+               p += sprintf(p, "%s" ,((i == mi->max_prob_rate) ? "P" : ""));
+
+               p += sprintf(p, ",%u%s", mr->bitrate / 2,
+                               (mr->bitrate & 1 ? ".5," : ","));
+               p += sprintf(p, "%u,", i);
+               p += sprintf(p, "%u,",mr->perfect_tx_time);
+
+               tp_max = minstrel_get_tp_avg(mr, MINSTREL_FRAC(100,100));
+               tp_avg = minstrel_get_tp_avg(mr, mrs->prob_ewma);
+               prob = MINSTREL_TRUNC(mrs->cur_prob * 1000);
+               eprob = MINSTREL_TRUNC(mrs->prob_ewma * 1000);
+
+               p += sprintf(p, "%u.%u,%u.%u,%u.%u,%u.%u,%u.%u,%u,%u,%u,"
+                               "%llu,%llu,%d,%d\n",
+                               tp_max / 10, tp_max % 10,
+                               tp_avg / 10, tp_avg % 10,
+                               eprob / 10, eprob % 10,
+                               mrs->prob_ewmsd / 10, mrs->prob_ewmsd % 10,
+                               prob / 10, prob % 10,
+                               mrs->retry_count,
+                               mrs->last_success,
+                               mrs->last_attempts,
+                               (unsigned long long)mrs->succ_hist,
+                               (unsigned long long)mrs->att_hist,
+                               mi->total_packets - mi->sample_packets,
+                               mi->sample_packets);
+
+       }
+       ms->len = p - ms->buf;
+
+       WARN_ON(ms->len + sizeof(*ms) > 2048);
 
-int
-minstrel_stats_release(struct inode *inode, struct file *file)
-{
-       kfree(file->private_data);
        return 0;
 }
 
-static const struct file_operations minstrel_stat_fops = {
+static const struct file_operations minstrel_stat_csv_fops = {
        .owner = THIS_MODULE,
-       .open = minstrel_stats_open,
+       .open = minstrel_stats_csv_open,
        .read = minstrel_stats_read,
        .release = minstrel_stats_release,
        .llseek = default_llseek,
@@ -138,6 +218,9 @@ minstrel_add_sta_debugfs(void *priv, void *priv_sta, struct dentry *dir)
 
        mi->dbg_stats = debugfs_create_file("rc_stats", S_IRUGO, dir, mi,
                        &minstrel_stat_fops);
+
+       mi->dbg_stats_csv = debugfs_create_file("rc_stats_csv", S_IRUGO, dir,
+                       mi, &minstrel_stat_csv_fops);
 }
 
 void
@@ -146,4 +229,6 @@ minstrel_remove_sta_debugfs(void *priv, void *priv_sta)
        struct minstrel_sta_info *mi = priv_sta;
 
        debugfs_remove(mi->dbg_stats);
+
+       debugfs_remove(mi->dbg_stats_csv);
 }
index 60698fc7042e5d0568f4d65e8d5a10bb05de9e21..7430a1df2ab13df2c1b227952a1315678aa20ce9 100644 (file)
@@ -313,67 +313,35 @@ minstrel_get_ratestats(struct minstrel_ht_sta *mi, int index)
        return &mi->groups[index / MCS_GROUP_RATES].rates[index % MCS_GROUP_RATES];
 }
 
-
 /*
- * Recalculate success probabilities and counters for a rate using EWMA
+ * Return current throughput based on the average A-MPDU length, taking into
+ * account the expected number of retransmissions and their expected length
  */
-static void
-minstrel_calc_rate_ewma(struct minstrel_rate_stats *mr)
+int
+minstrel_ht_get_tp_avg(struct minstrel_ht_sta *mi, int group, int rate,
+                      int prob_ewma)
 {
-       if (unlikely(mr->attempts > 0)) {
-               mr->sample_skipped = 0;
-               mr->cur_prob = MINSTREL_FRAC(mr->success, mr->attempts);
-               if (!mr->att_hist)
-                       mr->probability = mr->cur_prob;
-               else
-                       mr->probability = minstrel_ewma(mr->probability,
-                               mr->cur_prob, EWMA_LEVEL);
-               mr->att_hist += mr->attempts;
-               mr->succ_hist += mr->success;
-       } else {
-               mr->sample_skipped++;
-       }
-       mr->last_success = mr->success;
-       mr->last_attempts = mr->attempts;
-       mr->success = 0;
-       mr->attempts = 0;
-}
-
-/*
- * Calculate throughput based on the average A-MPDU length, taking into account
- * the expected number of retransmissions and their expected length
- */
-static void
-minstrel_ht_calc_tp(struct minstrel_ht_sta *mi, int group, int rate)
-{
-       struct minstrel_rate_stats *mr;
        unsigned int nsecs = 0;
-       unsigned int tp;
-       unsigned int prob;
 
-       mr = &mi->groups[group].rates[rate];
-       prob = mr->probability;
-
-       if (prob < MINSTREL_FRAC(1, 10)) {
-               mr->cur_tp = 0;
-               return;
-       }
-
-       /*
-        * For the throughput calculation, limit the probability value to 90% to
-        * account for collision related packet error rate fluctuation
-        */
-       if (prob > MINSTREL_FRAC(9, 10))
-               prob = MINSTREL_FRAC(9, 10);
+       /* do not account throughput if sucess prob is below 10% */
+       if (prob_ewma < MINSTREL_FRAC(10, 100))
+               return 0;
 
        if (group != MINSTREL_CCK_GROUP)
                nsecs = 1000 * mi->overhead / MINSTREL_TRUNC(mi->avg_ampdu_len);
 
        nsecs += minstrel_mcs_groups[group].duration[rate];
 
-       /* prob is scaled - see MINSTREL_FRAC above */
-       tp = 1000000 * ((prob * 1000) / nsecs);
-       mr->cur_tp = MINSTREL_TRUNC(tp);
+       /*
+        * For the throughput calculation, limit the probability value to 90% to
+        * account for collision related packet error rate fluctuation
+        * (prob is scaled - see MINSTREL_FRAC above)
+        */
+       if (prob_ewma > MINSTREL_FRAC(90, 100))
+               return MINSTREL_TRUNC(100000 * ((MINSTREL_FRAC(90, 100) * 1000)
+                                                                     / nsecs));
+       else
+               return MINSTREL_TRUNC(100000 * ((prob_ewma * 1000) / nsecs));
 }
 
 /*
@@ -387,22 +355,23 @@ static void
 minstrel_ht_sort_best_tp_rates(struct minstrel_ht_sta *mi, u16 index,
                               u16 *tp_list)
 {
-       int cur_group, cur_idx, cur_thr, cur_prob;
-       int tmp_group, tmp_idx, tmp_thr, tmp_prob;
+       int cur_group, cur_idx, cur_tp_avg, cur_prob;
+       int tmp_group, tmp_idx, tmp_tp_avg, tmp_prob;
        int j = MAX_THR_RATES;
 
        cur_group = index / MCS_GROUP_RATES;
        cur_idx = index  % MCS_GROUP_RATES;
-       cur_thr = mi->groups[cur_group].rates[cur_idx].cur_tp;
-       cur_prob = mi->groups[cur_group].rates[cur_idx].probability;
+       cur_prob = mi->groups[cur_group].rates[cur_idx].prob_ewma;
+       cur_tp_avg = minstrel_ht_get_tp_avg(mi, cur_group, cur_idx, cur_prob);
 
        do {
                tmp_group = tp_list[j - 1] / MCS_GROUP_RATES;
                tmp_idx = tp_list[j - 1] % MCS_GROUP_RATES;
-               tmp_thr = mi->groups[tmp_group].rates[tmp_idx].cur_tp;
-               tmp_prob = mi->groups[tmp_group].rates[tmp_idx].probability;
-               if (cur_thr < tmp_thr ||
-                   (cur_thr == tmp_thr && cur_prob <= tmp_prob))
+               tmp_prob = mi->groups[tmp_group].rates[tmp_idx].prob_ewma;
+               tmp_tp_avg = minstrel_ht_get_tp_avg(mi, tmp_group, tmp_idx,
+                                                   tmp_prob);
+               if (cur_tp_avg < tmp_tp_avg ||
+                   (cur_tp_avg == tmp_tp_avg && cur_prob <= tmp_prob))
                        break;
                j--;
        } while (j > 0);
@@ -422,16 +391,21 @@ static void
 minstrel_ht_set_best_prob_rate(struct minstrel_ht_sta *mi, u16 index)
 {
        struct minstrel_mcs_group_data *mg;
-       struct minstrel_rate_stats *mr;
-       int tmp_group, tmp_idx, tmp_tp, tmp_prob, max_tp_group;
+       struct minstrel_rate_stats *mrs;
+       int tmp_group, tmp_idx, tmp_tp_avg, tmp_prob;
+       int max_tp_group, cur_tp_avg, cur_group, cur_idx;
+       int max_gpr_group, max_gpr_idx;
+       int max_gpr_tp_avg, max_gpr_prob;
 
+       cur_group = index / MCS_GROUP_RATES;
+       cur_idx = index % MCS_GROUP_RATES;
        mg = &mi->groups[index / MCS_GROUP_RATES];
-       mr = &mg->rates[index % MCS_GROUP_RATES];
+       mrs = &mg->rates[index % MCS_GROUP_RATES];
 
        tmp_group = mi->max_prob_rate / MCS_GROUP_RATES;
        tmp_idx = mi->max_prob_rate % MCS_GROUP_RATES;
-       tmp_tp = mi->groups[tmp_group].rates[tmp_idx].cur_tp;
-       tmp_prob = mi->groups[tmp_group].rates[tmp_idx].probability;
+       tmp_prob = mi->groups[tmp_group].rates[tmp_idx].prob_ewma;
+       tmp_tp_avg = minstrel_ht_get_tp_avg(mi, tmp_group, tmp_idx, tmp_prob);
 
        /* if max_tp_rate[0] is from MCS_GROUP max_prob_rate get selected from
         * MCS_GROUP as well as CCK_GROUP rates do not allow aggregation */
@@ -440,15 +414,24 @@ minstrel_ht_set_best_prob_rate(struct minstrel_ht_sta *mi, u16 index)
            (max_tp_group != MINSTREL_CCK_GROUP))
                return;
 
-       if (mr->probability > MINSTREL_FRAC(75, 100)) {
-               if (mr->cur_tp > tmp_tp)
+       if (mrs->prob_ewma > MINSTREL_FRAC(75, 100)) {
+               cur_tp_avg = minstrel_ht_get_tp_avg(mi, cur_group, cur_idx,
+                                                   mrs->prob_ewma);
+               if (cur_tp_avg > tmp_tp_avg)
                        mi->max_prob_rate = index;
-               if (mr->cur_tp > mg->rates[mg->max_group_prob_rate].cur_tp)
+
+               max_gpr_group = mg->max_group_prob_rate / MCS_GROUP_RATES;
+               max_gpr_idx = mg->max_group_prob_rate % MCS_GROUP_RATES;
+               max_gpr_prob = mi->groups[max_gpr_group].rates[max_gpr_idx].prob_ewma;
+               max_gpr_tp_avg = minstrel_ht_get_tp_avg(mi, max_gpr_group,
+                                                       max_gpr_idx,
+                                                       max_gpr_prob);
+               if (cur_tp_avg > max_gpr_tp_avg)
                        mg->max_group_prob_rate = index;
        } else {
-               if (mr->probability > tmp_prob)
+               if (mrs->prob_ewma > tmp_prob)
                        mi->max_prob_rate = index;
-               if (mr->probability > mg->rates[mg->max_group_prob_rate].probability)
+               if (mrs->prob_ewma > mg->rates[mg->max_group_prob_rate].prob_ewma)
                        mg->max_group_prob_rate = index;
        }
 }
@@ -465,16 +448,18 @@ minstrel_ht_assign_best_tp_rates(struct minstrel_ht_sta *mi,
                                 u16 tmp_mcs_tp_rate[MAX_THR_RATES],
                                 u16 tmp_cck_tp_rate[MAX_THR_RATES])
 {
-       unsigned int tmp_group, tmp_idx, tmp_cck_tp, tmp_mcs_tp;
+       unsigned int tmp_group, tmp_idx, tmp_cck_tp, tmp_mcs_tp, tmp_prob;
        int i;
 
        tmp_group = tmp_cck_tp_rate[0] / MCS_GROUP_RATES;
        tmp_idx = tmp_cck_tp_rate[0] % MCS_GROUP_RATES;
-       tmp_cck_tp = mi->groups[tmp_group].rates[tmp_idx].cur_tp;
+       tmp_prob = mi->groups[tmp_group].rates[tmp_idx].prob_ewma;
+       tmp_cck_tp = minstrel_ht_get_tp_avg(mi, tmp_group, tmp_idx, tmp_prob);
 
        tmp_group = tmp_mcs_tp_rate[0] / MCS_GROUP_RATES;
        tmp_idx = tmp_mcs_tp_rate[0] % MCS_GROUP_RATES;
-       tmp_mcs_tp = mi->groups[tmp_group].rates[tmp_idx].cur_tp;
+       tmp_prob = mi->groups[tmp_group].rates[tmp_idx].prob_ewma;
+       tmp_mcs_tp = minstrel_ht_get_tp_avg(mi, tmp_group, tmp_idx, tmp_prob);
 
        if (tmp_cck_tp > tmp_mcs_tp) {
                for(i = 0; i < MAX_THR_RATES; i++) {
@@ -493,8 +478,7 @@ static inline void
 minstrel_ht_prob_rate_reduce_streams(struct minstrel_ht_sta *mi)
 {
        struct minstrel_mcs_group_data *mg;
-       struct minstrel_rate_stats *mr;
-       int tmp_max_streams, group;
+       int tmp_max_streams, group, tmp_idx, tmp_prob;
        int tmp_tp = 0;
 
        tmp_max_streams = minstrel_mcs_groups[mi->max_tp_rate[0] /
@@ -503,11 +487,16 @@ minstrel_ht_prob_rate_reduce_streams(struct minstrel_ht_sta *mi)
                mg = &mi->groups[group];
                if (!mg->supported || group == MINSTREL_CCK_GROUP)
                        continue;
-               mr = minstrel_get_ratestats(mi, mg->max_group_prob_rate);
-               if (tmp_tp < mr->cur_tp &&
+
+               tmp_idx = mg->max_group_prob_rate % MCS_GROUP_RATES;
+               tmp_prob = mi->groups[group].rates[tmp_idx].prob_ewma;
+
+               if (tmp_tp < minstrel_ht_get_tp_avg(mi, group, tmp_idx, tmp_prob) &&
                   (minstrel_mcs_groups[group].streams < tmp_max_streams)) {
                                mi->max_prob_rate = mg->max_group_prob_rate;
-                               tmp_tp = mr->cur_tp;
+                               tmp_tp = minstrel_ht_get_tp_avg(mi, group,
+                                                               tmp_idx,
+                                                               tmp_prob);
                }
        }
 }
@@ -525,8 +514,8 @@ static void
 minstrel_ht_update_stats(struct minstrel_priv *mp, struct minstrel_ht_sta *mi)
 {
        struct minstrel_mcs_group_data *mg;
-       struct minstrel_rate_stats *mr;
-       int group, i, j;
+       struct minstrel_rate_stats *mrs;
+       int group, i, j, cur_prob;
        u16 tmp_mcs_tp_rate[MAX_THR_RATES], tmp_group_tp_rate[MAX_THR_RATES];
        u16 tmp_cck_tp_rate[MAX_THR_RATES], index;
 
@@ -565,12 +554,12 @@ minstrel_ht_update_stats(struct minstrel_priv *mp, struct minstrel_ht_sta *mi)
 
                        index = MCS_GROUP_RATES * group + i;
 
-                       mr = &mg->rates[i];
-                       mr->retry_updated = false;
-                       minstrel_calc_rate_ewma(mr);
-                       minstrel_ht_calc_tp(mi, group, i);
+                       mrs = &mg->rates[i];
+                       mrs->retry_updated = false;
+                       minstrel_calc_rate_stats(mrs);
+                       cur_prob = mrs->prob_ewma;
 
-                       if (!mr->cur_tp)
+                       if (minstrel_ht_get_tp_avg(mi, group, i, cur_prob) == 0)
                                continue;
 
                        /* Find max throughput rate set */
@@ -614,7 +603,7 @@ minstrel_ht_update_stats(struct minstrel_priv *mp, struct minstrel_ht_sta *mi)
 #endif
 
        /* Reset update timer */
-       mi->stats_update = jiffies;
+       mi->last_stats_update = jiffies;
 }
 
 static bool
@@ -637,7 +626,7 @@ minstrel_ht_txstat_valid(struct minstrel_priv *mp, struct ieee80211_tx_rate *rat
 }
 
 static void
-minstrel_next_sample_idx(struct minstrel_ht_sta *mi)
+minstrel_set_next_sample_idx(struct minstrel_ht_sta *mi)
 {
        struct minstrel_mcs_group_data *mg;
 
@@ -778,7 +767,8 @@ minstrel_ht_tx_status(void *priv, struct ieee80211_supported_band *sband,
                update = true;
        }
 
-       if (time_after(jiffies, mi->stats_update + (mp->update_interval / 2 * HZ) / 1000)) {
+       if (time_after(jiffies, mi->last_stats_update +
+                               (mp->update_interval / 2 * HZ) / 1000)) {
                update = true;
                minstrel_ht_update_stats(mp, mi);
        }
@@ -791,7 +781,7 @@ static void
 minstrel_calc_retransmit(struct minstrel_priv *mp, struct minstrel_ht_sta *mi,
                          int index)
 {
-       struct minstrel_rate_stats *mr;
+       struct minstrel_rate_stats *mrs;
        const struct mcs_group *group;
        unsigned int tx_time, tx_time_rtscts, tx_time_data;
        unsigned int cw = mp->cw_min;
@@ -800,16 +790,16 @@ minstrel_calc_retransmit(struct minstrel_priv *mp, struct minstrel_ht_sta *mi,
        unsigned int ampdu_len = MINSTREL_TRUNC(mi->avg_ampdu_len);
        unsigned int overhead = 0, overhead_rtscts = 0;
 
-       mr = minstrel_get_ratestats(mi, index);
-       if (mr->probability < MINSTREL_FRAC(1, 10)) {
-               mr->retry_count = 1;
-               mr->retry_count_rtscts = 1;
+       mrs = minstrel_get_ratestats(mi, index);
+       if (mrs->prob_ewma < MINSTREL_FRAC(1, 10)) {
+               mrs->retry_count = 1;
+               mrs->retry_count_rtscts = 1;
                return;
        }
 
-       mr->retry_count = 2;
-       mr->retry_count_rtscts = 2;
-       mr->retry_updated = true;
+       mrs->retry_count = 2;
+       mrs->retry_count_rtscts = 2;
+       mrs->retry_updated = true;
 
        group = &minstrel_mcs_groups[index / MCS_GROUP_RATES];
        tx_time_data = group->duration[index % MCS_GROUP_RATES] * ampdu_len / 1000;
@@ -840,9 +830,9 @@ minstrel_calc_retransmit(struct minstrel_priv *mp, struct minstrel_ht_sta *mi,
                tx_time_rtscts += ctime + overhead_rtscts + tx_time_data;
 
                if (tx_time_rtscts < mp->segment_size)
-                       mr->retry_count_rtscts++;
+                       mrs->retry_count_rtscts++;
        } while ((tx_time < mp->segment_size) &&
-                (++mr->retry_count < mp->max_retry));
+                (++mrs->retry_count < mp->max_retry));
 }
 
 
@@ -851,22 +841,22 @@ minstrel_ht_set_rate(struct minstrel_priv *mp, struct minstrel_ht_sta *mi,
                      struct ieee80211_sta_rates *ratetbl, int offset, int index)
 {
        const struct mcs_group *group = &minstrel_mcs_groups[index / MCS_GROUP_RATES];
-       struct minstrel_rate_stats *mr;
+       struct minstrel_rate_stats *mrs;
        u8 idx;
        u16 flags = group->flags;
 
-       mr = minstrel_get_ratestats(mi, index);
-       if (!mr->retry_updated)
+       mrs = minstrel_get_ratestats(mi, index);
+       if (!mrs->retry_updated)
                minstrel_calc_retransmit(mp, mi, index);
 
-       if (mr->probability < MINSTREL_FRAC(20, 100) || !mr->retry_count) {
+       if (mrs->prob_ewma < MINSTREL_FRAC(20, 100) || !mrs->retry_count) {
                ratetbl->rate[offset].count = 2;
                ratetbl->rate[offset].count_rts = 2;
                ratetbl->rate[offset].count_cts = 2;
        } else {
-               ratetbl->rate[offset].count = mr->retry_count;
-               ratetbl->rate[offset].count_cts = mr->retry_count;
-               ratetbl->rate[offset].count_rts = mr->retry_count_rtscts;
+               ratetbl->rate[offset].count = mrs->retry_count;
+               ratetbl->rate[offset].count_cts = mrs->retry_count;
+               ratetbl->rate[offset].count_rts = mrs->retry_count_rtscts;
        }
 
        if (index / MCS_GROUP_RATES == MINSTREL_CCK_GROUP)
@@ -924,7 +914,7 @@ minstrel_get_duration(int index)
 static int
 minstrel_get_sample_rate(struct minstrel_priv *mp, struct minstrel_ht_sta *mi)
 {
-       struct minstrel_rate_stats *mr;
+       struct minstrel_rate_stats *mrs;
        struct minstrel_mcs_group_data *mg;
        unsigned int sample_dur, sample_group, cur_max_tp_streams;
        int sample_idx = 0;
@@ -940,12 +930,12 @@ minstrel_get_sample_rate(struct minstrel_priv *mp, struct minstrel_ht_sta *mi)
        sample_group = mi->sample_group;
        mg = &mi->groups[sample_group];
        sample_idx = sample_table[mg->column][mg->index];
-       minstrel_next_sample_idx(mi);
+       minstrel_set_next_sample_idx(mi);
 
        if (!(mg->supported & BIT(sample_idx)))
                return -1;
 
-       mr = &mg->rates[sample_idx];
+       mrs = &mg->rates[sample_idx];
        sample_idx += sample_group * MCS_GROUP_RATES;
 
        /*
@@ -962,7 +952,7 @@ minstrel_get_sample_rate(struct minstrel_priv *mp, struct minstrel_ht_sta *mi)
         * Do not sample if the probability is already higher than 95%
         * to avoid wasting airtime.
         */
-       if (mr->probability > MINSTREL_FRAC(95, 100))
+       if (mrs->prob_ewma > MINSTREL_FRAC(95, 100))
                return -1;
 
        /*
@@ -977,7 +967,7 @@ minstrel_get_sample_rate(struct minstrel_priv *mp, struct minstrel_ht_sta *mi)
            (cur_max_tp_streams - 1 <
             minstrel_mcs_groups[sample_group].streams ||
             sample_dur >= minstrel_get_duration(mi->max_prob_rate))) {
-               if (mr->sample_skipped < 20)
+               if (mrs->sample_skipped < 20)
                        return -1;
 
                if (mi->sample_slow++ > 2)
@@ -1131,7 +1121,7 @@ minstrel_ht_update_caps(void *priv, struct ieee80211_supported_band *sband,
        memset(mi, 0, sizeof(*mi));
 
        mi->sta = sta;
-       mi->stats_update = jiffies;
+       mi->last_stats_update = jiffies;
 
        ack_dur = ieee80211_frame_duration(sband->band, 10, 60, 1, 1, 0);
        mi->overhead = ieee80211_frame_duration(sband->band, 0, 60, 1, 1, 0);
@@ -1328,16 +1318,19 @@ static u32 minstrel_ht_get_expected_throughput(void *priv_sta)
 {
        struct minstrel_ht_sta_priv *msp = priv_sta;
        struct minstrel_ht_sta *mi = &msp->ht;
-       int i, j;
+       int i, j, prob, tp_avg;
 
        if (!msp->is_ht)
                return mac80211_minstrel.get_expected_throughput(priv_sta);
 
        i = mi->max_tp_rate[0] / MCS_GROUP_RATES;
        j = mi->max_tp_rate[0] % MCS_GROUP_RATES;
+       prob = mi->groups[i].rates[j].prob_ewma;
+
+       /* convert tp_avg from pkt per second in kbps */
+       tp_avg = minstrel_ht_get_tp_avg(mi, i, j, prob) * AVG_PKT_SIZE * 8 / 1024;
 
-       /* convert cur_tp from pkt per second in kbps */
-       return mi->groups[i].rates[j].cur_tp * AVG_PKT_SIZE * 8 / 1024;
+       return tp_avg;
 }
 
 static const struct rate_control_ops mac80211_minstrel_ht = {
index f2217d6aa0c2ee28b158616711b99895b250df44..e8b52a94d24b64fd3e78b4d3a390c3dbdad9e328 100644 (file)
@@ -78,7 +78,7 @@ struct minstrel_ht_sta {
        u16 max_prob_rate;
 
        /* time of last status update */
-       unsigned long stats_update;
+       unsigned long last_stats_update;
 
        /* overhead time in usec for each frame */
        unsigned int overhead;
@@ -112,6 +112,7 @@ struct minstrel_ht_sta_priv {
        };
 #ifdef CONFIG_MAC80211_DEBUGFS
        struct dentry *dbg_stats;
+       struct dentry *dbg_stats_csv;
 #endif
        void *ratelist;
        void *sample_table;
@@ -120,5 +121,7 @@ struct minstrel_ht_sta_priv {
 
 void minstrel_ht_add_sta_debugfs(void *priv, void *priv_sta, struct dentry *dir);
 void minstrel_ht_remove_sta_debugfs(void *priv, void *priv_sta);
+int minstrel_ht_get_tp_avg(struct minstrel_ht_sta *mi, int group, int rate,
+                          int prob_ewma);
 
 #endif
index 20c676b8e5b68fa366ed670640e9091c39ebfde3..6822ce0f95e580641f782fb2f59dcf9f09e5e639 100644 (file)
@@ -19,7 +19,7 @@ static char *
 minstrel_ht_stats_dump(struct minstrel_ht_sta *mi, int i, char *p)
 {
        const struct mcs_group *mg;
-       unsigned int j, tp, prob, eprob;
+       unsigned int j, tp_max, tp_avg, prob, eprob, tx_time;
        char htmode = '2';
        char gimode = 'L';
        u32 gflags;
@@ -38,19 +38,26 @@ minstrel_ht_stats_dump(struct minstrel_ht_sta *mi, int i, char *p)
                gimode = 'S';
 
        for (j = 0; j < MCS_GROUP_RATES; j++) {
-               struct minstrel_rate_stats *mr = &mi->groups[i].rates[j];
+               struct minstrel_rate_stats *mrs = &mi->groups[i].rates[j];
                static const int bitrates[4] = { 10, 20, 55, 110 };
                int idx = i * MCS_GROUP_RATES + j;
 
                if (!(mi->groups[i].supported & BIT(j)))
                        continue;
 
-               if (gflags & IEEE80211_TX_RC_MCS)
-                       p += sprintf(p, " HT%c0/%cGI ", htmode, gimode);
-               else if (gflags & IEEE80211_TX_RC_VHT_MCS)
-                       p += sprintf(p, "VHT%c0/%cGI ", htmode, gimode);
-               else
-                       p += sprintf(p, " CCK/%cP   ", j < 4 ? 'L' : 'S');
+               if (gflags & IEEE80211_TX_RC_MCS) {
+                       p += sprintf(p, "HT%c0  ", htmode);
+                       p += sprintf(p, "%cGI  ", gimode);
+                       p += sprintf(p, "%d  ", mg->streams);
+               } else if (gflags & IEEE80211_TX_RC_VHT_MCS) {
+                       p += sprintf(p, "VHT%c0 ", htmode);
+                       p += sprintf(p, "%cGI ", gimode);
+                       p += sprintf(p, "%d  ", mg->streams);
+               } else {
+                       p += sprintf(p, "CCK    ");
+                       p += sprintf(p, "%cP  ", j < 4 ? 'L' : 'S');
+                       p += sprintf(p, "1 ");
+               }
 
                *(p++) = (idx == mi->max_tp_rate[0]) ? 'A' : ' ';
                *(p++) = (idx == mi->max_tp_rate[1]) ? 'B' : ' ';
@@ -59,29 +66,39 @@ minstrel_ht_stats_dump(struct minstrel_ht_sta *mi, int i, char *p)
                *(p++) = (idx == mi->max_prob_rate) ? 'P' : ' ';
 
                if (gflags & IEEE80211_TX_RC_MCS) {
-                       p += sprintf(p, " MCS%-2u ", (mg->streams - 1) * 8 + j);
+                       p += sprintf(p, "  MCS%-2u", (mg->streams - 1) * 8 + j);
                } else if (gflags & IEEE80211_TX_RC_VHT_MCS) {
-                       p += sprintf(p, " MCS%-1u/%1u", j, mg->streams);
+                       p += sprintf(p, "  MCS%-1u/%1u", j, mg->streams);
                } else {
                        int r = bitrates[j % 4];
 
-                       p += sprintf(p, " %2u.%1uM ", r / 10, r % 10);
+                       p += sprintf(p, "   %2u.%1uM", r / 10, r % 10);
                }
 
-               tp = mr->cur_tp / 10;
-               prob = MINSTREL_TRUNC(mr->cur_prob * 1000);
-               eprob = MINSTREL_TRUNC(mr->probability * 1000);
+               p += sprintf(p, "  %3u  ", idx);
 
-               p += sprintf(p, " %4u.%1u %3u.%1u %3u.%1u "
-                               "%3u %4u(%4u) %9llu(%9llu)\n",
-                               tp / 10, tp % 10,
+               /* tx_time[rate(i)] in usec */
+               tx_time = DIV_ROUND_CLOSEST(mg->duration[j], 1000);
+               p += sprintf(p, "%6u  ", tx_time);
+
+               tp_max = minstrel_ht_get_tp_avg(mi, i, j, MINSTREL_FRAC(100, 100));
+               tp_avg = minstrel_ht_get_tp_avg(mi, i, j, mrs->prob_ewma);
+               prob = MINSTREL_TRUNC(mrs->cur_prob * 1000);
+               eprob = MINSTREL_TRUNC(mrs->prob_ewma * 1000);
+
+               p += sprintf(p, "%4u.%1u   %4u.%1u   %3u.%1u    %3u.%1u"
+                               "     %3u.%1u %3u   %3u %-3u   "
+                               "%9llu   %-9llu\n",
+                               tp_max / 10, tp_max % 10,
+                               tp_avg / 10, tp_avg % 10,
                                eprob / 10, eprob % 10,
+                               mrs->prob_ewmsd / 10, mrs->prob_ewmsd % 10,
                                prob / 10, prob % 10,
-                               mr->retry_count,
-                               mr->last_success,
-                               mr->last_attempts,
-                               (unsigned long long)mr->succ_hist,
-                               (unsigned long long)mr->att_hist);
+                               mrs->retry_count,
+                               mrs->last_success,
+                               mrs->last_attempts,
+                               (unsigned long long)mrs->succ_hist,
+                               (unsigned long long)mrs->att_hist);
        }
 
        return p;
@@ -94,8 +111,8 @@ minstrel_ht_stats_open(struct inode *inode, struct file *file)
        struct minstrel_ht_sta *mi = &msp->ht;
        struct minstrel_debugfs_info *ms;
        unsigned int i;
-       char *p;
        int ret;
+       char *p;
 
        if (!msp->is_ht) {
                inode->i_private = &msp->legacy;
@@ -110,8 +127,14 @@ minstrel_ht_stats_open(struct inode *inode, struct file *file)
 
        file->private_data = ms;
        p = ms->buf;
-       p += sprintf(p, " type           rate      tpt eprob *prob "
-                       "ret  *ok(*cum)        ok(      cum)\n");
+
+       p += sprintf(p, "\n");
+       p += sprintf(p, "              best   ____________rate__________    "
+                       "______statistics______    ________last_______    "
+                       "______sum-of________\n");
+       p += sprintf(p, "mode guard #  rate  [name   idx airtime  max_tp]  "
+                       "[ Ã¸(tp) Ã¸(prob) sd(prob)]  [prob.|retry|suc|att]  [#success | "
+                       "#attempts]\n");
 
        p = minstrel_ht_stats_dump(mi, MINSTREL_CCK_GROUP, p);
        for (i = 0; i < MINSTREL_CCK_GROUP; i++)
@@ -123,11 +146,10 @@ minstrel_ht_stats_open(struct inode *inode, struct file *file)
                        "lookaround %d\n",
                        max(0, (int) mi->total_packets - (int) mi->sample_packets),
                        mi->sample_packets);
-       p += sprintf(p, "Average A-MPDU length: %d.%d\n",
+       p += sprintf(p, "Average # of aggregated frames per A-MPDU: %d.%d\n",
                MINSTREL_TRUNC(mi->avg_ampdu_len),
                MINSTREL_TRUNC(mi->avg_ampdu_len * 10) % 10);
        ms->len = p - ms->buf;
-
        WARN_ON(ms->len + sizeof(*ms) > 32768);
 
        return nonseekable_open(inode, file);
@@ -141,6 +163,143 @@ static const struct file_operations minstrel_ht_stat_fops = {
        .llseek = no_llseek,
 };
 
+static char *
+minstrel_ht_stats_csv_dump(struct minstrel_ht_sta *mi, int i, char *p)
+{
+       const struct mcs_group *mg;
+       unsigned int j, tp_max, tp_avg, prob, eprob, tx_time;
+       char htmode = '2';
+       char gimode = 'L';
+       u32 gflags;
+
+       if (!mi->groups[i].supported)
+               return p;
+
+       mg = &minstrel_mcs_groups[i];
+       gflags = mg->flags;
+
+       if (gflags & IEEE80211_TX_RC_40_MHZ_WIDTH)
+               htmode = '4';
+       else if (gflags & IEEE80211_TX_RC_80_MHZ_WIDTH)
+               htmode = '8';
+       if (gflags & IEEE80211_TX_RC_SHORT_GI)
+               gimode = 'S';
+
+       for (j = 0; j < MCS_GROUP_RATES; j++) {
+               struct minstrel_rate_stats *mrs = &mi->groups[i].rates[j];
+               static const int bitrates[4] = { 10, 20, 55, 110 };
+               int idx = i * MCS_GROUP_RATES + j;
+
+               if (!(mi->groups[i].supported & BIT(j)))
+                       continue;
+
+               if (gflags & IEEE80211_TX_RC_MCS) {
+                       p += sprintf(p, "HT%c0,", htmode);
+                       p += sprintf(p, "%cGI,", gimode);
+                       p += sprintf(p, "%d,", mg->streams);
+               } else if (gflags & IEEE80211_TX_RC_VHT_MCS) {
+                       p += sprintf(p, "VHT%c0,", htmode);
+                       p += sprintf(p, "%cGI,", gimode);
+                       p += sprintf(p, "%d,", mg->streams);
+               } else {
+                       p += sprintf(p, "CCK,");
+                       p += sprintf(p, "%cP,", j < 4 ? 'L' : 'S');
+                       p += sprintf(p, "1,");
+               }
+
+               p += sprintf(p, "%s" ,((idx == mi->max_tp_rate[0]) ? "A" : ""));
+               p += sprintf(p, "%s" ,((idx == mi->max_tp_rate[1]) ? "B" : ""));
+               p += sprintf(p, "%s" ,((idx == mi->max_tp_rate[2]) ? "C" : ""));
+               p += sprintf(p, "%s" ,((idx == mi->max_tp_rate[3]) ? "D" : ""));
+               p += sprintf(p, "%s" ,((idx == mi->max_prob_rate) ? "P" : ""));
+
+               if (gflags & IEEE80211_TX_RC_MCS) {
+                       p += sprintf(p, ",MCS%-2u,", (mg->streams - 1) * 8 + j);
+               } else if (gflags & IEEE80211_TX_RC_VHT_MCS) {
+                       p += sprintf(p, ",MCS%-1u/%1u,", j, mg->streams);
+               } else {
+                       int r = bitrates[j % 4];
+                       p += sprintf(p, ",%2u.%1uM,", r / 10, r % 10);
+               }
+
+               p += sprintf(p, "%u,", idx);
+               tx_time = DIV_ROUND_CLOSEST(mg->duration[j], 1000);
+               p += sprintf(p, "%u,", tx_time);
+
+               tp_max = minstrel_ht_get_tp_avg(mi, i, j, MINSTREL_FRAC(100, 100));
+               tp_avg = minstrel_ht_get_tp_avg(mi, i, j, mrs->prob_ewma);
+               prob = MINSTREL_TRUNC(mrs->cur_prob * 1000);
+               eprob = MINSTREL_TRUNC(mrs->prob_ewma * 1000);
+
+               p += sprintf(p, "%u.%u,%u.%u,%u.%u,%u.%u,%u.%u,%u,%u,"
+                               "%u,%llu,%llu,",
+                               tp_max / 10, tp_max % 10,
+                               tp_avg / 10, tp_avg % 10,
+                               eprob / 10, eprob % 10,
+                               mrs->prob_ewmsd / 10, mrs->prob_ewmsd % 10,
+                               prob / 10, prob % 10,
+                               mrs->retry_count,
+                               mrs->last_success,
+                               mrs->last_attempts,
+                               (unsigned long long)mrs->succ_hist,
+                               (unsigned long long)mrs->att_hist);
+               p += sprintf(p, "%d,%d,%d.%d\n",
+                               max(0, (int) mi->total_packets -
+                               (int) mi->sample_packets),
+                               mi->sample_packets,
+                               MINSTREL_TRUNC(mi->avg_ampdu_len),
+                               MINSTREL_TRUNC(mi->avg_ampdu_len * 10) % 10);
+       }
+
+       return p;
+}
+
+static int
+minstrel_ht_stats_csv_open(struct inode *inode, struct file *file)
+{
+       struct minstrel_ht_sta_priv *msp = inode->i_private;
+       struct minstrel_ht_sta *mi = &msp->ht;
+       struct minstrel_debugfs_info *ms;
+       unsigned int i;
+       int ret;
+       char *p;
+
+       if (!msp->is_ht) {
+               inode->i_private = &msp->legacy;
+               ret = minstrel_stats_csv_open(inode, file);
+               inode->i_private = msp;
+               return ret;
+       }
+
+       ms = kmalloc(32768, GFP_KERNEL);
+
+       if (!ms)
+               return -ENOMEM;
+
+       file->private_data = ms;
+
+       p = ms->buf;
+
+       p = minstrel_ht_stats_csv_dump(mi, MINSTREL_CCK_GROUP, p);
+       for (i = 0; i < MINSTREL_CCK_GROUP; i++)
+               p = minstrel_ht_stats_csv_dump(mi, i, p);
+       for (i++; i < ARRAY_SIZE(mi->groups); i++)
+               p = minstrel_ht_stats_csv_dump(mi, i, p);
+
+       ms->len = p - ms->buf;
+       WARN_ON(ms->len + sizeof(*ms) > 32768);
+
+       return nonseekable_open(inode, file);
+}
+
+static const struct file_operations minstrel_ht_stat_csv_fops = {
+       .owner = THIS_MODULE,
+       .open = minstrel_ht_stats_csv_open,
+       .read = minstrel_stats_read,
+       .release = minstrel_stats_release,
+       .llseek = no_llseek,
+};
+
 void
 minstrel_ht_add_sta_debugfs(void *priv, void *priv_sta, struct dentry *dir)
 {
@@ -148,6 +307,8 @@ minstrel_ht_add_sta_debugfs(void *priv, void *priv_sta, struct dentry *dir)
 
        msp->dbg_stats = debugfs_create_file("rc_stats", S_IRUGO, dir, msp,
                        &minstrel_ht_stat_fops);
+       msp->dbg_stats_csv = debugfs_create_file("rc_stats_csv", S_IRUGO,
+                            dir, msp, &minstrel_ht_stat_csv_fops);
 }
 
 void
@@ -156,4 +317,5 @@ minstrel_ht_remove_sta_debugfs(void *priv, void *priv_sta)
        struct minstrel_ht_sta_priv *msp = priv_sta;
 
        debugfs_remove(msp->dbg_stats);
+       debugfs_remove(msp->dbg_stats_csv);
 }
index 2cd02278d4d4076c580f9253460ce98f677edfe1..260eed45b6d2ff105052643169465c04d333c182 100644 (file)
@@ -1185,6 +1185,7 @@ static void sta_ps_start(struct sta_info *sta)
        struct ieee80211_sub_if_data *sdata = sta->sdata;
        struct ieee80211_local *local = sdata->local;
        struct ps_data *ps;
+       int tid;
 
        if (sta->sdata->vif.type == NL80211_IFTYPE_AP ||
            sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
@@ -1198,6 +1199,18 @@ static void sta_ps_start(struct sta_info *sta)
                drv_sta_notify(local, sdata, STA_NOTIFY_SLEEP, &sta->sta);
        ps_dbg(sdata, "STA %pM aid %d enters power save mode\n",
               sta->sta.addr, sta->sta.aid);
+
+       if (!sta->sta.txq[0])
+               return;
+
+       for (tid = 0; tid < ARRAY_SIZE(sta->sta.txq); tid++) {
+               struct txq_info *txqi = to_txq_info(sta->sta.txq[tid]);
+
+               if (!skb_queue_len(&txqi->queue))
+                       set_bit(tid, &sta->txq_buffered_tids);
+               else
+                       clear_bit(tid, &sta->txq_buffered_tids);
+       }
 }
 
 static void sta_ps_end(struct sta_info *sta)
@@ -3424,7 +3437,8 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
        __le16 fc;
        struct ieee80211_rx_data rx;
        struct ieee80211_sub_if_data *prev;
-       struct sta_info *sta, *tmp, *prev_sta;
+       struct sta_info *sta, *prev_sta;
+       struct rhash_head *tmp;
        int err = 0;
 
        fc = ((struct ieee80211_hdr *)skb->data)->frame_control;
@@ -3459,9 +3473,13 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
                ieee80211_scan_rx(local, skb);
 
        if (ieee80211_is_data(fc)) {
+               const struct bucket_table *tbl;
+
                prev_sta = NULL;
 
-               for_each_sta_info(local, hdr->addr2, sta, tmp) {
+               tbl = rht_dereference_rcu(local->sta_hash.tbl, &local->sta_hash);
+
+               for_each_sta_info(local, tbl, hdr->addr2, sta, tmp) {
                        if (!prev_sta) {
                                prev_sta = sta;
                                continue;
index aacaa1a85e636c4b31df4540c680508be33cdb42..12971b71d0fa1ea8ce69dbdc09314c1c1641d7c8 100644 (file)
  * freed before they are done using it.
  */
 
+static const struct rhashtable_params sta_rht_params = {
+       .nelem_hint = 3, /* start small */
+       .head_offset = offsetof(struct sta_info, hash_node),
+       .key_offset = offsetof(struct sta_info, sta.addr),
+       .key_len = ETH_ALEN,
+       .hashfn = sta_addr_hash,
+};
+
 /* Caller must hold local->sta_mtx */
 static int sta_info_hash_del(struct ieee80211_local *local,
                             struct sta_info *sta)
 {
-       struct sta_info *s;
-
-       s = rcu_dereference_protected(local->sta_hash[STA_HASH(sta->sta.addr)],
-                                     lockdep_is_held(&local->sta_mtx));
-       if (!s)
-               return -ENOENT;
-       if (s == sta) {
-               rcu_assign_pointer(local->sta_hash[STA_HASH(sta->sta.addr)],
-                                  s->hnext);
-               return 0;
-       }
-
-       while (rcu_access_pointer(s->hnext) &&
-              rcu_access_pointer(s->hnext) != sta)
-               s = rcu_dereference_protected(s->hnext,
-                                       lockdep_is_held(&local->sta_mtx));
-       if (rcu_access_pointer(s->hnext)) {
-               rcu_assign_pointer(s->hnext, sta->hnext);
-               return 0;
-       }
-
-       return -ENOENT;
+       return rhashtable_remove_fast(&local->sta_hash, &sta->hash_node,
+                                     sta_rht_params);
 }
 
 static void __cleanup_single_sta(struct sta_info *sta)
@@ -118,6 +106,16 @@ static void __cleanup_single_sta(struct sta_info *sta)
                atomic_dec(&ps->num_sta_ps);
        }
 
+       if (sta->sta.txq[0]) {
+               for (i = 0; i < ARRAY_SIZE(sta->sta.txq); i++) {
+                       struct txq_info *txqi = to_txq_info(sta->sta.txq[i]);
+                       int n = skb_queue_len(&txqi->queue);
+
+                       ieee80211_purge_tx_queue(&local->hw, &txqi->queue);
+                       atomic_sub(n, &sdata->txqs_len[txqi->txq.ac]);
+               }
+       }
+
        for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
                local->total_ps_buffered -= skb_queue_len(&sta->ps_tx_buf[ac]);
                ieee80211_purge_tx_queue(&local->hw, &sta->ps_tx_buf[ac]);
@@ -159,18 +157,8 @@ struct sta_info *sta_info_get(struct ieee80211_sub_if_data *sdata,
                              const u8 *addr)
 {
        struct ieee80211_local *local = sdata->local;
-       struct sta_info *sta;
 
-       sta = rcu_dereference_check(local->sta_hash[STA_HASH(addr)],
-                                   lockdep_is_held(&local->sta_mtx));
-       while (sta) {
-               if (sta->sdata == sdata &&
-                   ether_addr_equal(sta->sta.addr, addr))
-                       break;
-               sta = rcu_dereference_check(sta->hnext,
-                                           lockdep_is_held(&local->sta_mtx));
-       }
-       return sta;
+       return rhashtable_lookup_fast(&local->sta_hash, addr, sta_rht_params);
 }
 
 /*
@@ -182,18 +170,24 @@ struct sta_info *sta_info_get_bss(struct ieee80211_sub_if_data *sdata,
 {
        struct ieee80211_local *local = sdata->local;
        struct sta_info *sta;
+       struct rhash_head *tmp;
+       const struct bucket_table *tbl;
 
-       sta = rcu_dereference_check(local->sta_hash[STA_HASH(addr)],
-                                   lockdep_is_held(&local->sta_mtx));
-       while (sta) {
-               if ((sta->sdata == sdata ||
-                    (sta->sdata->bss && sta->sdata->bss == sdata->bss)) &&
-                   ether_addr_equal(sta->sta.addr, addr))
-                       break;
-               sta = rcu_dereference_check(sta->hnext,
-                                           lockdep_is_held(&local->sta_mtx));
+       rcu_read_lock();
+       tbl = rht_dereference_rcu(local->sta_hash.tbl, &local->sta_hash);
+
+       for_each_sta_info(local, tbl, addr, sta, tmp) {
+               if (sta->sdata == sdata ||
+                   (sta->sdata->bss && sta->sdata->bss == sdata->bss)) {
+                       rcu_read_unlock();
+                       /* this is safe as the caller must already hold
+                        * another rcu read section or the mutex
+                        */
+                       return sta;
+               }
        }
-       return sta;
+       rcu_read_unlock();
+       return NULL;
 }
 
 struct sta_info *sta_info_get_by_idx(struct ieee80211_sub_if_data *sdata,
@@ -234,6 +228,8 @@ void sta_info_free(struct ieee80211_local *local, struct sta_info *sta)
 
        sta_dbg(sta->sdata, "Destroyed STA %pM\n", sta->sta.addr);
 
+       if (sta->sta.txq[0])
+               kfree(to_txq_info(sta->sta.txq[0]));
        kfree(rcu_dereference_raw(sta->sta.rates));
        kfree(sta);
 }
@@ -242,9 +238,8 @@ void sta_info_free(struct ieee80211_local *local, struct sta_info *sta)
 static void sta_info_hash_add(struct ieee80211_local *local,
                              struct sta_info *sta)
 {
-       lockdep_assert_held(&local->sta_mtx);
-       sta->hnext = local->sta_hash[STA_HASH(sta->sta.addr)];
-       rcu_assign_pointer(local->sta_hash[STA_HASH(sta->sta.addr)], sta);
+       rhashtable_insert_fast(&local->sta_hash, &sta->hash_node,
+                              sta_rht_params);
 }
 
 static void sta_deliver_ps_frames(struct work_struct *wk)
@@ -285,11 +280,12 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
                                const u8 *addr, gfp_t gfp)
 {
        struct ieee80211_local *local = sdata->local;
+       struct ieee80211_hw *hw = &local->hw;
        struct sta_info *sta;
        struct timespec uptime;
        int i;
 
-       sta = kzalloc(sizeof(*sta) + local->hw.sta_data_size, gfp);
+       sta = kzalloc(sizeof(*sta) + hw->sta_data_size, gfp);
        if (!sta)
                return NULL;
 
@@ -321,11 +317,25 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
        for (i = 0; i < ARRAY_SIZE(sta->chain_signal_avg); i++)
                ewma_init(&sta->chain_signal_avg[i], 1024, 8);
 
-       if (sta_prepare_rate_control(local, sta, gfp)) {
-               kfree(sta);
-               return NULL;
+       if (local->ops->wake_tx_queue) {
+               void *txq_data;
+               int size = sizeof(struct txq_info) +
+                          ALIGN(hw->txq_data_size, sizeof(void *));
+
+               txq_data = kcalloc(ARRAY_SIZE(sta->sta.txq), size, gfp);
+               if (!txq_data)
+                       goto free;
+
+               for (i = 0; i < ARRAY_SIZE(sta->sta.txq); i++) {
+                       struct txq_info *txq = txq_data + i * size;
+
+                       ieee80211_init_tx_queue(sdata, sta, txq, i);
+               }
        }
 
+       if (sta_prepare_rate_control(local, sta, gfp))
+               goto free_txq;
+
        for (i = 0; i < IEEE80211_NUM_TIDS; i++) {
                /*
                 * timer_to_tid must be initialized with identity mapping
@@ -346,7 +356,7 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
        if (sdata->vif.type == NL80211_IFTYPE_AP ||
            sdata->vif.type == NL80211_IFTYPE_AP_VLAN) {
                struct ieee80211_supported_band *sband =
-                       local->hw.wiphy->bands[ieee80211_get_sdata_band(sdata)];
+                       hw->wiphy->bands[ieee80211_get_sdata_band(sdata)];
                u8 smps = (sband->ht_cap.cap & IEEE80211_HT_CAP_SM_PS) >>
                                IEEE80211_HT_CAP_SM_PS_SHIFT;
                /*
@@ -371,6 +381,13 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
        sta_dbg(sdata, "Allocated STA %pM\n", sta->sta.addr);
 
        return sta;
+
+free_txq:
+       if (sta->sta.txq[0])
+               kfree(to_txq_info(sta->sta.txq[0]));
+free:
+       kfree(sta);
+       return NULL;
 }
 
 static int sta_info_insert_check(struct sta_info *sta)
@@ -640,6 +657,8 @@ static void __sta_info_recalc_tim(struct sta_info *sta, bool ignore_pending)
 
                indicate_tim |=
                        sta->driver_buffered_tids & tids;
+               indicate_tim |=
+                       sta->txq_buffered_tids & tids;
        }
 
  done:
@@ -948,19 +967,32 @@ static void sta_info_cleanup(unsigned long data)
                  round_jiffies(jiffies + STA_INFO_CLEANUP_INTERVAL));
 }
 
-void sta_info_init(struct ieee80211_local *local)
+u32 sta_addr_hash(const void *key, u32 length, u32 seed)
+{
+       return jhash(key, ETH_ALEN, seed);
+}
+
+int sta_info_init(struct ieee80211_local *local)
 {
+       int err;
+
+       err = rhashtable_init(&local->sta_hash, &sta_rht_params);
+       if (err)
+               return err;
+
        spin_lock_init(&local->tim_lock);
        mutex_init(&local->sta_mtx);
        INIT_LIST_HEAD(&local->sta_list);
 
        setup_timer(&local->sta_cleanup, sta_info_cleanup,
                    (unsigned long)local);
+       return 0;
 }
 
 void sta_info_stop(struct ieee80211_local *local)
 {
        del_timer_sync(&local->sta_cleanup);
+       rhashtable_destroy(&local->sta_hash);
 }
 
 
@@ -1024,16 +1056,21 @@ void ieee80211_sta_expire(struct ieee80211_sub_if_data *sdata,
 }
 
 struct ieee80211_sta *ieee80211_find_sta_by_ifaddr(struct ieee80211_hw *hw,
-                                              const u8 *addr,
-                                              const u8 *localaddr)
+                                                  const u8 *addr,
+                                                  const u8 *localaddr)
 {
-       struct sta_info *sta, *nxt;
+       struct ieee80211_local *local = hw_to_local(hw);
+       struct sta_info *sta;
+       struct rhash_head *tmp;
+       const struct bucket_table *tbl;
+
+       tbl = rht_dereference_rcu(local->sta_hash.tbl, &local->sta_hash);
 
        /*
         * Just return a random station if localaddr is NULL
         * ... first in list.
         */
-       for_each_sta_info(hw_to_local(hw), addr, sta, nxt) {
+       for_each_sta_info(local, tbl, addr, sta, tmp) {
                if (localaddr &&
                    !ether_addr_equal(sta->sdata->vif.addr, localaddr))
                        continue;
@@ -1071,7 +1108,7 @@ void ieee80211_sta_ps_deliver_wakeup(struct sta_info *sta)
        struct ieee80211_sub_if_data *sdata = sta->sdata;
        struct ieee80211_local *local = sdata->local;
        struct sk_buff_head pending;
-       int filtered = 0, buffered = 0, ac;
+       int filtered = 0, buffered = 0, ac, i;
        unsigned long flags;
        struct ps_data *ps;
 
@@ -1090,10 +1127,22 @@ void ieee80211_sta_ps_deliver_wakeup(struct sta_info *sta)
 
        BUILD_BUG_ON(BITS_TO_LONGS(IEEE80211_NUM_TIDS) > 1);
        sta->driver_buffered_tids = 0;
+       sta->txq_buffered_tids = 0;
 
        if (!(local->hw.flags & IEEE80211_HW_AP_LINK_PS))
                drv_sta_notify(local, sdata, STA_NOTIFY_AWAKE, &sta->sta);
 
+       if (sta->sta.txq[0]) {
+               for (i = 0; i < ARRAY_SIZE(sta->sta.txq); i++) {
+                       struct txq_info *txqi = to_txq_info(sta->sta.txq[i]);
+
+                       if (!skb_queue_len(&txqi->queue))
+                               continue;
+
+                       drv_wake_tx_queue(local, txqi);
+               }
+       }
+
        skb_queue_head_init(&pending);
 
        /* sync with ieee80211_tx_h_unicast_ps_buf */
@@ -1275,8 +1324,10 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta,
                /* if we already have frames from software, then we can't also
                 * release from hardware queues
                 */
-               if (skb_queue_empty(&frames))
+               if (skb_queue_empty(&frames)) {
                        driver_release_tids |= sta->driver_buffered_tids & tids;
+                       driver_release_tids |= sta->txq_buffered_tids & tids;
+               }
 
                if (driver_release_tids) {
                        /* If the driver has data on more than one TID then
@@ -1447,6 +1498,9 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta,
 
                sta_info_recalc_tim(sta);
        } else {
+               unsigned long tids = sta->txq_buffered_tids & driver_release_tids;
+               int tid;
+
                /*
                 * We need to release a frame that is buffered somewhere in the
                 * driver ... it'll have to handle that.
@@ -1466,8 +1520,22 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta,
                 * that the TID(s) became empty before returning here from the
                 * release function.
                 * Either way, however, when the driver tells us that the TID(s)
-                * became empty we'll do the TIM recalculation.
+                * became empty or we find that a txq became empty, we'll do the
+                * TIM recalculation.
                 */
+
+               if (!sta->sta.txq[0])
+                       return;
+
+               for (tid = 0; tid < ARRAY_SIZE(sta->sta.txq); tid++) {
+                       struct txq_info *txqi = to_txq_info(sta->sta.txq[tid]);
+
+                       if (!(tids & BIT(tid)) || skb_queue_len(&txqi->queue))
+                               continue;
+
+                       sta_info_recalc_tim(sta);
+                       break;
+               }
        }
 }
 
index 7e2fa4018d41331cc3845639eb7dfc497c13bc27..5c164fb3f6c5bd2d68b5daa3993ff369a2a8792f 100644 (file)
@@ -16,6 +16,7 @@
 #include <linux/workqueue.h>
 #include <linux/average.h>
 #include <linux/etherdevice.h>
+#include <linux/rhashtable.h>
 #include "key.h"
 
 /**
@@ -248,7 +249,7 @@ struct sta_ampdu_mlme {
  *
  * @list: global linked list entry
  * @free_list: list entry for keeping track of stations to free
- * @hnext: hash table linked list pointer
+ * @hash_node: hash node for rhashtable
  * @local: pointer to the global information
  * @sdata: virtual interface this station belongs to
  * @ptk: peer keys negotiated with this station, if any
@@ -276,6 +277,7 @@ struct sta_ampdu_mlme {
  *     entered power saving state, these are also delivered to
  *     the station when it leaves powersave or polls for frames
  * @driver_buffered_tids: bitmap of TIDs the driver has data buffered on
+ * @txq_buffered_tids: bitmap of TIDs that mac80211 has txq data buffered on
  * @rx_packets: Number of MSDUs received from this STA
  * @rx_bytes: Number of bytes received from this STA
  * @last_rx: time (in jiffies) when last frame was received from this STA
@@ -341,7 +343,7 @@ struct sta_info {
        /* General information, mostly static */
        struct list_head list, free_list;
        struct rcu_head rcu_head;
-       struct sta_info __rcu *hnext;
+       struct rhash_head hash_node;
        struct ieee80211_local *local;
        struct ieee80211_sub_if_data *sdata;
        struct ieee80211_key __rcu *gtk[NUM_DEFAULT_KEYS + NUM_DEFAULT_MGMT_KEYS];
@@ -370,6 +372,7 @@ struct sta_info {
        struct sk_buff_head ps_tx_buf[IEEE80211_NUM_ACS];
        struct sk_buff_head tx_filtered[IEEE80211_NUM_ACS];
        unsigned long driver_buffered_tids;
+       unsigned long txq_buffered_tids;
 
        /* Updated from RX path only, no locking requirements */
        unsigned long rx_packets;
@@ -537,10 +540,6 @@ rcu_dereference_protected_tid_tx(struct sta_info *sta, int tid)
                                         lockdep_is_held(&sta->ampdu_mlme.mtx));
 }
 
-#define STA_HASH_SIZE 256
-#define STA_HASH(sta) (sta[5])
-
-
 /* Maximum number of frames to buffer per power saving station per AC */
 #define STA_MAX_TX_BUFFER      64
 
@@ -561,26 +560,15 @@ struct sta_info *sta_info_get(struct ieee80211_sub_if_data *sdata,
 struct sta_info *sta_info_get_bss(struct ieee80211_sub_if_data *sdata,
                                  const u8 *addr);
 
-static inline
-void for_each_sta_info_type_check(struct ieee80211_local *local,
-                                 const u8 *addr,
-                                 struct sta_info *sta,
-                                 struct sta_info *nxt)
-{
-}
+u32 sta_addr_hash(const void *key, u32 length, u32 seed);
+
+#define _sta_bucket_idx(_tbl, _a)                                      \
+       rht_bucket_index(_tbl, sta_addr_hash(_a, ETH_ALEN, (_tbl)->hash_rnd))
 
-#define for_each_sta_info(local, _addr, _sta, nxt)                     \
-       for (   /* initialise loop */                                   \
-               _sta = rcu_dereference(local->sta_hash[STA_HASH(_addr)]),\
-               nxt = _sta ? rcu_dereference(_sta->hnext) : NULL;       \
-               /* typecheck */                                         \
-               for_each_sta_info_type_check(local, (_addr), _sta, nxt),\
-               /* continue condition */                                \
-               _sta;                                                   \
-               /* advance loop */                                      \
-               _sta = nxt,                                             \
-               nxt = _sta ? rcu_dereference(_sta->hnext) : NULL        \
-            )                                                          \
+#define for_each_sta_info(local, tbl, _addr, _sta, _tmp)               \
+       rht_for_each_entry_rcu(_sta, _tmp, tbl,                         \
+                              _sta_bucket_idx(tbl, _addr),             \
+                              hash_node)                               \
        /* compare address and run code only if it matches */           \
        if (ether_addr_equal(_sta->sta.addr, (_addr)))
 
@@ -617,7 +605,7 @@ int sta_info_destroy_addr_bss(struct ieee80211_sub_if_data *sdata,
 
 void sta_info_recalc_tim(struct sta_info *sta);
 
-void sta_info_init(struct ieee80211_local *local);
+int sta_info_init(struct ieee80211_local *local);
 void sta_info_stop(struct ieee80211_local *local);
 
 /**
index 2c51742428d59ebda73b2819b8bfc4391128d36c..005fdbe39a8b2f694b2deaa3d55e1a2baef031f3 100644 (file)
@@ -654,7 +654,8 @@ void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb)
        struct ieee80211_supported_band *sband;
        struct ieee80211_sub_if_data *sdata;
        struct net_device *prev_dev = NULL;
-       struct sta_info *sta, *tmp;
+       struct sta_info *sta;
+       struct rhash_head *tmp;
        int retry_count;
        int rates_idx;
        bool send_to_cooked;
@@ -663,6 +664,7 @@ void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb)
        int rtap_len;
        int shift = 0;
        int tid = IEEE80211_NUM_TIDS;
+       const struct bucket_table *tbl;
 
        rates_idx = ieee80211_tx_get_rates(hw, info, &retry_count);
 
@@ -671,7 +673,9 @@ void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb)
        sband = local->hw.wiphy->bands[info->band];
        fc = hdr->frame_control;
 
-       for_each_sta_info(local, hdr->addr1, sta, tmp) {
+       tbl = rht_dereference_rcu(local->sta_hash.tbl, &local->sta_hash);
+
+       for_each_sta_info(local, tbl, hdr->addr1, sta, tmp) {
                /* skip wrong virtual interface */
                if (!ether_addr_equal(hdr->addr2, sta->sdata->vif.addr))
                        continue;
index e9e462b349e5f828841398ec869c4ec2bf56e6eb..790bd45081c49f764b75330e2b3b0e6cde08d04d 100644 (file)
@@ -2312,6 +2312,37 @@ TRACE_EVENT(drv_tdls_recv_channel_switch,
        )
 );
 
+TRACE_EVENT(drv_wake_tx_queue,
+       TP_PROTO(struct ieee80211_local *local,
+                struct ieee80211_sub_if_data *sdata,
+                struct txq_info *txq),
+
+       TP_ARGS(local, sdata, txq),
+
+       TP_STRUCT__entry(
+               LOCAL_ENTRY
+               VIF_ENTRY
+               STA_ENTRY
+               __field(u8, ac)
+               __field(u8, tid)
+       ),
+
+       TP_fast_assign(
+               struct ieee80211_sta *sta = txq->txq.sta;
+
+               LOCAL_ASSIGN;
+               VIF_ASSIGN;
+               STA_ASSIGN;
+               __entry->ac = txq->txq.ac;
+               __entry->tid = txq->txq.tid;
+       ),
+
+       TP_printk(
+               LOCAL_PR_FMT  VIF_PR_FMT  STA_PR_FMT " ac:%d tid:%d",
+               LOCAL_PR_ARG, VIF_PR_ARG, STA_PR_ARG, __entry->ac, __entry->tid
+       )
+);
+
 #ifdef CONFIG_MAC80211_MESSAGE_TRACING
 #undef TRACE_SYSTEM
 #define TRACE_SYSTEM mac80211_msg
index 9f7fb4eec37bde2af4de010e474561513709ff29..667111ee6a20fc48493f88605e8ed45e36d6d55e 100644 (file)
@@ -767,12 +767,22 @@ ieee80211_tx_h_rate_ctrl(struct ieee80211_tx_data *tx)
        return TX_CONTINUE;
 }
 
+static __le16 ieee80211_tx_next_seq(struct sta_info *sta, int tid)
+{
+       u16 *seq = &sta->tid_seq[tid];
+       __le16 ret = cpu_to_le16(*seq);
+
+       /* Increase the sequence number. */
+       *seq = (*seq + 0x10) & IEEE80211_SCTL_SEQ;
+
+       return ret;
+}
+
 static ieee80211_tx_result debug_noinline
 ieee80211_tx_h_sequence(struct ieee80211_tx_data *tx)
 {
        struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx->skb);
        struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)tx->skb->data;
-       u16 *seq;
        u8 *qc;
        int tid;
 
@@ -823,13 +833,10 @@ ieee80211_tx_h_sequence(struct ieee80211_tx_data *tx)
 
        qc = ieee80211_get_qos_ctl(hdr);
        tid = *qc & IEEE80211_QOS_CTL_TID_MASK;
-       seq = &tx->sta->tid_seq[tid];
        tx->sta->tx_msdu[tid]++;
 
-       hdr->seq_ctrl = cpu_to_le16(*seq);
-
-       /* Increase the sequence number. */
-       *seq = (*seq + 0x10) & IEEE80211_SCTL_SEQ;
+       if (!tx->sta->sta.txq[0])
+               hdr->seq_ctrl = ieee80211_tx_next_seq(tx->sta, tid);
 
        return TX_CONTINUE;
 }
@@ -1070,7 +1077,7 @@ static bool ieee80211_tx_prep_agg(struct ieee80211_tx_data *tx,
                 * nothing -- this aggregation session is being started
                 * but that might still fail with the driver
                 */
-       } else {
+       } else if (!tx->sta->sta.txq[tid]) {
                spin_lock(&tx->sta->lock);
                /*
                 * Need to re-check now, because we may get here
@@ -1211,13 +1218,102 @@ ieee80211_tx_prepare(struct ieee80211_sub_if_data *sdata,
        return TX_CONTINUE;
 }
 
+static void ieee80211_drv_tx(struct ieee80211_local *local,
+                            struct ieee80211_vif *vif,
+                            struct ieee80211_sta *pubsta,
+                            struct sk_buff *skb)
+{
+       struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data;
+       struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
+       struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+       struct ieee80211_tx_control control = {
+               .sta = pubsta,
+       };
+       struct ieee80211_txq *txq = NULL;
+       struct txq_info *txqi;
+       u8 ac;
+
+       if (info->control.flags & IEEE80211_TX_CTRL_PS_RESPONSE)
+               goto tx_normal;
+
+       if (!ieee80211_is_data(hdr->frame_control))
+               goto tx_normal;
+
+       if (pubsta) {
+               u8 tid = skb->priority & IEEE80211_QOS_CTL_TID_MASK;
+
+               txq = pubsta->txq[tid];
+       } else if (vif) {
+               txq = vif->txq;
+       }
+
+       if (!txq)
+               goto tx_normal;
+
+       ac = txq->ac;
+       txqi = to_txq_info(txq);
+       atomic_inc(&sdata->txqs_len[ac]);
+       if (atomic_read(&sdata->txqs_len[ac]) >= local->hw.txq_ac_max_pending)
+               netif_stop_subqueue(sdata->dev, ac);
+
+       skb_queue_tail(&txqi->queue, skb);
+       drv_wake_tx_queue(local, txqi);
+
+       return;
+
+tx_normal:
+       drv_tx(local, &control, skb);
+}
+
+struct sk_buff *ieee80211_tx_dequeue(struct ieee80211_hw *hw,
+                                    struct ieee80211_txq *txq)
+{
+       struct ieee80211_local *local = hw_to_local(hw);
+       struct ieee80211_sub_if_data *sdata = vif_to_sdata(txq->vif);
+       struct txq_info *txqi = container_of(txq, struct txq_info, txq);
+       struct ieee80211_hdr *hdr;
+       struct sk_buff *skb = NULL;
+       u8 ac = txq->ac;
+
+       spin_lock_bh(&txqi->queue.lock);
+
+       if (test_bit(IEEE80211_TXQ_STOP, &txqi->flags))
+               goto out;
+
+       skb = __skb_dequeue(&txqi->queue);
+       if (!skb)
+               goto out;
+
+       atomic_dec(&sdata->txqs_len[ac]);
+       if (__netif_subqueue_stopped(sdata->dev, ac))
+               ieee80211_propagate_queue_wake(local, sdata->vif.hw_queue[ac]);
+
+       hdr = (struct ieee80211_hdr *)skb->data;
+       if (txq->sta && ieee80211_is_data_qos(hdr->frame_control)) {
+               struct sta_info *sta = container_of(txq->sta, struct sta_info,
+                                                   sta);
+               struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+
+               hdr->seq_ctrl = ieee80211_tx_next_seq(sta, txq->tid);
+               if (test_bit(IEEE80211_TXQ_AMPDU, &txqi->flags))
+                       info->flags |= IEEE80211_TX_CTL_AMPDU;
+               else
+                       info->flags &= ~IEEE80211_TX_CTL_AMPDU;
+       }
+
+out:
+       spin_unlock_bh(&txqi->queue.lock);
+
+       return skb;
+}
+EXPORT_SYMBOL(ieee80211_tx_dequeue);
+
 static bool ieee80211_tx_frags(struct ieee80211_local *local,
                               struct ieee80211_vif *vif,
                               struct ieee80211_sta *sta,
                               struct sk_buff_head *skbs,
                               bool txpending)
 {
-       struct ieee80211_tx_control control;
        struct sk_buff *skb, *tmp;
        unsigned long flags;
 
@@ -1275,10 +1371,9 @@ static bool ieee80211_tx_frags(struct ieee80211_local *local,
                spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
 
                info->control.vif = vif;
-               control.sta = sta;
 
                __skb_unlink(skb, skbs);
-               drv_tx(local, &control, skb);
+               ieee80211_drv_tx(local, vif, sta, skb);
        }
 
        return true;
index d1742a7d9ea497f248c4987d712507dd4d851ced..79412f16b61db9953a4a537db3bd5693d7c61cdb 100644 (file)
@@ -308,6 +308,11 @@ void ieee80211_propagate_queue_wake(struct ieee80211_local *local, int queue)
                for (ac = 0; ac < n_acs; ac++) {
                        int ac_queue = sdata->vif.hw_queue[ac];
 
+                       if (local->ops->wake_tx_queue &&
+                           (atomic_read(&sdata->txqs_len[ac]) >
+                            local->hw.txq_ac_max_pending))
+                               continue;
+
                        if (ac_queue == queue ||
                            (sdata->vif.cab_queue == queue &&
                             local->queue_stop_reasons[ac_queue] == 0 &&
@@ -2189,46 +2194,6 @@ void ieee80211_recalc_min_chandef(struct ieee80211_sub_if_data *sdata)
        mutex_unlock(&local->chanctx_mtx);
 }
 
-static bool ieee80211_id_in_list(const u8 *ids, int n_ids, u8 id)
-{
-       int i;
-
-       for (i = 0; i < n_ids; i++)
-               if (ids[i] == id)
-                       return true;
-       return false;
-}
-
-size_t ieee80211_ie_split_ric(const u8 *ies, size_t ielen,
-                             const u8 *ids, int n_ids,
-                             const u8 *after_ric, int n_after_ric,
-                             size_t offset)
-{
-       size_t pos = offset;
-
-       while (pos < ielen && ieee80211_id_in_list(ids, n_ids, ies[pos])) {
-               if (ies[pos] == WLAN_EID_RIC_DATA && n_after_ric) {
-                       pos += 2 + ies[pos + 1];
-
-                       while (pos < ielen &&
-                              !ieee80211_id_in_list(after_ric, n_after_ric,
-                                                    ies[pos]))
-                               pos += 2 + ies[pos + 1];
-               } else {
-                       pos += 2 + ies[pos + 1];
-               }
-       }
-
-       return pos;
-}
-
-size_t ieee80211_ie_split(const u8 *ies, size_t ielen,
-                         const u8 *ids, int n_ids, size_t offset)
-{
-       return ieee80211_ie_split_ric(ies, ielen, ids, n_ids, NULL, 0, offset);
-}
-EXPORT_SYMBOL(ieee80211_ie_split);
-
 size_t ieee80211_ie_split_vendor(const u8 *ies, size_t ielen, size_t offset)
 {
        size_t pos = offset;
@@ -3352,3 +3317,20 @@ u8 *ieee80211_add_wmm_info_ie(u8 *buf, u8 qosinfo)
 
        return buf;
 }
+
+void ieee80211_init_tx_queue(struct ieee80211_sub_if_data *sdata,
+                            struct sta_info *sta,
+                            struct txq_info *txqi, int tid)
+{
+       skb_queue_head_init(&txqi->queue);
+       txqi->txq.vif = &sdata->vif;
+
+       if (sta) {
+               txqi->txq.sta = &sta->sta;
+               sta->sta.txq[tid] = &txqi->txq;
+               txqi->txq.ac = ieee802_1d_to_ac[tid & 7];
+       } else {
+               sdata->vif.txq = &txqi->txq;
+               txqi->txq.ac = IEEE80211_AC_BE;
+       }
+}
index b13dfb4ff001908160ba087db5d25377d43c5491..4f5543dd25243e0c959dacb00327155cd4e27e2b 100644 (file)
@@ -175,7 +175,7 @@ config CFG80211_INTERNAL_REGDB
          Most distributions have a CRDA package.  So if unsure, say N.
 
 config CFG80211_WEXT
-       bool "cfg80211 wireless extensions compatibility"
+       bool "cfg80211 wireless extensions compatibility" if !CFG80211_WEXT_EXPORT
        depends on CFG80211
        select WEXT_CORE
        default y if CFG80211_WEXT_EXPORT
index 6dd1ab3b10ea25c76e20a73998bb5f1ebc41b6b8..dd78445c7d50630524b7d33b96b73a2e416c662c 100644 (file)
@@ -5664,7 +5664,7 @@ static int nl80211_set_reg(struct sk_buff *skb, struct genl_info *info)
                }
        }
 
-       r = set_regdom(rd);
+       r = set_regdom(rd, REGD_SOURCE_CRDA);
        /* set_regdom took ownership */
        rd = NULL;
 
index be5f81caa488bf24a0bfc79c6ffb9ba534d1e4aa..0e347f888fe910d07e7a094058755f37bee9fcd9 100644 (file)
@@ -135,6 +135,11 @@ static spinlock_t reg_indoor_lock;
 /* Used to track the userspace process controlling the indoor setting */
 static u32 reg_is_indoor_portid;
 
+/* Max number of consecutive attempts to communicate with CRDA  */
+#define REG_MAX_CRDA_TIMEOUTS 10
+
+static u32 reg_crda_timeouts;
+
 static const struct ieee80211_regdomain *get_cfg80211_regdom(void)
 {
        return rtnl_dereference(cfg80211_regdomain);
@@ -485,7 +490,7 @@ static void reg_regdb_search(struct work_struct *work)
        mutex_unlock(&reg_regdb_search_mutex);
 
        if (!IS_ERR_OR_NULL(regdom))
-               set_regdom(regdom);
+               set_regdom(regdom, REGD_SOURCE_INTERNAL_DB);
 
        rtnl_unlock();
 }
@@ -535,15 +540,20 @@ static int call_crda(const char *alpha2)
        snprintf(country, sizeof(country), "COUNTRY=%c%c",
                 alpha2[0], alpha2[1]);
 
+       /* query internal regulatory database (if it exists) */
+       reg_regdb_query(alpha2);
+
+       if (reg_crda_timeouts > REG_MAX_CRDA_TIMEOUTS) {
+               pr_info("Exceeded CRDA call max attempts. Not calling CRDA\n");
+               return -EINVAL;
+       }
+
        if (!is_world_regdom((char *) alpha2))
                pr_info("Calling CRDA for country: %c%c\n",
                        alpha2[0], alpha2[1]);
        else
                pr_info("Calling CRDA to update world regulatory domain\n");
 
-       /* query internal regulatory database (if it exists) */
-       reg_regdb_query(alpha2);
-
        return kobject_uevent_env(&reg_pdev->dev.kobj, KOBJ_CHANGE, env);
 }
 
@@ -2293,6 +2303,9 @@ int regulatory_hint_user(const char *alpha2,
        request->initiator = NL80211_REGDOM_SET_BY_USER;
        request->user_reg_hint_type = user_reg_hint_type;
 
+       /* Allow calling CRDA again */
+       reg_crda_timeouts = 0;
+
        queue_regulatory_request(request);
 
        return 0;
@@ -2362,6 +2375,9 @@ int regulatory_hint(struct wiphy *wiphy, const char *alpha2)
        request->alpha2[1] = alpha2[1];
        request->initiator = NL80211_REGDOM_SET_BY_DRIVER;
 
+       /* Allow calling CRDA again */
+       reg_crda_timeouts = 0;
+
        queue_regulatory_request(request);
 
        return 0;
@@ -2415,6 +2431,9 @@ void regulatory_hint_country_ie(struct wiphy *wiphy, enum ieee80211_band band,
        request->initiator = NL80211_REGDOM_SET_BY_COUNTRY_IE;
        request->country_ie_env = env;
 
+       /* Allow calling CRDA again */
+       reg_crda_timeouts = 0;
+
        queue_regulatory_request(request);
        request = NULL;
 out:
@@ -2893,7 +2912,8 @@ static int reg_set_rd_country_ie(const struct ieee80211_regdomain *rd,
  * multiple drivers can be ironed out later. Caller must've already
  * kmalloc'd the rd structure.
  */
-int set_regdom(const struct ieee80211_regdomain *rd)
+int set_regdom(const struct ieee80211_regdomain *rd,
+              enum ieee80211_regd_source regd_src)
 {
        struct regulatory_request *lr;
        bool user_reset = false;
@@ -2904,6 +2924,9 @@ int set_regdom(const struct ieee80211_regdomain *rd)
                return -EINVAL;
        }
 
+       if (regd_src == REGD_SOURCE_CRDA)
+               reg_crda_timeouts = 0;
+
        lr = get_last_request();
 
        /* Note that this doesn't update the wiphys, this is done below */
@@ -3063,6 +3086,7 @@ static void reg_timeout_work(struct work_struct *work)
 {
        REG_DBG_PRINT("Timeout while waiting for CRDA to reply, restoring regulatory settings\n");
        rtnl_lock();
+       reg_crda_timeouts++;
        restore_regulatory_settings(true);
        rtnl_unlock();
 }
index a2c4e16459da06a3b6ebad974aaab665eda717f0..9f495d76eca075d6f779078196f9adddd98a34aa 100644 (file)
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+enum ieee80211_regd_source {
+       REGD_SOURCE_INTERNAL_DB,
+       REGD_SOURCE_CRDA,
+};
+
 extern const struct ieee80211_regdomain __rcu *cfg80211_regdomain;
 
 bool reg_is_valid_request(const char *alpha2);
@@ -46,7 +51,9 @@ void wiphy_regulatory_deregister(struct wiphy *wiphy);
 int __init regulatory_init(void);
 void regulatory_exit(void);
 
-int set_regdom(const struct ieee80211_regdomain *rd);
+int set_regdom(const struct ieee80211_regdomain *rd,
+              enum ieee80211_regd_source regd_src);
+
 unsigned int reg_get_max_bandwidth(const struct ieee80211_regdomain *rd,
                                   const struct ieee80211_reg_rule *rule);
 
index ea1da6621ff051028970f50154af56d6f2e01b46..d11454f87bacf9396241bd5bbc3ba643ef0b3302 100644 (file)
@@ -42,7 +42,7 @@ struct cfg80211_conn {
                CFG80211_CONN_CONNECTED,
        } state;
        u8 bssid[ETH_ALEN], prev_bssid[ETH_ALEN];
-       u8 *ie;
+       const u8 *ie;
        size_t ie_len;
        bool auto_auth, prev_bssid_valid;
 };
@@ -423,6 +423,62 @@ void cfg80211_sme_assoc_timeout(struct wireless_dev *wdev)
        schedule_work(&rdev->conn_work);
 }
 
+static int cfg80211_sme_get_conn_ies(struct wireless_dev *wdev,
+                                    const u8 *ies, size_t ies_len,
+                                    const u8 **out_ies, size_t *out_ies_len)
+{
+       struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
+       u8 *buf;
+       size_t offs;
+
+       if (!rdev->wiphy.extended_capabilities_len ||
+           (ies && cfg80211_find_ie(WLAN_EID_EXT_CAPABILITY, ies, ies_len))) {
+               *out_ies = kmemdup(ies, ies_len, GFP_KERNEL);
+               if (!*out_ies)
+                       return -ENOMEM;
+               *out_ies_len = ies_len;
+               return 0;
+       }
+
+       buf = kmalloc(ies_len + rdev->wiphy.extended_capabilities_len + 2,
+                     GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       if (ies_len) {
+               static const u8 before_extcapa[] = {
+                       /* not listing IEs expected to be created by driver */
+                       WLAN_EID_RSN,
+                       WLAN_EID_QOS_CAPA,
+                       WLAN_EID_RRM_ENABLED_CAPABILITIES,
+                       WLAN_EID_MOBILITY_DOMAIN,
+                       WLAN_EID_SUPPORTED_REGULATORY_CLASSES,
+                       WLAN_EID_BSS_COEX_2040,
+               };
+
+               offs = ieee80211_ie_split(ies, ies_len, before_extcapa,
+                                         ARRAY_SIZE(before_extcapa), 0);
+               memcpy(buf, ies, offs);
+               /* leave a whole for extended capabilities IE */
+               memcpy(buf + offs + rdev->wiphy.extended_capabilities_len + 2,
+                      ies + offs, ies_len - offs);
+       } else {
+               offs = 0;
+       }
+
+       /* place extended capabilities IE (with only driver capabilities) */
+       buf[offs] = WLAN_EID_EXT_CAPABILITY;
+       buf[offs + 1] = rdev->wiphy.extended_capabilities_len;
+       memcpy(buf + offs + 2,
+              rdev->wiphy.extended_capabilities,
+              rdev->wiphy.extended_capabilities_len);
+
+       *out_ies = buf;
+       *out_ies_len = ies_len + rdev->wiphy.extended_capabilities_len + 2;
+
+       return 0;
+}
+
 static int cfg80211_sme_connect(struct wireless_dev *wdev,
                                struct cfg80211_connect_params *connect,
                                const u8 *prev_bssid)
@@ -453,16 +509,14 @@ static int cfg80211_sme_connect(struct wireless_dev *wdev,
                memcpy(wdev->conn->bssid, connect->bssid, ETH_ALEN);
        }
 
-       if (connect->ie) {
-               wdev->conn->ie = kmemdup(connect->ie, connect->ie_len,
-                                       GFP_KERNEL);
-               wdev->conn->params.ie = wdev->conn->ie;
-               if (!wdev->conn->ie) {
-                       kfree(wdev->conn);
-                       wdev->conn = NULL;
-                       return -ENOMEM;
-               }
+       if (cfg80211_sme_get_conn_ies(wdev, connect->ie, connect->ie_len,
+                                     &wdev->conn->ie,
+                                     &wdev->conn->params.ie_len)) {
+               kfree(wdev->conn);
+               wdev->conn = NULL;
+               return -ENOMEM;
        }
+       wdev->conn->params.ie = wdev->conn->ie;
 
        if (connect->auth_type == NL80211_AUTHTYPE_AUTOMATIC) {
                wdev->conn->auto_auth = true;
index f218b151530a915c106376bde1643700750e5007..70051ab52f4f34d2817e76210aafa70bbda62e60 100644 (file)
@@ -1290,6 +1290,47 @@ int cfg80211_get_p2p_attr(const u8 *ies, unsigned int len,
 }
 EXPORT_SYMBOL(cfg80211_get_p2p_attr);
 
+static bool ieee80211_id_in_list(const u8 *ids, int n_ids, u8 id)
+{
+       int i;
+
+       for (i = 0; i < n_ids; i++)
+               if (ids[i] == id)
+                       return true;
+       return false;
+}
+
+size_t ieee80211_ie_split_ric(const u8 *ies, size_t ielen,
+                             const u8 *ids, int n_ids,
+                             const u8 *after_ric, int n_after_ric,
+                             size_t offset)
+{
+       size_t pos = offset;
+
+       while (pos < ielen && ieee80211_id_in_list(ids, n_ids, ies[pos])) {
+               if (ies[pos] == WLAN_EID_RIC_DATA && n_after_ric) {
+                       pos += 2 + ies[pos + 1];
+
+                       while (pos < ielen &&
+                              !ieee80211_id_in_list(after_ric, n_after_ric,
+                                                    ies[pos]))
+                               pos += 2 + ies[pos + 1];
+               } else {
+                       pos += 2 + ies[pos + 1];
+               }
+       }
+
+       return pos;
+}
+EXPORT_SYMBOL(ieee80211_ie_split_ric);
+
+size_t ieee80211_ie_split(const u8 *ies, size_t ielen,
+                         const u8 *ids, int n_ids, size_t offset)
+{
+       return ieee80211_ie_split_ric(ies, ielen, ids, n_ids, NULL, 0, offset);
+}
+EXPORT_SYMBOL(ieee80211_ie_split);
+
 bool ieee80211_operating_class_to_band(u8 operating_class,
                                       enum ieee80211_band *band)
 {
index 4e21b72dd7093ff7ff0c116b44bb8d75ee2db3b5..30594bfa5fb1c00fffed6e353b21acd4afbf3c28 100644 (file)
@@ -103,10 +103,13 @@ static struct nlmsg_perm nlmsg_xfrm_perms[] =
        { XFRM_MSG_FLUSHPOLICY, NETLINK_XFRM_SOCKET__NLMSG_WRITE },
        { XFRM_MSG_NEWAE,       NETLINK_XFRM_SOCKET__NLMSG_WRITE },
        { XFRM_MSG_GETAE,       NETLINK_XFRM_SOCKET__NLMSG_READ  },
+       { XFRM_MSG_REPORT,      NETLINK_XFRM_SOCKET__NLMSG_READ  },
+       { XFRM_MSG_MIGRATE,     NETLINK_XFRM_SOCKET__NLMSG_WRITE },
        { XFRM_MSG_NEWSADINFO,  NETLINK_XFRM_SOCKET__NLMSG_READ  },
        { XFRM_MSG_GETSADINFO,  NETLINK_XFRM_SOCKET__NLMSG_READ  },
        { XFRM_MSG_NEWSPDINFO,  NETLINK_XFRM_SOCKET__NLMSG_WRITE },
        { XFRM_MSG_GETSPDINFO,  NETLINK_XFRM_SOCKET__NLMSG_READ  },
+       { XFRM_MSG_MAPPING,     NETLINK_XFRM_SOCKET__NLMSG_READ  },
 };
 
 static struct nlmsg_perm nlmsg_audit_perms[] =