mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-25 21:24:08 +08:00
There isn't much left, but we have
* new mac80211 internal software queue to allow drivers to have shorter hardware queues and pull on-demand * use rhashtable for mac80211 station table * minstrel rate control debug improvements and some refactoring * fix noisy message about TX power reduction * fix continuous message printing and activity if CRDA doesn't respond * fix VHT-related capabilities with "iw connect" or "iwconfig ..." * fix Kconfig for cfg80211 wireless extensions compatibility -----BEGIN PGP SIGNATURE----- iQIcBAABCAAGBQJVJ7CPAAoJEDBSmw7B7bqr8+IQAKCAbUyd6PFRT5tcz9kW5GCW /ibb+n1e14yWKgNEe1gddUGKG/L3HGCBXNkCYzR2M8mlL7dLPqspaBcGHS4dx8F4 D0AuikqvtXIxfAXi0zmU2uo7rH7u2X34R2LtS8AlKByD+jmFvxMiPPvxNFgzJu/7 63UQm73p2pnu/KdXLW1OQEcpZtZJ9+N/uBiq9zbVdX3A8T84ME0oMyy+EAQqCZdK CcsTXHCnAgmmXWJlu1JRdopr1bd38mSGB70eXduFtPqDdmtQRnoaCQ9e+tJDA4j4 svEw0yDmsc4WG1EKLKKCRd3uFOZsng+lcXrHfpm5wlSPpCOItfQ9BzT3x1u6Y5JU Z1WMOMkkEce+95U7/RLoXwC/2RS3XelUXTde4cGIRMvO5drOrU58P0gdn3J+yKbv 6v+2GGKy/39tdXUOxIl3EZT/huIl+h1UNO8C2hyaEwdXK+X1zl31/u6kk1Ns18Wr YPEJixxHx0zR8jaZgDC7OlWLuqn4Ay+Ls9yCyIesdHzKpizJKqn83PntYnpJmxoA 9hlIyRDWnqH44KxzB85ni1C2Qudec3mcCWIWV7M+UoSC1Cgs/LxDzH7kRejR2ZIl vRhg5pqyr53L0h2lq5DO4Cj4UzbXb7YioKJRxjyKloNOlRrCZtK/VEsHbdsKEcIp d/wHj1AyFZeQfuhk8Qqr =mtuo -----END PGP SIGNATURE----- Merge tag 'mac80211-next-for-davem-2015-04-10' of git://git.kernel.org/pub/scm/linux/kernel/git/jberg/mac80211-next Johannes Berg says: ==================== There isn't much left, but we have * new mac80211 internal software queue to allow drivers to have shorter hardware queues and pull on-demand * use rhashtable for mac80211 station table * minstrel rate control debug improvements and some refactoring * fix noisy message about TX power reduction * fix continuous message printing and activity if CRDA doesn't respond * fix VHT-related capabilities with "iw connect" or "iwconfig ..." * fix Kconfig for cfg80211 wireless extensions compatibility ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
commit
4e78eb0dbf
@ -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
|
||||
|
@ -84,6 +84,39 @@
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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
|
||||
* ieee80211_tx_dequeue - dequeue a packet from a software tx queue
|
||||
*
|
||||
* @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
|
||||
* @hw: pointer as obtained from ieee80211_alloc_hw()
|
||||
* @txq: pointer obtained from station or virtual interface
|
||||
*
|
||||
* 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.
|
||||
* 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 */
|
||||
|
@ -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)
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 = {
|
||||
|
@ -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)
|
||||
@ -24,11 +23,34 @@
|
||||
|
||||
/*
|
||||
* 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);
|
||||
|
||||
|
@ -54,59 +54,6 @@
|
||||
#include <net/mac80211.h>
|
||||
#include "rc80211_minstrel.h"
|
||||
|
||||
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;
|
||||
char *p;
|
||||
|
||||
ms = kmalloc(2048, GFP_KERNEL);
|
||||
if (!ms)
|
||||
return -ENOMEM;
|
||||
|
||||
file->private_data = ms;
|
||||
p = ms->buf;
|
||||
p += sprintf(p, "rate tpt eprob *prob"
|
||||
" *ok(*cum) ok( cum)\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;
|
||||
|
||||
*(p++) = (i == mi->max_tp_rate[0]) ? 'A' : ' ';
|
||||
*(p++) = (i == mi->max_tp_rate[1]) ? 'B' : ' ';
|
||||
*(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,
|
||||
(mr->bitrate & 1 ? ".5" : " "));
|
||||
|
||||
tp = MINSTREL_TRUNC(mrs->cur_tp / 10);
|
||||
prob = MINSTREL_TRUNC(mrs->cur_prob * 1000);
|
||||
eprob = MINSTREL_TRUNC(mrs->probability * 1000);
|
||||
|
||||
p += sprintf(p, " %4u.%1u %3u.%1u %3u.%1u"
|
||||
" %4u(%4u) %9llu(%9llu)\n",
|
||||
tp / 10, tp % 10,
|
||||
eprob / 10, eprob % 10,
|
||||
prob / 10, prob % 10,
|
||||
mrs->last_success,
|
||||
mrs->last_attempts,
|
||||
(unsigned long long)mrs->succ_hist,
|
||||
(unsigned long long)mrs->att_hist);
|
||||
}
|
||||
p += sprintf(p, "\nTotal packet count:: ideal %d "
|
||||
"lookaround %d\n\n",
|
||||
mi->total_packets - mi->sample_packets,
|
||||
mi->sample_packets);
|
||||
ms->len = p - ms->buf;
|
||||
|
||||
WARN_ON(ms->len + sizeof(*ms) > 2048);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t
|
||||
minstrel_stats_read(struct file *file, char __user *buf, size_t len, loff_t *ppos)
|
||||
{
|
||||
@ -123,6 +70,73 @@ minstrel_stats_release(struct inode *inode, struct file *file)
|
||||
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_max, tp_avg, prob, eprob;
|
||||
char *p;
|
||||
|
||||
ms = kmalloc(2048, GFP_KERNEL);
|
||||
if (!ms)
|
||||
return -ENOMEM;
|
||||
|
||||
file->private_data = ms;
|
||||
p = ms->buf;
|
||||
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;
|
||||
|
||||
*(p++) = (i == mi->max_tp_rate[0]) ? 'A' : ' ';
|
||||
*(p++) = (i == mi->max_tp_rate[1]) ? 'B' : ' ';
|
||||
*(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,
|
||||
(mr->bitrate & 1 ? ".5" : " "));
|
||||
p += sprintf(p, "%3u ", i);
|
||||
p += sprintf(p, "%6u ", 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, "%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,
|
||||
(unsigned long long)mrs->att_hist);
|
||||
}
|
||||
p += sprintf(p, "\nTotal packet count:: ideal %d "
|
||||
"lookaround %d\n\n",
|
||||
mi->total_packets - mi->sample_packets,
|
||||
mi->sample_packets);
|
||||
ms->len = p - ms->buf;
|
||||
|
||||
WARN_ON(ms->len + sizeof(*ms) > 2048);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations minstrel_stat_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = minstrel_stats_open,
|
||||
@ -131,6 +145,72 @@ static const struct file_operations minstrel_stat_fops = {
|
||||
.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 = 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);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations minstrel_stat_csv_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = minstrel_stats_csv_open,
|
||||
.read = minstrel_stats_read,
|
||||
.release = minstrel_stats_release,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
void
|
||||
minstrel_add_sta_debugfs(void *priv, void *priv_sta, struct dentry *dir)
|
||||
{
|
||||
@ -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);
|
||||
}
|
||||
|
@ -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 cur_tp from pkt per second in kbps */
|
||||
return mi->groups[i].rates[j].cur_tp * AVG_PKT_SIZE * 8 / 1024;
|
||||
/* 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;
|
||||
|
||||
return tp_avg;
|
||||
}
|
||||
|
||||
static const struct rate_control_ops mac80211_minstrel_ht = {
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -64,32 +64,20 @@
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 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 _sta_bucket_idx(_tbl, _a) \
|
||||
rht_bucket_index(_tbl, sta_addr_hash(_a, ETH_ALEN, (_tbl)->hash_rnd))
|
||||
|
||||
#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);
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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(®_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(®_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();
|
||||
}
|
||||
|
@ -16,6 +16,11 @@
|
||||
* 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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user