Linux wireless drivers development
 help / color / mirror / Atom feed
* [PATCH wireless-next] wifi: cfg80211: fix regulatory.db async firmware request blocking __usermodehelper_disable()
@ 2026-06-25  9:29 Kavita Kavita
  2026-06-26 14:46 ` Carlos Llamas
  0 siblings, 1 reply; 2+ messages in thread
From: Kavita Kavita @ 2026-06-25  9:29 UTC (permalink / raw)
  To: johannes; +Cc: linux-wireless

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 <kavita.kavita@oss.qualcomm.com>
---
 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",
-				      &reg_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", &reg_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",
+						    &reg_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


^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH wireless-next] wifi: cfg80211: fix regulatory.db async firmware request blocking __usermodehelper_disable()
  2026-06-25  9:29 [PATCH wireless-next] wifi: cfg80211: fix regulatory.db async firmware request blocking __usermodehelper_disable() Kavita Kavita
@ 2026-06-26 14:46 ` Carlos Llamas
  0 siblings, 0 replies; 2+ messages in thread
From: Carlos Llamas @ 2026-06-26 14:46 UTC (permalink / raw)
  To: Kavita Kavita; +Cc: johannes, linux-wireless

On Thu, Jun 25, 2026 at 02:59:04PM +0530, Kavita Kavita wrote:
> 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 <kavita.kavita@oss.qualcomm.com>

Are you missing a `Fixes:` tag for this?


^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-06-26 14:46 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-25  9:29 [PATCH wireless-next] wifi: cfg80211: fix regulatory.db async firmware request blocking __usermodehelper_disable() Kavita Kavita
2026-06-26 14:46 ` Carlos Llamas

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox