cfg80211: Add AP beacon regulatory hints
When devices are world roaming they cannot beacon or do active scan
on 5 GHz or on channels 12, 13 and 14 on the 2 GHz band. Although
we have a good regulatory API some cards may _always_ world roam, this
is also true when a system does not have CRDA present. Devices doing world
roaming can still passive scan, if they find a beacon from an AP on
one of the world roaming frequencies we make the assumption we can do
the same and we also remove the passive scan requirement.
This adds support for providing beacon regulatory hints based on scans.
This works for devices that do either hardware or software scanning.
If a channel has not yet been marked as having had a beacon present
on it we queue the beacon hint processing into the workqueue.
All wireless devices will benefit from beacon regulatory hints from
any wireless device on a system including new devices connected to
the system at a later time.
Signed-off-by: Luis R. Rodriguez <lrodriguez@atheros.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
diff --git a/net/wireless/reg.c b/net/wireless/reg.c
index da2a8ac..e5e432d 100644
--- a/net/wireless/reg.c
+++ b/net/wireless/reg.c
@@ -68,9 +68,22 @@
*/
static const struct ieee80211_regdomain *country_ie_regdomain;
+/* Used to queue up regulatory hints */
static LIST_HEAD(reg_requests_list);
static spinlock_t reg_requests_lock;
+/* Used to queue up beacon hints for review */
+static LIST_HEAD(reg_pending_beacons);
+static spinlock_t reg_pending_beacons_lock;
+
+/* Used to keep track of processed beacon hints */
+static LIST_HEAD(reg_beacon_list);
+
+struct reg_beacon {
+ struct list_head list;
+ struct ieee80211_channel chan;
+};
+
/* We keep a static world regulatory domain in case of the absence of CRDA */
static const struct ieee80211_regdomain world_regdom = {
.n_reg_rules = 3,
@@ -1011,16 +1024,120 @@
wiphy_update_regulatory(&drv->wiphy, setby);
}
+static void handle_reg_beacon(struct wiphy *wiphy,
+ unsigned int chan_idx,
+ struct reg_beacon *reg_beacon)
+{
+#ifdef CONFIG_CFG80211_REG_DEBUG
+#define REG_DEBUG_BEACON_FLAG(desc) \
+ printk(KERN_DEBUG "cfg80211: Enabling " desc " on " \
+ "frequency: %d MHz (Ch %d) on %s\n", \
+ reg_beacon->chan.center_freq, \
+ ieee80211_frequency_to_channel(reg_beacon->chan.center_freq), \
+ wiphy_name(wiphy));
+#else
+#define REG_DEBUG_BEACON_FLAG(desc) do {} while (0)
+#endif
+ struct ieee80211_supported_band *sband;
+ struct ieee80211_channel *chan;
+
+ assert_cfg80211_lock();
+
+ sband = wiphy->bands[reg_beacon->chan.band];
+ chan = &sband->channels[chan_idx];
+
+ if (likely(chan->center_freq != reg_beacon->chan.center_freq))
+ return;
+
+ if (chan->flags & IEEE80211_CHAN_PASSIVE_SCAN) {
+ chan->flags &= ~IEEE80211_CHAN_PASSIVE_SCAN;
+ REG_DEBUG_BEACON_FLAG("active scanning");
+ }
+
+ if (chan->flags & IEEE80211_CHAN_NO_IBSS) {
+ chan->flags &= ~IEEE80211_CHAN_NO_IBSS;
+ REG_DEBUG_BEACON_FLAG("beaconing");
+ }
+
+ chan->beacon_found = true;
+#undef REG_DEBUG_BEACON_FLAG
+}
+
+/*
+ * Called when a scan on a wiphy finds a beacon on
+ * new channel
+ */
+static void wiphy_update_new_beacon(struct wiphy *wiphy,
+ struct reg_beacon *reg_beacon)
+{
+ unsigned int i;
+ struct ieee80211_supported_band *sband;
+
+ assert_cfg80211_lock();
+
+ if (!wiphy->bands[reg_beacon->chan.band])
+ return;
+
+ sband = wiphy->bands[reg_beacon->chan.band];
+
+ for (i = 0; i < sband->n_channels; i++)
+ handle_reg_beacon(wiphy, i, reg_beacon);
+}
+
+/*
+ * Called upon reg changes or a new wiphy is added
+ */
+static void wiphy_update_beacon_reg(struct wiphy *wiphy)
+{
+ unsigned int i;
+ struct ieee80211_supported_band *sband;
+ struct reg_beacon *reg_beacon;
+
+ assert_cfg80211_lock();
+
+ if (list_empty(®_beacon_list))
+ return;
+
+ list_for_each_entry(reg_beacon, ®_beacon_list, list) {
+ if (!wiphy->bands[reg_beacon->chan.band])
+ continue;
+ sband = wiphy->bands[reg_beacon->chan.band];
+ for (i = 0; i < sband->n_channels; i++)
+ handle_reg_beacon(wiphy, i, reg_beacon);
+ }
+}
+
+static bool reg_is_world_roaming(struct wiphy *wiphy)
+{
+ if (is_world_regdom(cfg80211_regdomain->alpha2) ||
+ (wiphy->regd && is_world_regdom(wiphy->regd->alpha2)))
+ return true;
+ if (last_request->initiator != REGDOM_SET_BY_COUNTRY_IE &&
+ wiphy->custom_regulatory)
+ return true;
+ return false;
+}
+
+/* Reap the advantages of previously found beacons */
+static void reg_process_beacons(struct wiphy *wiphy)
+{
+ if (!reg_is_world_roaming(wiphy))
+ return;
+ wiphy_update_beacon_reg(wiphy);
+}
+
void wiphy_update_regulatory(struct wiphy *wiphy, enum reg_set_by setby)
{
enum ieee80211_band band;
if (ignore_reg_update(wiphy, setby))
- return;
+ goto out;
for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
if (wiphy->bands[band])
handle_band(wiphy, band);
}
+out:
+ reg_process_beacons(wiphy);
if (wiphy->reg_notifier)
wiphy->reg_notifier(wiphy, last_request);
}
@@ -1314,6 +1431,7 @@
return r;
}
+/* Processes regulatory hints, this is all the REGDOM_SET_BY_* */
static void reg_process_pending_hints(void)
{
struct regulatory_request *reg_request;
@@ -1344,9 +1462,44 @@
spin_unlock(®_requests_lock);
}
+/* Processes beacon hints -- this has nothing to do with country IEs */
+static void reg_process_pending_beacon_hints(void)
+{
+ struct cfg80211_registered_device *drv;
+ struct reg_beacon *pending_beacon, *tmp;
+
+ mutex_lock(&cfg80211_mutex);
+
+ /* This goes through the _pending_ beacon list */
+ spin_lock_bh(®_pending_beacons_lock);
+
+ if (list_empty(®_pending_beacons)) {
+ spin_unlock_bh(®_pending_beacons_lock);
+ goto out;
+ }
+
+ list_for_each_entry_safe(pending_beacon, tmp,
+ ®_pending_beacons, list) {
+
+ list_del_init(&pending_beacon->list);
+
+ /* Applies the beacon hint to current wiphys */
+ list_for_each_entry(drv, &cfg80211_drv_list, list)
+ wiphy_update_new_beacon(&drv->wiphy, pending_beacon);
+
+ /* Remembers the beacon hint for new wiphys or reg changes */
+ list_add_tail(&pending_beacon->list, ®_beacon_list);
+ }
+
+ spin_unlock_bh(®_pending_beacons_lock);
+out:
+ mutex_unlock(&cfg80211_mutex);
+}
+
static void reg_todo(struct work_struct *work)
{
reg_process_pending_hints();
+ reg_process_pending_beacon_hints();
}
static DECLARE_WORK(reg_work, reg_todo);
@@ -1587,6 +1740,55 @@
}
EXPORT_SYMBOL(regulatory_hint_11d);
+static bool freq_is_chan_12_13_14(u16 freq)
+{
+ if (freq == ieee80211_channel_to_frequency(12) ||
+ freq == ieee80211_channel_to_frequency(13) ||
+ freq == ieee80211_channel_to_frequency(14))
+ return true;
+ return false;
+}
+
+int regulatory_hint_found_beacon(struct wiphy *wiphy,
+ struct ieee80211_channel *beacon_chan,
+ gfp_t gfp)
+{
+ struct reg_beacon *reg_beacon;
+
+ if (likely((beacon_chan->beacon_found ||
+ (beacon_chan->flags & IEEE80211_CHAN_RADAR) ||
+ (beacon_chan->band == IEEE80211_BAND_2GHZ &&
+ !freq_is_chan_12_13_14(beacon_chan->center_freq)))))
+ return 0;
+
+ reg_beacon = kzalloc(sizeof(struct reg_beacon), gfp);
+ if (!reg_beacon)
+ return -ENOMEM;
+
+#ifdef CONFIG_CFG80211_REG_DEBUG
+ printk(KERN_DEBUG "cfg80211: Found new beacon on "
+ "frequency: %d MHz (Ch %d) on %s\n",
+ beacon_chan->center_freq,
+ ieee80211_frequency_to_channel(beacon_chan->center_freq),
+ wiphy_name(wiphy));
+#endif
+ memcpy(®_beacon->chan, beacon_chan,
+ sizeof(struct ieee80211_channel));
+
+
+ /*
+ * Since we can be called from BH or and non-BH context
+ * we must use spin_lock_bh()
+ */
+ spin_lock_bh(®_pending_beacons_lock);
+ list_add_tail(®_beacon->list, ®_pending_beacons);
+ spin_unlock_bh(®_pending_beacons_lock);
+
+ schedule_work(®_work);
+
+ return 0;
+}
+
static void print_rd_rules(const struct ieee80211_regdomain *rd)
{
unsigned int i;
@@ -1908,6 +2110,7 @@
return PTR_ERR(reg_pdev);
spin_lock_init(®_requests_lock);
+ spin_lock_init(®_pending_beacons_lock);
#ifdef CONFIG_WIRELESS_OLD_REGULATORY
cfg80211_regdomain = static_regdom(ieee80211_regdom);
@@ -1951,6 +2154,7 @@
void regulatory_exit(void)
{
struct regulatory_request *reg_request, *tmp;
+ struct reg_beacon *reg_beacon, *btmp;
cancel_work_sync(®_work);
@@ -1965,6 +2169,24 @@
platform_device_unregister(reg_pdev);
+ spin_lock_bh(®_pending_beacons_lock);
+ if (!list_empty(®_pending_beacons)) {
+ list_for_each_entry_safe(reg_beacon, btmp,
+ ®_pending_beacons, list) {
+ list_del(®_beacon->list);
+ kfree(reg_beacon);
+ }
+ }
+ spin_unlock_bh(®_pending_beacons_lock);
+
+ if (!list_empty(®_beacon_list)) {
+ list_for_each_entry_safe(reg_beacon, btmp,
+ ®_beacon_list, list) {
+ list_del(®_beacon->list);
+ kfree(reg_beacon);
+ }
+ }
+
spin_lock(®_requests_lock);
if (!list_empty(®_requests_list)) {
list_for_each_entry_safe(reg_request, tmp,