Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

firmware: add sanity check on shutdown/suspend

The firmware API should not be used after we go to suspend
and after we reboot/halt. The suspend/resume case is a bit
complex, so this documents that so things are clearer.

We want to know about users of the API in incorrect places so
that their callers are corrected, so this also adds a warn
for those cases.

Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Luis R. Rodriguez and committed by
Greg Kroah-Hartman
81f95076 a669f04a

+110
+11
Documentation/driver-api/firmware/request_firmware.rst
··· 44 44 .. kernel-doc:: drivers/base/firmware_class.c 45 45 :functions: request_firmware_nowait 46 46 47 + Considerations for suspend and resume 48 + ===================================== 49 + 50 + During suspend and resume only the built-in firmware and the firmware cache 51 + elements of the firmware API can be used. This is managed by fw_pm_notify(). 52 + 53 + fw_pm_notify 54 + ------------ 55 + .. kernel-doc:: drivers/base/firmware_class.c 56 + :functions: fw_pm_notify 57 + 47 58 request firmware API expected driver use 48 59 ======================================== 49 60
+99
drivers/base/firmware_class.c
··· 260 260 * guarding for corner cases a global lock should be OK */ 261 261 static DEFINE_MUTEX(fw_lock); 262 262 263 + static bool __enable_firmware = false; 264 + 265 + static void enable_firmware(void) 266 + { 267 + mutex_lock(&fw_lock); 268 + __enable_firmware = true; 269 + mutex_unlock(&fw_lock); 270 + } 271 + 272 + static void disable_firmware(void) 273 + { 274 + mutex_lock(&fw_lock); 275 + __enable_firmware = false; 276 + mutex_unlock(&fw_lock); 277 + } 278 + 279 + /* 280 + * When disabled only the built-in firmware and the firmware cache will be 281 + * used to look for firmware. 282 + */ 283 + static bool firmware_enabled(void) 284 + { 285 + bool enabled = false; 286 + 287 + mutex_lock(&fw_lock); 288 + if (__enable_firmware) 289 + enabled = true; 290 + mutex_unlock(&fw_lock); 291 + 292 + return enabled; 293 + } 294 + 263 295 static struct firmware_cache fw_cache; 264 296 265 297 static struct firmware_buf *__allocate_fw_buf(const char *fw_name, ··· 1195 1163 if (ret <= 0) /* error or already assigned */ 1196 1164 goto out; 1197 1165 1166 + if (!firmware_enabled()) { 1167 + WARN(1, "firmware request while host is not available\n"); 1168 + ret = -EHOSTDOWN; 1169 + goto out; 1170 + } 1171 + 1198 1172 ret = 0; 1199 1173 timeout = firmware_loading_timeout(); 1200 1174 if (opt_flags & FW_OPT_NOWAIT) { ··· 1733 1695 msecs_to_jiffies(delay)); 1734 1696 } 1735 1697 1698 + /** 1699 + * fw_pm_notify - notifier for suspend/resume 1700 + * @notify_block: unused 1701 + * @mode: mode we are switching to 1702 + * @unused: unused 1703 + * 1704 + * Used to modify the firmware_class state as we move in between states. 1705 + * The firmware_class implements a firmware cache to enable device driver 1706 + * to fetch firmware upon resume before the root filesystem is ready. We 1707 + * disable API calls which do not use the built-in firmware or the firmware 1708 + * cache when we know these calls will not work. 1709 + * 1710 + * The inner logic behind all this is a bit complex so it is worth summarizing 1711 + * the kernel's own suspend/resume process with context and focus on how this 1712 + * can impact the firmware API. 1713 + * 1714 + * First a review on how we go to suspend:: 1715 + * 1716 + * pm_suspend() --> enter_state() --> 1717 + * sys_sync() 1718 + * suspend_prepare() --> 1719 + * __pm_notifier_call_chain(PM_SUSPEND_PREPARE, ...); 1720 + * suspend_freeze_processes() --> 1721 + * freeze_processes() --> 1722 + * __usermodehelper_set_disable_depth(UMH_DISABLED); 1723 + * freeze all tasks ... 1724 + * freeze_kernel_threads() 1725 + * suspend_devices_and_enter() --> 1726 + * dpm_suspend_start() --> 1727 + * dpm_prepare() 1728 + * dpm_suspend() 1729 + * suspend_enter() --> 1730 + * platform_suspend_prepare() 1731 + * dpm_suspend_late() 1732 + * freeze_enter() 1733 + * syscore_suspend() 1734 + * 1735 + * When we resume we bail out of a loop from suspend_devices_and_enter() and 1736 + * unwind back out to the caller enter_state() where we were before as follows:: 1737 + * 1738 + * enter_state() --> 1739 + * suspend_devices_and_enter() --> (bail from loop) 1740 + * dpm_resume_end() --> 1741 + * dpm_resume() 1742 + * dpm_complete() 1743 + * suspend_finish() --> 1744 + * suspend_thaw_processes() --> 1745 + * thaw_processes() --> 1746 + * __usermodehelper_set_disable_depth(UMH_FREEZING); 1747 + * thaw_workqueues(); 1748 + * thaw all processes ... 1749 + * usermodehelper_enable(); 1750 + * pm_notifier_call_chain(PM_POST_SUSPEND); 1751 + * 1752 + * fw_pm_notify() works through pm_notifier_call_chain(). 1753 + */ 1736 1754 static int fw_pm_notify(struct notifier_block *notify_block, 1737 1755 unsigned long mode, void *unused) 1738 1756 { ··· 1802 1708 */ 1803 1709 kill_pending_fw_fallback_reqs(true); 1804 1710 device_cache_fw_images(); 1711 + disable_firmware(); 1805 1712 break; 1806 1713 1807 1714 case PM_POST_SUSPEND: ··· 1815 1720 mutex_lock(&fw_lock); 1816 1721 fw_cache.state = FW_LOADER_NO_CACHE; 1817 1722 mutex_unlock(&fw_lock); 1723 + enable_firmware(); 1818 1724 1819 1725 device_uncache_fw_images_delay(10 * MSEC_PER_SEC); 1820 1726 break; ··· 1864 1768 static int fw_shutdown_notify(struct notifier_block *unused1, 1865 1769 unsigned long unused2, void *unused3) 1866 1770 { 1771 + disable_firmware(); 1867 1772 /* 1868 1773 * Kill all pending fallback requests to avoid both stalling shutdown, 1869 1774 * and avoid a deadlock with the usermode_lock. ··· 1880 1783 1881 1784 static int __init firmware_class_init(void) 1882 1785 { 1786 + enable_firmware(); 1883 1787 fw_cache_init(); 1884 1788 register_reboot_notifier(&fw_shutdown_nb); 1885 1789 #ifdef CONFIG_FW_LOADER_USER_HELPER ··· 1892 1794 1893 1795 static void __exit firmware_class_exit(void) 1894 1796 { 1797 + disable_firmware(); 1895 1798 #ifdef CONFIG_PM_SLEEP 1896 1799 unregister_syscore_ops(&fw_syscore_ops); 1897 1800 unregister_pm_notifier(&fw_cache.pm_notify);