|
@@ -260,6 +260,38 @@ static int fw_cache_piggyback_on_request(const char *name);
|
|
|
* guarding for corner cases a global lock should be OK */
|
|
|
static DEFINE_MUTEX(fw_lock);
|
|
|
|
|
|
+static bool __enable_firmware = false;
|
|
|
+
|
|
|
+static void enable_firmware(void)
|
|
|
+{
|
|
|
+ mutex_lock(&fw_lock);
|
|
|
+ __enable_firmware = true;
|
|
|
+ mutex_unlock(&fw_lock);
|
|
|
+}
|
|
|
+
|
|
|
+static void disable_firmware(void)
|
|
|
+{
|
|
|
+ mutex_lock(&fw_lock);
|
|
|
+ __enable_firmware = false;
|
|
|
+ mutex_unlock(&fw_lock);
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * When disabled only the built-in firmware and the firmware cache will be
|
|
|
+ * used to look for firmware.
|
|
|
+ */
|
|
|
+static bool firmware_enabled(void)
|
|
|
+{
|
|
|
+ bool enabled = false;
|
|
|
+
|
|
|
+ mutex_lock(&fw_lock);
|
|
|
+ if (__enable_firmware)
|
|
|
+ enabled = true;
|
|
|
+ mutex_unlock(&fw_lock);
|
|
|
+
|
|
|
+ return enabled;
|
|
|
+}
|
|
|
+
|
|
|
static struct firmware_cache fw_cache;
|
|
|
|
|
|
static struct firmware_buf *__allocate_fw_buf(const char *fw_name,
|
|
@@ -1163,6 +1195,12 @@ _request_firmware(const struct firmware **firmware_p, const char *name,
|
|
|
if (ret <= 0) /* error or already assigned */
|
|
|
goto out;
|
|
|
|
|
|
+ if (!firmware_enabled()) {
|
|
|
+ WARN(1, "firmware request while host is not available\n");
|
|
|
+ ret = -EHOSTDOWN;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
ret = 0;
|
|
|
timeout = firmware_loading_timeout();
|
|
|
if (opt_flags & FW_OPT_NOWAIT) {
|
|
@@ -1695,6 +1733,62 @@ static void device_uncache_fw_images_delay(unsigned long delay)
|
|
|
msecs_to_jiffies(delay));
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * fw_pm_notify - notifier for suspend/resume
|
|
|
+ * @notify_block: unused
|
|
|
+ * @mode: mode we are switching to
|
|
|
+ * @unused: unused
|
|
|
+ *
|
|
|
+ * Used to modify the firmware_class state as we move in between states.
|
|
|
+ * The firmware_class implements a firmware cache to enable device driver
|
|
|
+ * to fetch firmware upon resume before the root filesystem is ready. We
|
|
|
+ * disable API calls which do not use the built-in firmware or the firmware
|
|
|
+ * cache when we know these calls will not work.
|
|
|
+ *
|
|
|
+ * The inner logic behind all this is a bit complex so it is worth summarizing
|
|
|
+ * the kernel's own suspend/resume process with context and focus on how this
|
|
|
+ * can impact the firmware API.
|
|
|
+ *
|
|
|
+ * First a review on how we go to suspend::
|
|
|
+ *
|
|
|
+ * pm_suspend() --> enter_state() -->
|
|
|
+ * sys_sync()
|
|
|
+ * suspend_prepare() -->
|
|
|
+ * __pm_notifier_call_chain(PM_SUSPEND_PREPARE, ...);
|
|
|
+ * suspend_freeze_processes() -->
|
|
|
+ * freeze_processes() -->
|
|
|
+ * __usermodehelper_set_disable_depth(UMH_DISABLED);
|
|
|
+ * freeze all tasks ...
|
|
|
+ * freeze_kernel_threads()
|
|
|
+ * suspend_devices_and_enter() -->
|
|
|
+ * dpm_suspend_start() -->
|
|
|
+ * dpm_prepare()
|
|
|
+ * dpm_suspend()
|
|
|
+ * suspend_enter() -->
|
|
|
+ * platform_suspend_prepare()
|
|
|
+ * dpm_suspend_late()
|
|
|
+ * freeze_enter()
|
|
|
+ * syscore_suspend()
|
|
|
+ *
|
|
|
+ * When we resume we bail out of a loop from suspend_devices_and_enter() and
|
|
|
+ * unwind back out to the caller enter_state() where we were before as follows::
|
|
|
+ *
|
|
|
+ * enter_state() -->
|
|
|
+ * suspend_devices_and_enter() --> (bail from loop)
|
|
|
+ * dpm_resume_end() -->
|
|
|
+ * dpm_resume()
|
|
|
+ * dpm_complete()
|
|
|
+ * suspend_finish() -->
|
|
|
+ * suspend_thaw_processes() -->
|
|
|
+ * thaw_processes() -->
|
|
|
+ * __usermodehelper_set_disable_depth(UMH_FREEZING);
|
|
|
+ * thaw_workqueues();
|
|
|
+ * thaw all processes ...
|
|
|
+ * usermodehelper_enable();
|
|
|
+ * pm_notifier_call_chain(PM_POST_SUSPEND);
|
|
|
+ *
|
|
|
+ * fw_pm_notify() works through pm_notifier_call_chain().
|
|
|
+ */
|
|
|
static int fw_pm_notify(struct notifier_block *notify_block,
|
|
|
unsigned long mode, void *unused)
|
|
|
{
|
|
@@ -1708,6 +1802,7 @@ static int fw_pm_notify(struct notifier_block *notify_block,
|
|
|
*/
|
|
|
kill_pending_fw_fallback_reqs(true);
|
|
|
device_cache_fw_images();
|
|
|
+ disable_firmware();
|
|
|
break;
|
|
|
|
|
|
case PM_POST_SUSPEND:
|
|
@@ -1720,6 +1815,7 @@ static int fw_pm_notify(struct notifier_block *notify_block,
|
|
|
mutex_lock(&fw_lock);
|
|
|
fw_cache.state = FW_LOADER_NO_CACHE;
|
|
|
mutex_unlock(&fw_lock);
|
|
|
+ enable_firmware();
|
|
|
|
|
|
device_uncache_fw_images_delay(10 * MSEC_PER_SEC);
|
|
|
break;
|
|
@@ -1768,6 +1864,7 @@ static void __init fw_cache_init(void)
|
|
|
static int fw_shutdown_notify(struct notifier_block *unused1,
|
|
|
unsigned long unused2, void *unused3)
|
|
|
{
|
|
|
+ disable_firmware();
|
|
|
/*
|
|
|
* Kill all pending fallback requests to avoid both stalling shutdown,
|
|
|
* and avoid a deadlock with the usermode_lock.
|
|
@@ -1783,6 +1880,7 @@ static struct notifier_block fw_shutdown_nb = {
|
|
|
|
|
|
static int __init firmware_class_init(void)
|
|
|
{
|
|
|
+ enable_firmware();
|
|
|
fw_cache_init();
|
|
|
register_reboot_notifier(&fw_shutdown_nb);
|
|
|
#ifdef CONFIG_FW_LOADER_USER_HELPER
|
|
@@ -1794,6 +1892,7 @@ static int __init firmware_class_init(void)
|
|
|
|
|
|
static void __exit firmware_class_exit(void)
|
|
|
{
|
|
|
+ disable_firmware();
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
|
unregister_syscore_ops(&fw_syscore_ops);
|
|
|
unregister_pm_notifier(&fw_cache.pm_notify);
|