cfg80211 schedules an asynchronous request_firmware() work item for regulatory.db via request_firmware_work_func(). When the direct firmware load fails, _request_firmware() falls back to the sysfs fallback path via firmware_fallback_sysfs(), which blocks indefinitely in wait_for_completion_killable_timeout() waiting for userspace to supply the firmware through the sysfs interface. While this work item is pending, any caller of __usermodehelper_disable() will deadlock attempting to acquire the usermodehelper rwsem for writing, since the sysfs firmware fallback path holds the rwsem for reading and is blocked waiting for userspace response that can never arrive while usermode helpers are being disabled. Observed call traces where system suspend blocked due to regulatory.db async firmware request: kworker/6:3 (pid 194) holding usermodehelper rwsem read lock, blocked waiting for userspace firmware response: wait_for_completion_killable_timeout+0x48 firmware_fallback_sysfs+0x270 _request_firmware+0x790 request_firmware_work_func+0x44 process_one_work[jt]+0x59c worker_thread+0x260 kthread+0x150 ret_from_fork+0x10 Caller blocked in __usermodehelper_disable() during system suspend: rwsem_down_write_slowpath+0x768 down_write+0x98 __usermodehelper_disable+0x3c freeze_processes+0x18 pm_suspend+0x320 state_store+0x104 kernfs_fop_write_iter[jt]+0x168 vfs_write+0x270 ksys_write+0x78 Any service or kernel subsystem invoking __usermodehelper_disable() is susceptible to this hang as long as the regulatory.db sysfs fallback request remains outstanding. Fix this by replacing the unconditional uevent-based load with a two-step approach. First, attempt a synchronous load via request_firmware_direct() at init time. This is fast and non-blocking, if the file is present in standard paths it is loaded immediately with no delay. If not found, the load is deferred to wiphy_regulatory_register() and triggered via firmware_request_nowait_nowarn() only when the first non-self-managed wiphy registers. For self-managed drivers (REGULATORY_WIPHY_SELF_MANAGED), the hang is avoided because wiphy_regulatory_register() handles them separately and the deferred load path is never reached, so the file load is not attempted at all. For this case, regulatory information is obtained from driver/firmware during wiphy registration. For non-self-managed drivers, the file is required and is expected to be present. The deferred load via firmware_request_nowait_nowarn() is triggered only when the first such wiphy registers. This ensures the database is loaded only when it is actually needed, avoiding the sysfs fallback path on systems where the file is absent at init time. Also refactor regdb_fw_cb() into two functions: regdb_load() which validates and stores the firmware image, and regdb_fw_cb_restore() which is the async callback that calls restore_regulatory_settings() to replay all pending regulatory requests (country hints from core, user, driver and country IE) that arrived while the database was not yet available. NOTE: This issue was observed on Android platforms where regulatory.db is absent. Steps to reproduce on Android platforms: 1. Power off the device completely. 2. Connect the charger; the device enters off-mode charging. 3. While in off-mode charging, short press the power key. Signed-off-by: Kavita Kavita --- net/wireless/reg.c | 132 ++++++++++++++++++++++++++------------------- 1 file changed, 78 insertions(+), 54 deletions(-) diff --git a/net/wireless/reg.c b/net/wireless/reg.c index 1e8214d6b6d8..d7b864d32ba1 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -988,78 +988,98 @@ static int query_regdb(const char *alpha2) return -ENODATA; } -static void regdb_fw_cb(const struct firmware *fw, void *context) +MODULE_FIRMWARE("regulatory.db"); + +/* Validate and store firmware image as regdb. Used by all load paths. */ +static int regdb_load(const struct firmware *fw) { - int set_error = 0; - bool restore = true; void *db; - if (!fw) { - pr_info("failed to load regulatory.db\n"); - set_error = -ENODATA; - } else if (!valid_regdb(fw->data, fw->size)) { + ASSERT_RTNL(); + + if (!valid_regdb(fw->data, fw->size)) { pr_info("loaded regulatory.db is malformed or signature is missing/invalid\n"); - set_error = -EINVAL; + return -EINVAL; } + db = kmemdup(fw->data, fw->size, GFP_KERNEL); + if (!db) + return -ENOMEM; + + regdb = db; + return 0; +} + +static void regdb_fw_cb_restore(const struct firmware *fw, void *context) +{ + int err; + rtnl_lock(); - if (regdb && !IS_ERR(regdb)) { - /* negative case - a bug - * positive case - can happen due to race in case of multiple cb's in - * queue, due to usage of asynchronous callback - * - * Either case, just restore and free new db. - */ - } else if (set_error) { - regdb = ERR_PTR(set_error); - } else if (fw) { - db = kmemdup(fw->data, fw->size, GFP_KERNEL); - if (db) { - regdb = db; - restore = context && query_regdb(context); - } else { - restore = true; - } - } - if (restore) + /* Skip if a concurrent wiphy registration already loaded the db. */ + if (regdb && !IS_ERR(regdb)) + goto out_unlock; + + /* + * Replay all pending regulatory hints that arrived while the + * database was not yet available, regardless of load outcome. + */ + if (!fw) { + pr_info("failed to load regulatory.db\n"); + regdb = ERR_PTR(-ENODATA); restore_regulatory_settings(true, false); + goto out_unlock; + } - rtnl_unlock(); + err = regdb_load(fw); + if (err) + regdb = ERR_PTR(err); - kfree(context); + restore_regulatory_settings(true, false); +out_unlock: + rtnl_unlock(); release_firmware(fw); } -MODULE_FIRMWARE("regulatory.db"); - static int query_regdb_file(const char *alpha2) { + const struct firmware *fw; int err; ASSERT_RTNL(); - if (regdb) + if (regdb && !IS_ERR(regdb)) return query_regdb(alpha2); - alpha2 = kmemdup(alpha2, 2, GFP_KERNEL); - if (!alpha2) - return -ENOMEM; + /* + * Load failed or async udev load in progress. If -EINPROGRESS, + * hints are preserved and replayed once the udev load completes. + */ + if (IS_ERR(regdb) && PTR_ERR(regdb) != -EINPROGRESS) + return PTR_ERR(regdb); - err = request_firmware_nowait(THIS_MODULE, true, "regulatory.db", - ®_fdev->dev, GFP_KERNEL, - (void *)alpha2, regdb_fw_cb); + /* + * Preserve the hint if the file is not found on direct paths; + * an async udev load will be triggered on wiphy registration + * and will replay all pending hints on completion. + */ + err = request_firmware_direct(&fw, "regulatory.db", ®_fdev->dev); if (err) - kfree(alpha2); + return 0; + err = regdb_load(fw); + release_firmware(fw); + if (err) { + regdb = ERR_PTR(err); + return err; + } - return err; + return query_regdb(alpha2); } int reg_reload_regdb(void) { const struct firmware *fw; - void *db; int err; const struct ieee80211_regdomain *current_regdomain; struct regulatory_request *request; @@ -1068,21 +1088,14 @@ int reg_reload_regdb(void) if (err) return err; - if (!valid_regdb(fw->data, fw->size)) { - err = -ENODATA; - goto out; - } - - db = kmemdup(fw->data, fw->size, GFP_KERNEL); - if (!db) { - err = -ENOMEM; - goto out; - } - rtnl_lock(); if (!IS_ERR_OR_NULL(regdb)) kfree(regdb); - regdb = db; + err = regdb_load(fw); + if (err) { + regdb = ERR_PTR(err); + goto out_unlock; + } /* reset regulatory domain */ current_regdomain = get_cfg80211_regdom(); @@ -1103,7 +1116,6 @@ int reg_reload_regdb(void) out_unlock: rtnl_unlock(); - out: release_firmware(fw); return err; } @@ -4085,6 +4097,8 @@ void wiphy_regulatory_register(struct wiphy *wiphy) { struct regulatory_request *lr = get_last_request(); + ASSERT_RTNL(); + /* self-managed devices ignore beacon hints and country IE */ if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) { wiphy->regulatory_flags |= REGULATORY_DISABLE_BEACON_HINTS | @@ -4097,6 +4111,16 @@ void wiphy_regulatory_register(struct wiphy *wiphy) */ if (lr->initiator == NL80211_REGDOM_SET_BY_USER) reg_call_notifier(wiphy, lr); + } else if (!regdb) { + /* + * regulatory.db not yet loaded; trigger an async udev + * request to load it when the first wiphy registers. + */ + if (!firmware_request_nowait_nowarn(THIS_MODULE, + "regulatory.db", + ®_fdev->dev, GFP_KERNEL, + NULL, regdb_fw_cb_restore)) + regdb = ERR_PTR(-EINPROGRESS); } if (!reg_dev_ignore_cell_hint(wiphy)) base-commit: 972c4dd19cb92e03d75b66c426cfade07582a1ba -- 2.34.1