Commit e646a025 authored by Johannes Berg's avatar Johannes Berg

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: default avatarJouni Malinen <j@w1.fi>
Signed-off-by: default avatarJohannes Berg <johannes.berg@intel.com>
parent 61edb116
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
* Copyright 2008-2011 Luis R. Rodriguez <mcgrof@qca.qualcomm.com> * Copyright 2008-2011 Luis R. Rodriguez <mcgrof@qca.qualcomm.com>
* Copyright 2013-2014 Intel Mobile Communications GmbH * Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright 2017 Intel Deutschland 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 * Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
...@@ -131,7 +131,8 @@ static spinlock_t reg_indoor_lock; ...@@ -131,7 +131,8 @@ static spinlock_t reg_indoor_lock;
/* Used to track the userspace process controlling the indoor setting */ /* Used to track the userspace process controlling the indoor setting */
static u32 reg_is_indoor_portid; 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) static const struct ieee80211_regdomain *get_cfg80211_regdom(void)
{ {
...@@ -263,6 +264,7 @@ static const struct ieee80211_regdomain *cfg80211_world_regdom = ...@@ -263,6 +264,7 @@ static const struct ieee80211_regdomain *cfg80211_world_regdom =
static char *ieee80211_regdom = "00"; static char *ieee80211_regdom = "00";
static char user_alpha2[2]; static char user_alpha2[2];
static const struct ieee80211_regdomain *cfg80211_user_regdom;
module_param(ieee80211_regdom, charp, 0444); module_param(ieee80211_regdom, charp, 0444);
MODULE_PARM_DESC(ieee80211_regdom, "IEEE 802.11 regulatory domain code"); MODULE_PARM_DESC(ieee80211_regdom, "IEEE 802.11 regulatory domain code");
...@@ -445,6 +447,15 @@ reg_copy_regd(const struct ieee80211_regdomain *src_regd) ...@@ -445,6 +447,15 @@ reg_copy_regd(const struct ieee80211_regdomain *src_regd)
return 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 reg_regdb_apply_request {
struct list_head list; struct list_head list;
const struct ieee80211_regdomain *regdom; const struct ieee80211_regdomain *regdom;
...@@ -510,7 +521,7 @@ static void crda_timeout_work(struct work_struct *work) ...@@ -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"); pr_debug("Timeout while waiting for CRDA to reply, restoring regulatory settings\n");
rtnl_lock(); rtnl_lock();
reg_crda_timeouts++; reg_crda_timeouts++;
restore_regulatory_settings(true); restore_regulatory_settings(true, false);
rtnl_unlock(); rtnl_unlock();
} }
...@@ -1044,7 +1055,7 @@ static void regdb_fw_cb(const struct firmware *fw, void *context) ...@@ -1044,7 +1055,7 @@ static void regdb_fw_cb(const struct firmware *fw, void *context)
} }
if (restore) if (restore)
restore_regulatory_settings(true); restore_regulatory_settings(true, false);
rtnl_unlock(); rtnl_unlock();
...@@ -3117,7 +3128,7 @@ static void restore_custom_reg_settings(struct wiphy *wiphy) ...@@ -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 * keep their own regulatory domain on wiphy->regd so that does does
* not need to be remembered. * 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 alpha2[2];
char world_alpha2[2]; char world_alpha2[2];
...@@ -3176,6 +3187,31 @@ static void restore_regulatory_settings(bool reset_user) ...@@ -3176,6 +3187,31 @@ static void restore_regulatory_settings(bool reset_user)
restore_custom_reg_settings(&rdev->wiphy); restore_custom_reg_settings(&rdev->wiphy);
} }
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();
if (is_an_alpha2(alpha2) &&
!regulatory_hint_user(alpha2, NL80211_USER_REG_HINT_USER)) {
struct regulatory_request *ureq;
spin_lock(&reg_requests_lock);
ureq = list_last_entry(&reg_requests_list,
struct regulatory_request,
list);
list_del(&ureq->list);
spin_unlock(&reg_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); regulatory_hint_core(world_alpha2);
/* /*
...@@ -3185,6 +3221,7 @@ static void restore_regulatory_settings(bool reset_user) ...@@ -3185,6 +3221,7 @@ static void restore_regulatory_settings(bool reset_user)
*/ */
if (is_an_alpha2(alpha2)) if (is_an_alpha2(alpha2))
regulatory_hint_user(alpha2, NL80211_USER_REG_HINT_USER); regulatory_hint_user(alpha2, NL80211_USER_REG_HINT_USER);
}
spin_lock(&reg_requests_lock); spin_lock(&reg_requests_lock);
list_splice_tail_init(&tmp_reg_req_list, &reg_requests_list); list_splice_tail_init(&tmp_reg_req_list, &reg_requests_list);
...@@ -3244,7 +3281,7 @@ void regulatory_hint_disconnect(void) ...@@ -3244,7 +3281,7 @@ void regulatory_hint_disconnect(void)
} }
pr_debug("All devices are disconnected, going to restore regulatory settings\n"); 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) static bool freq_is_chan_12_13_14(u32 freq)
...@@ -3561,6 +3598,9 @@ int set_regdom(const struct ieee80211_regdomain *rd, ...@@ -3561,6 +3598,9 @@ int set_regdom(const struct ieee80211_regdomain *rd,
bool user_reset = false; bool user_reset = false;
int r; int r;
if (IS_ERR_OR_NULL(rd))
return -ENODATA;
if (!reg_is_valid_request(rd->alpha2)) { if (!reg_is_valid_request(rd->alpha2)) {
kfree(rd); kfree(rd);
return -EINVAL; return -EINVAL;
...@@ -3577,6 +3617,7 @@ int set_regdom(const struct ieee80211_regdomain *rd, ...@@ -3577,6 +3617,7 @@ int set_regdom(const struct ieee80211_regdomain *rd,
r = reg_set_rd_core(rd); r = reg_set_rd_core(rd);
break; break;
case NL80211_REGDOM_SET_BY_USER: case NL80211_REGDOM_SET_BY_USER:
cfg80211_save_user_regdom(rd);
r = reg_set_rd_user(rd, lr); r = reg_set_rd_user(rd, lr);
user_reset = true; user_reset = true;
break; break;
...@@ -3599,7 +3640,7 @@ int set_regdom(const struct ieee80211_regdomain *rd, ...@@ -3599,7 +3640,7 @@ int set_regdom(const struct ieee80211_regdomain *rd,
break; break;
default: default:
/* Back to world regulatory in case of errors */ /* Back to world regulatory in case of errors */
restore_regulatory_settings(user_reset); restore_regulatory_settings(user_reset, false);
} }
kfree(rd); kfree(rd);
...@@ -3935,6 +3976,8 @@ void regulatory_exit(void) ...@@ -3935,6 +3976,8 @@ void regulatory_exit(void)
if (!IS_ERR_OR_NULL(regdb)) if (!IS_ERR_OR_NULL(regdb))
kfree(regdb); kfree(regdb);
if (!IS_ERR_OR_NULL(cfg80211_user_regdom))
kfree(cfg80211_user_regdom);
free_regdb_keyring(); free_regdb_keyring();
} }
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
/* /*
* Copyright 2008-2011 Luis R. Rodriguez <mcgrof@qca.qualcomm.com> * Copyright 2008-2011 Luis R. Rodriguez <mcgrof@qca.qualcomm.com>
* Copyright (C) 2019 Intel Corporation
* *
* Permission to use, copy, modify, and/or distribute this software for any * Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
...@@ -22,6 +23,7 @@ ...@@ -22,6 +23,7 @@
enum ieee80211_regd_source { enum ieee80211_regd_source {
REGD_SOURCE_INTERNAL_DB, REGD_SOURCE_INTERNAL_DB,
REGD_SOURCE_CRDA, REGD_SOURCE_CRDA,
REGD_SOURCE_CACHED,
}; };
extern const struct ieee80211_regdomain __rcu *cfg80211_regdomain; extern const struct ieee80211_regdomain __rcu *cfg80211_regdomain;
......
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