From e646a0257b64dbef9d168e0f90daafa3fc1a83af Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 5 Feb 2019 21:08:29 +0100 Subject: cfg80211: restore regulatory without calling userspace Jouni reports that in some cases it is possible that getting disconnected (or stopping AP, after previous patches) results in further operations hitting the window within the regulatory core restoring the regdomain to the defaults. The reason for this is that we have to call out to CRDA or otherwise do some asynchronous work, and thus can't do the restore atomically. However, we've previously seen all the data we need to do the restore, so we can hang on to that data and use it later for the restore. This makes the whole thing happen within a single locked section and thus atomic. However, we can't *always* do this - there are unfortunately cases where the restore needs to re-request, because this is also used (abused?) as an error recovery process, so make the new behaviour optional and only use it when doing a regular restore as described above. Reported-by: Jouni Malinen Signed-off-by: Johannes Berg --- net/wireless/reg.c | 73 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 58 insertions(+), 15 deletions(-) (limited to 'net/wireless/reg.c') diff --git a/net/wireless/reg.c b/net/wireless/reg.c index adfa58fa6536..2f1bf91eb226 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -5,7 +5,7 @@ * Copyright 2008-2011 Luis R. Rodriguez * Copyright 2013-2014 Intel Mobile Communications GmbH * Copyright 2017 Intel Deutschland GmbH - * Copyright (C) 2018 Intel Corporation + * Copyright (C) 2018 - 2019 Intel Corporation * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -131,7 +131,8 @@ static spinlock_t reg_indoor_lock; /* Used to track the userspace process controlling the indoor setting */ static u32 reg_is_indoor_portid; -static void restore_regulatory_settings(bool reset_user); +static void restore_regulatory_settings(bool reset_user, bool cached); +static void print_regdomain(const struct ieee80211_regdomain *rd); static const struct ieee80211_regdomain *get_cfg80211_regdom(void) { @@ -263,6 +264,7 @@ static const struct ieee80211_regdomain *cfg80211_world_regdom = static char *ieee80211_regdom = "00"; static char user_alpha2[2]; +static const struct ieee80211_regdomain *cfg80211_user_regdom; module_param(ieee80211_regdom, charp, 0444); MODULE_PARM_DESC(ieee80211_regdom, "IEEE 802.11 regulatory domain code"); @@ -445,6 +447,15 @@ reg_copy_regd(const struct ieee80211_regdomain *src_regd) return regd; } +static void cfg80211_save_user_regdom(const struct ieee80211_regdomain *rd) +{ + ASSERT_RTNL(); + + if (!IS_ERR(cfg80211_user_regdom)) + kfree(cfg80211_user_regdom); + cfg80211_user_regdom = reg_copy_regd(rd); +} + struct reg_regdb_apply_request { struct list_head list; const struct ieee80211_regdomain *regdom; @@ -510,7 +521,7 @@ static void crda_timeout_work(struct work_struct *work) pr_debug("Timeout while waiting for CRDA to reply, restoring regulatory settings\n"); rtnl_lock(); reg_crda_timeouts++; - restore_regulatory_settings(true); + restore_regulatory_settings(true, false); rtnl_unlock(); } @@ -1044,7 +1055,7 @@ static void regdb_fw_cb(const struct firmware *fw, void *context) } if (restore) - restore_regulatory_settings(true); + restore_regulatory_settings(true, false); rtnl_unlock(); @@ -3117,7 +3128,7 @@ static void restore_custom_reg_settings(struct wiphy *wiphy) * keep their own regulatory domain on wiphy->regd so that does does * not need to be remembered. */ -static void restore_regulatory_settings(bool reset_user) +static void restore_regulatory_settings(bool reset_user, bool cached) { char alpha2[2]; char world_alpha2[2]; @@ -3176,15 +3187,41 @@ static void restore_regulatory_settings(bool reset_user) restore_custom_reg_settings(&rdev->wiphy); } - regulatory_hint_core(world_alpha2); + if (cached && (!is_an_alpha2(alpha2) || + !IS_ERR_OR_NULL(cfg80211_user_regdom))) { + reset_regdomains(false, cfg80211_world_regdom); + update_all_wiphy_regulatory(NL80211_REGDOM_SET_BY_CORE); + print_regdomain(get_cfg80211_regdom()); + nl80211_send_reg_change_event(&core_request_world); + reg_set_request_processed(); - /* - * This restores the ieee80211_regdom module parameter - * preference or the last user requested regulatory - * settings, user regulatory settings takes precedence. - */ - if (is_an_alpha2(alpha2)) - regulatory_hint_user(alpha2, NL80211_USER_REG_HINT_USER); + if (is_an_alpha2(alpha2) && + !regulatory_hint_user(alpha2, NL80211_USER_REG_HINT_USER)) { + struct regulatory_request *ureq; + + spin_lock(®_requests_lock); + ureq = list_last_entry(®_requests_list, + struct regulatory_request, + list); + list_del(&ureq->list); + spin_unlock(®_requests_lock); + + notify_self_managed_wiphys(ureq); + reg_update_last_request(ureq); + set_regdom(reg_copy_regd(cfg80211_user_regdom), + REGD_SOURCE_CACHED); + } + } else { + regulatory_hint_core(world_alpha2); + + /* + * This restores the ieee80211_regdom module parameter + * preference or the last user requested regulatory + * settings, user regulatory settings takes precedence. + */ + if (is_an_alpha2(alpha2)) + regulatory_hint_user(alpha2, NL80211_USER_REG_HINT_USER); + } spin_lock(®_requests_lock); list_splice_tail_init(&tmp_reg_req_list, ®_requests_list); @@ -3244,7 +3281,7 @@ void regulatory_hint_disconnect(void) } pr_debug("All devices are disconnected, going to restore regulatory settings\n"); - restore_regulatory_settings(false); + restore_regulatory_settings(false, true); } static bool freq_is_chan_12_13_14(u32 freq) @@ -3561,6 +3598,9 @@ int set_regdom(const struct ieee80211_regdomain *rd, bool user_reset = false; int r; + if (IS_ERR_OR_NULL(rd)) + return -ENODATA; + if (!reg_is_valid_request(rd->alpha2)) { kfree(rd); return -EINVAL; @@ -3577,6 +3617,7 @@ int set_regdom(const struct ieee80211_regdomain *rd, r = reg_set_rd_core(rd); break; case NL80211_REGDOM_SET_BY_USER: + cfg80211_save_user_regdom(rd); r = reg_set_rd_user(rd, lr); user_reset = true; break; @@ -3599,7 +3640,7 @@ int set_regdom(const struct ieee80211_regdomain *rd, break; default: /* Back to world regulatory in case of errors */ - restore_regulatory_settings(user_reset); + restore_regulatory_settings(user_reset, false); } kfree(rd); @@ -3935,6 +3976,8 @@ void regulatory_exit(void) if (!IS_ERR_OR_NULL(regdb)) kfree(regdb); + if (!IS_ERR_OR_NULL(cfg80211_user_regdom)) + kfree(cfg80211_user_regdom); free_regdb_keyring(); } -- cgit v1.2.3