Commit 4a4b8169 authored by Andrew Zaborowski's avatar Andrew Zaborowski Committed by Johannes Berg

cfg80211: Accept multiple RSSI thresholds for CQM

Change the SET CQM command's RSSI threshold attribute to accept any
number of thresholds as a sorted array.  The API should be backwards
compatible so that if one s32 threshold value is passed, the old
mechanism is enabled.  The netlink event generated is the same in both
cases.

cfg80211 handles an arbitrary number of RSSI thresholds but drivers have
to provide a method (set_cqm_rssi_range_config) that configures a range
set by a high and a low value.  Drivers have to call back when the RSSI
goes out of that range and there's no additional event for each time the
range is reconfigured as there was with the current one-threshold API.

This method doesn't have a hysteresis parameter because there's no
benefit to the cfg80211 code from having the hysteresis be handled by
hardware/driver in terms of the number of wakeups.  At the same time
it would likely be less consistent between drivers if offloaded or
done in the drivers.
Signed-off-by: default avatarAndrew Zaborowski <andrew.zaborowski@intel.com>
Signed-off-by: default avatarJohannes Berg <johannes.berg@intel.com>
parent 3eb0928f
...@@ -2712,6 +2712,11 @@ struct cfg80211_nan_func { ...@@ -2712,6 +2712,11 @@ struct cfg80211_nan_func {
* the current level is above/below the configured threshold; this may * the current level is above/below the configured threshold; this may
* need some care when the configuration is changed (without first being * need some care when the configuration is changed (without first being
* disabled.) * disabled.)
* @set_cqm_rssi_range_config: Configure two RSSI thresholds in the
* connection quality monitor. An event is to be sent only when the
* signal level is found to be outside the two values. The driver should
* set %NL80211_EXT_FEATURE_CQM_RSSI_LIST if this method is implemented.
* If it is provided then there's no point providing @set_cqm_rssi_config.
* @set_cqm_txe_config: Configure connection quality monitor TX error * @set_cqm_txe_config: Configure connection quality monitor TX error
* thresholds. * thresholds.
* @sched_scan_start: Tell the driver to start a scheduled scan. * @sched_scan_start: Tell the driver to start a scheduled scan.
...@@ -3001,6 +3006,10 @@ struct cfg80211_ops { ...@@ -3001,6 +3006,10 @@ struct cfg80211_ops {
struct net_device *dev, struct net_device *dev,
s32 rssi_thold, u32 rssi_hyst); s32 rssi_thold, u32 rssi_hyst);
int (*set_cqm_rssi_range_config)(struct wiphy *wiphy,
struct net_device *dev,
s32 rssi_low, s32 rssi_high);
int (*set_cqm_txe_config)(struct wiphy *wiphy, int (*set_cqm_txe_config)(struct wiphy *wiphy,
struct net_device *dev, struct net_device *dev,
u32 rate, u32 pkts, u32 intvl); u32 rate, u32 pkts, u32 intvl);
...@@ -3871,6 +3880,7 @@ void wiphy_free(struct wiphy *wiphy); ...@@ -3871,6 +3880,7 @@ void wiphy_free(struct wiphy *wiphy);
struct cfg80211_conn; struct cfg80211_conn;
struct cfg80211_internal_bss; struct cfg80211_internal_bss;
struct cfg80211_cached_keys; struct cfg80211_cached_keys;
struct cfg80211_cqm_config;
/** /**
* struct wireless_dev - wireless device state * struct wireless_dev - wireless device state
...@@ -3934,6 +3944,7 @@ struct cfg80211_cached_keys; ...@@ -3934,6 +3944,7 @@ struct cfg80211_cached_keys;
* @event_list: (private) list for internal event processing * @event_list: (private) list for internal event processing
* @event_lock: (private) lock for event list * @event_lock: (private) lock for event list
* @owner_nlportid: (private) owner socket port ID * @owner_nlportid: (private) owner socket port ID
* @cqm_config: (private) nl80211 RSSI monitor state
*/ */
struct wireless_dev { struct wireless_dev {
struct wiphy *wiphy; struct wiphy *wiphy;
...@@ -4002,6 +4013,8 @@ struct wireless_dev { ...@@ -4002,6 +4013,8 @@ struct wireless_dev {
bool prev_bssid_valid; bool prev_bssid_valid;
} wext; } wext;
#endif #endif
struct cfg80211_cqm_config *cqm_config;
}; };
static inline u8 *wdev_address(struct wireless_dev *wdev) static inline u8 *wdev_address(struct wireless_dev *wdev)
......
...@@ -3942,7 +3942,10 @@ enum nl80211_ps_state { ...@@ -3942,7 +3942,10 @@ enum nl80211_ps_state {
* @__NL80211_ATTR_CQM_INVALID: invalid * @__NL80211_ATTR_CQM_INVALID: invalid
* @NL80211_ATTR_CQM_RSSI_THOLD: RSSI threshold in dBm. This value specifies * @NL80211_ATTR_CQM_RSSI_THOLD: RSSI threshold in dBm. This value specifies
* the threshold for the RSSI level at which an event will be sent. Zero * the threshold for the RSSI level at which an event will be sent. Zero
* to disable. * to disable. Alternatively, if %NL80211_EXT_FEATURE_CQM_RSSI_LIST is
* set, multiple values can be supplied as a low-to-high sorted array of
* threshold values in dBm. Events will be sent when the RSSI value
* crosses any of the thresholds.
* @NL80211_ATTR_CQM_RSSI_HYST: RSSI hysteresis in dBm. This value specifies * @NL80211_ATTR_CQM_RSSI_HYST: RSSI hysteresis in dBm. This value specifies
* the minimum amount the RSSI level must change after an event before a * the minimum amount the RSSI level must change after an event before a
* new event may be issued (to reduce effects of RSSI oscillation). * new event may be issued (to reduce effects of RSSI oscillation).
...@@ -4753,6 +4756,9 @@ enum nl80211_feature_flags { ...@@ -4753,6 +4756,9 @@ enum nl80211_feature_flags {
* @NL80211_EXT_FEATURE_SCHED_SCAN_RELATIVE_RSSI: The driver supports sched_scan * @NL80211_EXT_FEATURE_SCHED_SCAN_RELATIVE_RSSI: The driver supports sched_scan
* for reporting BSSs with better RSSI than the current connected BSS * for reporting BSSs with better RSSI than the current connected BSS
* (%NL80211_ATTR_SCHED_SCAN_RELATIVE_RSSI). * (%NL80211_ATTR_SCHED_SCAN_RELATIVE_RSSI).
* @NL80211_EXT_FEATURE_CQM_RSSI_LIST: With this driver the
* %NL80211_ATTR_CQM_RSSI_THOLD attribute accepts a list of zero or more
* RSSI threshold values to monitor rather than exactly one threshold.
* *
* @NUM_NL80211_EXT_FEATURES: number of extended features. * @NUM_NL80211_EXT_FEATURES: number of extended features.
* @MAX_NL80211_EXT_FEATURES: highest extended feature index. * @MAX_NL80211_EXT_FEATURES: highest extended feature index.
...@@ -4771,6 +4777,7 @@ enum nl80211_ext_feature_index { ...@@ -4771,6 +4777,7 @@ enum nl80211_ext_feature_index {
NL80211_EXT_FEATURE_MGMT_TX_RANDOM_TA, NL80211_EXT_FEATURE_MGMT_TX_RANDOM_TA,
NL80211_EXT_FEATURE_MGMT_TX_RANDOM_TA_CONNECTED, NL80211_EXT_FEATURE_MGMT_TX_RANDOM_TA_CONNECTED,
NL80211_EXT_FEATURE_SCHED_SCAN_RELATIVE_RSSI, NL80211_EXT_FEATURE_SCHED_SCAN_RELATIVE_RSSI,
NL80211_EXT_FEATURE_CQM_RSSI_LIST,
/* add new features before the definition below */ /* add new features before the definition below */
NUM_NL80211_EXT_FEATURES, NUM_NL80211_EXT_FEATURES,
......
...@@ -954,6 +954,12 @@ void wiphy_rfkill_set_hw_state(struct wiphy *wiphy, bool blocked) ...@@ -954,6 +954,12 @@ void wiphy_rfkill_set_hw_state(struct wiphy *wiphy, bool blocked)
} }
EXPORT_SYMBOL(wiphy_rfkill_set_hw_state); EXPORT_SYMBOL(wiphy_rfkill_set_hw_state);
void cfg80211_cqm_config_free(struct wireless_dev *wdev)
{
kfree(wdev->cqm_config);
wdev->cqm_config = NULL;
}
void cfg80211_unregister_wdev(struct wireless_dev *wdev) void cfg80211_unregister_wdev(struct wireless_dev *wdev)
{ {
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy); struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
...@@ -980,6 +986,8 @@ void cfg80211_unregister_wdev(struct wireless_dev *wdev) ...@@ -980,6 +986,8 @@ void cfg80211_unregister_wdev(struct wireless_dev *wdev)
WARN_ON_ONCE(1); WARN_ON_ONCE(1);
break; break;
} }
cfg80211_cqm_config_free(wdev);
} }
EXPORT_SYMBOL(cfg80211_unregister_wdev); EXPORT_SYMBOL(cfg80211_unregister_wdev);
...@@ -1234,6 +1242,7 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb, ...@@ -1234,6 +1242,7 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb,
kzfree(wdev->wext.keys); kzfree(wdev->wext.keys);
#endif #endif
flush_work(&wdev->disconnect_wk); flush_work(&wdev->disconnect_wk);
cfg80211_cqm_config_free(wdev);
} }
/* /*
* synchronise (so that we won't find this netdev * synchronise (so that we won't find this netdev
......
...@@ -272,6 +272,13 @@ struct cfg80211_iface_destroy { ...@@ -272,6 +272,13 @@ struct cfg80211_iface_destroy {
u32 nlportid; u32 nlportid;
}; };
struct cfg80211_cqm_config {
u32 rssi_hyst;
s32 last_rssi_event_value;
int n_rssi_thresholds;
s32 rssi_thresholds[0];
};
void cfg80211_destroy_ifaces(struct cfg80211_registered_device *rdev); void cfg80211_destroy_ifaces(struct cfg80211_registered_device *rdev);
/* free object */ /* free object */
...@@ -512,4 +519,6 @@ void cfg80211_stop_nan(struct cfg80211_registered_device *rdev, ...@@ -512,4 +519,6 @@ void cfg80211_stop_nan(struct cfg80211_registered_device *rdev,
#define CFG80211_DEV_WARN_ON(cond) ({bool __r = (cond); __r; }) #define CFG80211_DEV_WARN_ON(cond) ({bool __r = (cond); __r; })
#endif #endif
void cfg80211_cqm_config_free(struct wireless_dev *wdev);
#endif /* __NET_WIRELESS_CORE_H */ #endif /* __NET_WIRELESS_CORE_H */
...@@ -9473,7 +9473,7 @@ static int nl80211_get_power_save(struct sk_buff *skb, struct genl_info *info) ...@@ -9473,7 +9473,7 @@ static int nl80211_get_power_save(struct sk_buff *skb, struct genl_info *info)
static const struct nla_policy static const struct nla_policy
nl80211_attr_cqm_policy[NL80211_ATTR_CQM_MAX + 1] = { nl80211_attr_cqm_policy[NL80211_ATTR_CQM_MAX + 1] = {
[NL80211_ATTR_CQM_RSSI_THOLD] = { .type = NLA_U32 }, [NL80211_ATTR_CQM_RSSI_THOLD] = { .type = NLA_BINARY },
[NL80211_ATTR_CQM_RSSI_HYST] = { .type = NLA_U32 }, [NL80211_ATTR_CQM_RSSI_HYST] = { .type = NLA_U32 },
[NL80211_ATTR_CQM_RSSI_THRESHOLD_EVENT] = { .type = NLA_U32 }, [NL80211_ATTR_CQM_RSSI_THRESHOLD_EVENT] = { .type = NLA_U32 },
[NL80211_ATTR_CQM_TXE_RATE] = { .type = NLA_U32 }, [NL80211_ATTR_CQM_TXE_RATE] = { .type = NLA_U32 },
...@@ -9502,28 +9502,123 @@ static int nl80211_set_cqm_txe(struct genl_info *info, ...@@ -9502,28 +9502,123 @@ static int nl80211_set_cqm_txe(struct genl_info *info,
return rdev_set_cqm_txe_config(rdev, dev, rate, pkts, intvl); return rdev_set_cqm_txe_config(rdev, dev, rate, pkts, intvl);
} }
static int cfg80211_cqm_rssi_update(struct cfg80211_registered_device *rdev,
struct net_device *dev)
{
struct wireless_dev *wdev = dev->ieee80211_ptr;
s32 last, low, high;
u32 hyst;
int i, n;
int err;
/* RSSI reporting disabled? */
if (!wdev->cqm_config)
return rdev_set_cqm_rssi_range_config(rdev, dev, 0, 0);
/*
* Obtain current RSSI value if possible, if not and no RSSI threshold
* event has been received yet, we should receive an event after a
* connection is established and enough beacons received to calculate
* the average.
*/
if (!wdev->cqm_config->last_rssi_event_value && wdev->current_bss &&
rdev->ops->get_station) {
struct station_info sinfo;
u8 *mac_addr;
mac_addr = wdev->current_bss->pub.bssid;
err = rdev_get_station(rdev, dev, mac_addr, &sinfo);
if (err)
return err;
if (sinfo.filled & BIT(NL80211_STA_INFO_BEACON_SIGNAL_AVG))
wdev->cqm_config->last_rssi_event_value =
(s8) sinfo.rx_beacon_signal_avg;
}
last = wdev->cqm_config->last_rssi_event_value;
hyst = wdev->cqm_config->rssi_hyst;
n = wdev->cqm_config->n_rssi_thresholds;
for (i = 0; i < n; i++)
if (last < wdev->cqm_config->rssi_thresholds[i])
break;
low = i > 0 ?
(wdev->cqm_config->rssi_thresholds[i - 1] - hyst) : S32_MIN;
high = i < n ?
(wdev->cqm_config->rssi_thresholds[i] + hyst - 1) : S32_MAX;
return rdev_set_cqm_rssi_range_config(rdev, dev, low, high);
}
static int nl80211_set_cqm_rssi(struct genl_info *info, static int nl80211_set_cqm_rssi(struct genl_info *info,
s32 threshold, u32 hysteresis) const s32 *thresholds, int n_thresholds,
u32 hysteresis)
{ {
struct cfg80211_registered_device *rdev = info->user_ptr[0]; struct cfg80211_registered_device *rdev = info->user_ptr[0];
struct net_device *dev = info->user_ptr[1]; struct net_device *dev = info->user_ptr[1];
struct wireless_dev *wdev = dev->ieee80211_ptr; struct wireless_dev *wdev = dev->ieee80211_ptr;
int i, err;
s32 prev = S32_MIN;
if (threshold > 0) /* Check all values negative and sorted */
return -EINVAL; for (i = 0; i < n_thresholds; i++) {
if (thresholds[i] > 0 || thresholds[i] <= prev)
/* disabling - hysteresis should also be zero then */ return -EINVAL;
if (threshold == 0)
hysteresis = 0;
if (!rdev->ops->set_cqm_rssi_config) prev = thresholds[i];
return -EOPNOTSUPP; }
if (wdev->iftype != NL80211_IFTYPE_STATION && if (wdev->iftype != NL80211_IFTYPE_STATION &&
wdev->iftype != NL80211_IFTYPE_P2P_CLIENT) wdev->iftype != NL80211_IFTYPE_P2P_CLIENT)
return -EOPNOTSUPP; return -EOPNOTSUPP;
return rdev_set_cqm_rssi_config(rdev, dev, threshold, hysteresis); wdev_lock(wdev);
cfg80211_cqm_config_free(wdev);
wdev_unlock(wdev);
if (n_thresholds <= 1 && rdev->ops->set_cqm_rssi_config) {
if (n_thresholds == 0 || thresholds[0] == 0) /* Disabling */
return rdev_set_cqm_rssi_config(rdev, dev, 0, 0);
return rdev_set_cqm_rssi_config(rdev, dev,
thresholds[0], hysteresis);
}
if (!wiphy_ext_feature_isset(&rdev->wiphy,
NL80211_EXT_FEATURE_CQM_RSSI_LIST))
return -EOPNOTSUPP;
if (n_thresholds == 1 && thresholds[0] == 0) /* Disabling */
n_thresholds = 0;
wdev_lock(wdev);
if (n_thresholds) {
struct cfg80211_cqm_config *cqm_config;
cqm_config = kzalloc(sizeof(struct cfg80211_cqm_config) +
n_thresholds * sizeof(s32), GFP_KERNEL);
if (!cqm_config) {
err = -ENOMEM;
goto unlock;
}
cqm_config->rssi_hyst = hysteresis;
cqm_config->n_rssi_thresholds = n_thresholds;
memcpy(cqm_config->rssi_thresholds, thresholds,
n_thresholds * sizeof(s32));
wdev->cqm_config = cqm_config;
}
err = cfg80211_cqm_rssi_update(rdev, dev);
unlock:
wdev_unlock(wdev);
return err;
} }
static int nl80211_set_cqm(struct sk_buff *skb, struct genl_info *info) static int nl80211_set_cqm(struct sk_buff *skb, struct genl_info *info)
...@@ -9543,10 +9638,16 @@ static int nl80211_set_cqm(struct sk_buff *skb, struct genl_info *info) ...@@ -9543,10 +9638,16 @@ static int nl80211_set_cqm(struct sk_buff *skb, struct genl_info *info)
if (attrs[NL80211_ATTR_CQM_RSSI_THOLD] && if (attrs[NL80211_ATTR_CQM_RSSI_THOLD] &&
attrs[NL80211_ATTR_CQM_RSSI_HYST]) { attrs[NL80211_ATTR_CQM_RSSI_HYST]) {
s32 threshold = nla_get_s32(attrs[NL80211_ATTR_CQM_RSSI_THOLD]); const s32 *thresholds =
nla_data(attrs[NL80211_ATTR_CQM_RSSI_THOLD]);
int len = nla_len(attrs[NL80211_ATTR_CQM_RSSI_THOLD]);
u32 hysteresis = nla_get_u32(attrs[NL80211_ATTR_CQM_RSSI_HYST]); u32 hysteresis = nla_get_u32(attrs[NL80211_ATTR_CQM_RSSI_HYST]);
return nl80211_set_cqm_rssi(info, threshold, hysteresis); if (len % 4)
return -EINVAL;
return nl80211_set_cqm_rssi(info, thresholds, len / 4,
hysteresis);
} }
if (attrs[NL80211_ATTR_CQM_TXE_RATE] && if (attrs[NL80211_ATTR_CQM_TXE_RATE] &&
...@@ -13983,6 +14084,8 @@ void cfg80211_cqm_rssi_notify(struct net_device *dev, ...@@ -13983,6 +14084,8 @@ void cfg80211_cqm_rssi_notify(struct net_device *dev,
s32 rssi_level, gfp_t gfp) s32 rssi_level, gfp_t gfp)
{ {
struct sk_buff *msg; struct sk_buff *msg;
struct wireless_dev *wdev = dev->ieee80211_ptr;
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
trace_cfg80211_cqm_rssi_notify(dev, rssi_event, rssi_level); trace_cfg80211_cqm_rssi_notify(dev, rssi_event, rssi_level);
...@@ -13990,6 +14093,15 @@ void cfg80211_cqm_rssi_notify(struct net_device *dev, ...@@ -13990,6 +14093,15 @@ void cfg80211_cqm_rssi_notify(struct net_device *dev,
rssi_event != NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH)) rssi_event != NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH))
return; return;
if (wdev->cqm_config) {
wdev->cqm_config->last_rssi_event_value = rssi_level;
cfg80211_cqm_rssi_update(rdev, dev);
if (rssi_level == 0)
rssi_level = wdev->cqm_config->last_rssi_event_value;
}
msg = cfg80211_prepare_cqm(dev, NULL, gfp); msg = cfg80211_prepare_cqm(dev, NULL, gfp);
if (!msg) if (!msg)
return; return;
......
...@@ -749,6 +749,18 @@ rdev_set_cqm_rssi_config(struct cfg80211_registered_device *rdev, ...@@ -749,6 +749,18 @@ rdev_set_cqm_rssi_config(struct cfg80211_registered_device *rdev,
return ret; return ret;
} }
static inline int
rdev_set_cqm_rssi_range_config(struct cfg80211_registered_device *rdev,
struct net_device *dev, s32 low, s32 high)
{
int ret;
trace_rdev_set_cqm_rssi_range_config(&rdev->wiphy, dev, low, high);
ret = rdev->ops->set_cqm_rssi_range_config(&rdev->wiphy, dev,
low, high);
trace_rdev_return_int(&rdev->wiphy, ret);
return ret;
}
static inline int static inline int
rdev_set_cqm_txe_config(struct cfg80211_registered_device *rdev, rdev_set_cqm_txe_config(struct cfg80211_registered_device *rdev,
struct net_device *dev, u32 rate, u32 pkts, u32 intvl) struct net_device *dev, u32 rate, u32 pkts, u32 intvl)
......
...@@ -1322,6 +1322,28 @@ TRACE_EVENT(rdev_set_cqm_rssi_config, ...@@ -1322,6 +1322,28 @@ TRACE_EVENT(rdev_set_cqm_rssi_config,
__entry->rssi_thold, __entry->rssi_hyst) __entry->rssi_thold, __entry->rssi_hyst)
); );
TRACE_EVENT(rdev_set_cqm_rssi_range_config,
TP_PROTO(struct wiphy *wiphy,
struct net_device *netdev, s32 low, s32 high),
TP_ARGS(wiphy, netdev, low, high),
TP_STRUCT__entry(
WIPHY_ENTRY
NETDEV_ENTRY
__field(s32, rssi_low)
__field(s32, rssi_high)
),
TP_fast_assign(
WIPHY_ASSIGN;
NETDEV_ASSIGN;
__entry->rssi_low = low;
__entry->rssi_high = high;
),
TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT
", range: %d - %d ",
WIPHY_PR_ARG, NETDEV_PR_ARG,
__entry->rssi_low, __entry->rssi_high)
);
TRACE_EVENT(rdev_set_cqm_txe_config, TRACE_EVENT(rdev_set_cqm_txe_config,
TP_PROTO(struct wiphy *wiphy, struct net_device *netdev, u32 rate, TP_PROTO(struct wiphy *wiphy, struct net_device *netdev, u32 rate,
u32 pkts, u32 intvl), u32 pkts, u32 intvl),
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment