diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 725dc8053727..a3fb7dd743c8 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -8236,20 +8236,20 @@ bool regulatory_pre_cac_allowed(struct wiphy *wiphy); */ /** - * reg_query_regdb_wmm - Query internal regulatory db for wmm rule - * Regulatory self-managed driver can use it to proactively - * + * reg_query_regdb_wmm - Query internal regulatory db for WMM rules * @alpha2: the ISO/IEC 3166 alpha2 wmm rule to be queried. * @freq: the frequency (in MHz) to be queried. - * @rule: pointer to store the wmm rule from the regulatory db. + * @rule: pointer to store the WMM rule from the regulatory db. * - * Self-managed wireless drivers can use this function to query + * Self-managed wireless drivers can use this function to query * the internal regulatory database to check whether the given - * ISO/IEC 3166 alpha2 country and freq have wmm rule limitations. + * ISO/IEC 3166 alpha2 country and freq have WMM rule limitations. * * Drivers should check the return value, its possible you can get * an -ENODATA. * + * This uses RCU internally so can be called in just about any context. + * * Return: 0 on success. -ENODATA. */ int reg_query_regdb_wmm(char *alpha2, int freq, diff --git a/net/wireless/reg.c b/net/wireless/reg.c index b7ef479a74f0..27ba860f0391 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -590,7 +590,7 @@ static inline int call_crda(const char *alpha2) #endif /* CONFIG_CFG80211_CRDA_SUPPORT */ /* code to directly load a firmware database through request_firmware */ -static const struct fwdb_header *regdb; +static const struct fwdb_header __rcu *global_regdb; struct fwdb_country { u8 alpha2[2]; @@ -892,16 +892,19 @@ static int __regdb_query_wmm(const struct fwdb_header *db, int reg_query_regdb_wmm(char *alpha2, int freq, struct ieee80211_reg_rule *rule) { - const struct fwdb_header *hdr = regdb; const struct fwdb_country *country; + const struct fwdb_header *regdb; + guard(rcu)(); + + regdb = rcu_dereference(global_regdb); if (!regdb) return -ENODATA; if (IS_ERR(regdb)) return PTR_ERR(regdb); - country = &hdr->country[0]; + country = ®db->country[0]; while (country->coll_ptr) { if (alpha2_equal(alpha2, country->alpha2)) return __regdb_query_wmm(regdb, country, freq, rule); @@ -970,15 +973,13 @@ static int regdb_query_country(const struct fwdb_header *db, static int query_regdb(const char *alpha2) { - const struct fwdb_header *hdr = regdb; + const struct fwdb_header *regdb = rtnl_dereference(global_regdb); const struct fwdb_country *country; - ASSERT_RTNL(); - if (IS_ERR(regdb)) return PTR_ERR(regdb); - country = &hdr->country[0]; + country = ®db->country[0]; while (country->coll_ptr) { if (alpha2_equal(alpha2, country->alpha2)) return regdb_query_country(regdb, country); @@ -990,6 +991,7 @@ static int query_regdb(const char *alpha2) static void regdb_fw_cb(const struct firmware *fw, void *context) { + const struct fwdb_header *regdb; int set_error = 0; bool restore = true; void *db; @@ -1003,6 +1005,7 @@ static void regdb_fw_cb(const struct firmware *fw, void *context) } rtnl_lock(); + regdb = rtnl_dereference(global_regdb); if (regdb && !IS_ERR(regdb)) { /* negative case - a bug * positive case - can happen due to race in case of multiple cb's in @@ -1011,11 +1014,11 @@ static void regdb_fw_cb(const struct firmware *fw, void *context) * Either case, just restore and free new db. */ } else if (set_error) { - regdb = ERR_PTR(set_error); + rcu_assign_pointer(global_regdb, ERR_PTR(set_error)); } else if (fw) { db = kmemdup(fw->data, fw->size, GFP_KERNEL); if (db) { - regdb = db; + rcu_assign_pointer(global_regdb, db); restore = context && query_regdb(context); } else { restore = true; @@ -1040,7 +1043,7 @@ static int query_regdb_file(const char *alpha2) ASSERT_RTNL(); - if (regdb) + if (rcu_access_pointer(global_regdb)) return query_regdb(alpha2); alpha2 = kmemdup(alpha2, 2, GFP_KERNEL); @@ -1058,8 +1061,8 @@ static int query_regdb_file(const char *alpha2) int reg_reload_regdb(void) { + const struct fwdb_header *db, *old; const struct firmware *fw; - void *db; int err; const struct ieee80211_regdomain *current_regdomain; struct regulatory_request *request; @@ -1080,9 +1083,7 @@ int reg_reload_regdb(void) } rtnl_lock(); - if (!IS_ERR_OR_NULL(regdb)) - kfree(regdb); - regdb = db; + old = rcu_replace_pointer_rtnl(global_regdb, db); /* reset regulatory domain */ current_regdomain = get_cfg80211_regdom(); @@ -1103,6 +1104,11 @@ int reg_reload_regdb(void) out_unlock: rtnl_unlock(); + + if (!IS_ERR_OR_NULL(old)) { + synchronize_rcu(); + kfree(old); + } out: release_firmware(fw); return err; @@ -4390,6 +4396,7 @@ void regulatory_exit(void) { struct regulatory_request *reg_request, *tmp; struct reg_beacon *reg_beacon, *btmp; + const struct fwdb_header *db; cancel_work_sync(®_work); cancel_crda_timeout_sync(); @@ -4419,8 +4426,10 @@ void regulatory_exit(void) kfree(reg_request); } - if (!IS_ERR_OR_NULL(regdb)) - kfree(regdb); + /* RCU is irrelevant on the way out */ + db = rcu_dereference_protected(global_regdb, true); + if (!IS_ERR_OR_NULL(db)) + kfree(db); if (!IS_ERR_OR_NULL(cfg80211_user_regdom)) kfree(cfg80211_user_regdom);