msm: pil: Synchronize request_firmware() with suspend

Calling request_firmware() during suspend/resume operations is
buggy and error prone. Therefore a check exists in
request_firmware() to see if a suspend/resume operation is in
progress. In testing we've seen that a wakeup interrupt can come
in during suspend and trigger subsystem restart to call
pil_force_boot() before userspace has fully thawed. Since this is
not allowed the request_firmware() call from pil_force_boot()
fails with -EBUSY and the processor is never rebooted.

Register for PM notifiers to avoid calling request_firmware()
when suspend/resume is in progress. Within the notifier
synchronize any request firmware calls with suspend using a
rwsem. In the case where pil is called before suspend, the
suspend notifier should block on the down_write() during
PM_SUSPEND_PREPARE. When suspend is initiated before pil any pil
operations should block on the down_read() until suspend
finishes. Doing this should fix races between suspend/resume and
request_firmware() within PIL.

Change-Id: I8dcafcac5be80ef41d00bca9b8509652ad1108cc
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
diff --git a/arch/arm/mach-msm/peripheral-loader.c b/arch/arm/mach-msm/peripheral-loader.c
index 6d1a5f0..d0e45f7 100644
--- a/arch/arm/mach-msm/peripheral-loader.c
+++ b/arch/arm/mach-msm/peripheral-loader.c
@@ -21,6 +21,8 @@
 #include <linux/memblock.h>
 #include <linux/slab.h>
 #include <linux/atomic.h>
+#include <linux/suspend.h>
+#include <linux/rwsem.h>
 
 #include <asm/uaccess.h>
 #include <asm/setup.h>
@@ -156,6 +158,9 @@
 	return (p->p_type & PT_LOAD) && !segment_is_hash(p->p_flags);
 }
 
+/* Sychronize request_firmware() with suspend */
+static DECLARE_RWSEM(pil_pm_rwsem);
+
 static int load_image(struct pil_device *pil)
 {
 	int i, ret;
@@ -164,6 +169,7 @@
 	const struct elf32_phdr *phdr;
 	const struct firmware *fw;
 
+	down_read(&pil_pm_rwsem);
 	snprintf(fw_name, sizeof(fw_name), "%s.mdt", pil->desc->name);
 	ret = request_firmware(&fw, fw_name, &pil->dev);
 	if (ret) {
@@ -225,6 +231,7 @@
 release_fw:
 	release_firmware(fw);
 out:
+	up_read(&pil_pm_rwsem);
 	return ret;
 }
 
@@ -511,11 +518,29 @@
 }
 EXPORT_SYMBOL(msm_pil_unregister);
 
+static int pil_pm_notify(struct notifier_block *b, unsigned long event, void *p)
+{
+	switch (event) {
+	case PM_SUSPEND_PREPARE:
+		down_write(&pil_pm_rwsem);
+		break;
+	case PM_POST_SUSPEND:
+		up_write(&pil_pm_rwsem);
+		break;
+	}
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block pil_pm_notifier = {
+	.notifier_call = pil_pm_notify,
+};
+
 static int __init msm_pil_init(void)
 {
 	int ret = msm_pil_debugfs_init();
 	if (ret)
 		return ret;
+	register_pm_notifier(&pil_pm_notifier);
 	return bus_register(&pil_bus_type);
 }
 subsys_initcall(msm_pil_init);
@@ -523,6 +548,7 @@
 static void __exit msm_pil_exit(void)
 {
 	bus_unregister(&pil_bus_type);
+	unregister_pm_notifier(&pil_pm_notifier);
 	msm_pil_debugfs_exit();
 }
 module_exit(msm_pil_exit);