msm: pil: Consolidate proxy voting into core

Almost each pil driver needs to proxy vote for certain resources
that the secure code cannot handle. Instead of repeating the same
boiler plate code in each pil driver move the proxy voting and
callback scheduling to the core. A driver is expected to provide
a proxy_vote and proxy_unvote function if proxy voting is
required.

Call the proxy_vote before booting the processor and remove the
proxy vote after the alloted timeout. If the driver doesn't
provide a timeout remove the proxy immediately after the
processor is booted. Also flush any pending works during shutdown
so that proxy votes don't linger.

Change-Id: I13322b5bfb040fd76cdfc0228771d076addf6559
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 6f7f771..fa9159e 100644
--- a/arch/arm/mach-msm/peripheral-loader.c
+++ b/arch/arm/mach-msm/peripheral-loader.c
@@ -24,6 +24,9 @@
 #include <linux/suspend.h>
 #include <linux/rwsem.h>
 #include <linux/sysfs.h>
+#include <linux/workqueue.h>
+#include <linux/jiffies.h>
+#include <linux/wakelock.h>
 
 #include <asm/uaccess.h>
 #include <asm/setup.h>
@@ -51,6 +54,9 @@
 #ifdef CONFIG_DEBUG_FS
 	struct dentry *dentry;
 #endif
+	struct delayed_work proxy;
+	struct wake_lock wlock;
+	char wake_name[32];
 };
 
 #define to_pil_device(d) container_of(d, struct pil_device, dev)
@@ -97,6 +103,30 @@
 	return dev ? to_pil_device(dev) : NULL;
 }
 
+static void pil_proxy_work(struct work_struct *work)
+{
+	struct pil_device *pil;
+
+	pil = container_of(work, struct pil_device, proxy.work);
+	pil->desc->ops->proxy_unvote(pil->desc);
+	wake_unlock(&pil->wlock);
+}
+
+static int pil_proxy_vote(struct pil_device *pil)
+{
+	if (pil->desc->ops->proxy_vote) {
+		wake_lock(&pil->wlock);
+		return pil->desc->ops->proxy_vote(pil->desc);
+	}
+	return 0;
+}
+
+static void pil_proxy_unvote(struct pil_device *pil, unsigned long timeout)
+{
+	if (pil->desc->ops->proxy_unvote)
+		schedule_delayed_work(&pil->proxy, msecs_to_jiffies(timeout));
+}
+
 #define IOMAP_SIZE SZ_4M
 
 static int load_segment(const struct elf32_phdr *phdr, unsigned num,
@@ -202,6 +232,7 @@
 	struct elf32_hdr *ehdr;
 	const struct elf32_phdr *phdr;
 	const struct firmware *fw;
+	unsigned long proxy_timeout = pil->desc->proxy_timeout;
 
 	down_read(&pil_pm_rwsem);
 	snprintf(fw_name, sizeof(fw_name), "%s.mdt", pil->desc->name);
@@ -255,13 +286,21 @@
 		}
 	}
 
+	ret = pil_proxy_vote(pil);
+	if (ret) {
+		dev_err(&pil->dev, "Failed to proxy vote\n");
+		goto release_fw;
+	}
+
 	ret = pil->desc->ops->auth_and_reset(pil->desc);
 	if (ret) {
 		dev_err(&pil->dev, "Failed to bring out of reset\n");
-		goto release_fw;
+		proxy_timeout = 0; /* Remove proxy vote immediately on error */
+		goto err_boot;
 	}
 	dev_info(&pil->dev, "brought %s out of reset\n", pil->desc->name);
-
+err_boot:
+	pil_proxy_unvote(pil, proxy_timeout);
 release_fw:
 	release_firmware(fw);
 out:
@@ -332,6 +371,13 @@
 }
 EXPORT_SYMBOL(pil_get);
 
+static void pil_shutdown(struct pil_device *pil)
+{
+	pil->desc->ops->shutdown(pil->desc);
+	flush_delayed_work(&pil->proxy);
+	pil_set_state(pil, PIL_OFFLINE);
+}
+
 /**
  * pil_put() - Inform PIL the peripheral no longer needs to be active
  * @peripheral_handle: pointer from a previous call to pil_get()
@@ -349,10 +395,8 @@
 	mutex_lock(&pil->lock);
 	if (WARN(!pil->count, "%s: Reference count mismatch\n", __func__))
 		goto err_out;
-	if (!--pil->count) {
-		pil->desc->ops->shutdown(pil->desc);
-		pil_set_state(pil, PIL_OFFLINE);
-	}
+	if (!--pil->count)
+		pil_shutdown(pil);
 	mutex_unlock(&pil->lock);
 
 	pil_d = find_peripheral(pil->desc->depends_on);
@@ -381,8 +425,7 @@
 
 	mutex_lock(&pil->lock);
 	if (!WARN(!pil->count, "%s: Reference count mismatch\n", __func__))
-		pil->desc->ops->shutdown(pil->desc);
-	pil_set_state(pil, PIL_OFFLINE);
+		pil_shutdown(pil);
 	mutex_unlock(&pil->lock);
 
 	put_device(&pil->dev);
@@ -502,8 +545,7 @@
 
 static int __msm_pil_shutdown(struct device *dev, void *data)
 {
-	struct pil_device *pil = to_pil_device(dev);
-	pil->desc->ops->shutdown(pil->desc);
+	pil_shutdown(to_pil_device(dev));
 	return 0;
 }
 
@@ -516,6 +558,7 @@
 static void pil_device_release(struct device *dev)
 {
 	struct pil_device *pil = to_pil_device(dev);
+	wake_lock_destroy(&pil->wlock);
 	mutex_destroy(&pil->lock);
 	kfree(pil);
 }
@@ -524,8 +567,14 @@
 {
 	int err;
 	static atomic_t pil_count = ATOMIC_INIT(-1);
-	struct pil_device *pil = kzalloc(sizeof(*pil), GFP_KERNEL);
+	struct pil_device *pil;
 
+	/* Ignore users who don't make any sense */
+	if (WARN(desc->ops->proxy_unvote && !desc->ops->proxy_vote,
+				"invalid proxy voting. ignoring\n"))
+		((struct pil_reset_ops *)desc->ops)->proxy_unvote = NULL;
+
+	pil = kzalloc(sizeof(*pil), GFP_KERNEL);
 	if (!pil)
 		return ERR_PTR(-ENOMEM);
 
@@ -536,10 +585,15 @@
 	pil->dev.bus = &pil_bus_type;
 	pil->dev.release = pil_device_release;
 
+	snprintf(pil->wake_name, sizeof(pil->wake_name), "pil-%s", desc->name);
+	wake_lock_init(&pil->wlock, WAKE_LOCK_SUSPEND, pil->wake_name);
+	INIT_DELAYED_WORK(&pil->proxy, pil_proxy_work);
+
 	dev_set_name(&pil->dev, "pil%d", atomic_inc_return(&pil_count));
 	err = device_register(&pil->dev);
 	if (err) {
 		put_device(&pil->dev);
+		wake_lock_destroy(&pil->wlock);
 		mutex_destroy(&pil->lock);
 		kfree(pil);
 		return ERR_PTR(err);
@@ -563,6 +617,7 @@
 	if (get_device(&pil->dev)) {
 		mutex_lock(&pil->lock);
 		WARN_ON(pil->count);
+		flush_delayed_work_sync(&pil->proxy);
 		msm_pil_debugfs_remove(pil);
 		device_unregister(&pil->dev);
 		mutex_unlock(&pil->lock);
diff --git a/arch/arm/mach-msm/peripheral-loader.h b/arch/arm/mach-msm/peripheral-loader.h
index cc00446..e3b250b 100644
--- a/arch/arm/mach-msm/peripheral-loader.h
+++ b/arch/arm/mach-msm/peripheral-loader.h
@@ -15,19 +15,41 @@
 struct device;
 struct module;
 
+/**
+ * struct pil_desc - PIL descriptor
+ * @name: string used for pil_get()
+ * @depends_on: booted before this peripheral
+ * @dev: parent device
+ * @ops: callback functions
+ * @owner: module the descriptor belongs to
+ * @proxy_timeout: delay in ms until proxy vote is removed
+ */
 struct pil_desc {
 	const char *name;
 	const char *depends_on;
 	struct device *dev;
 	const struct pil_reset_ops *ops;
 	struct module *owner;
+	unsigned long proxy_timeout;
 };
 
+/**
+ * struct pil_reset_ops - PIL operations
+ * @init_image: prepare an image for authentication
+ * @verify_blob: authenticate a program segment, called once for each loadable
+ *		 program segment (optional)
+ * @proxy_vote: make proxy votes before auth_and_reset (optional)
+ * @auth_and_reset: boot the processor
+ * @proxy_unvote: remove any proxy votes (optional)
+ * @shutdown: shutdown the processor
+ */
 struct pil_reset_ops {
 	int (*init_image)(struct pil_desc *pil, const u8 *metadata,
 			  size_t size);
 	int (*verify_blob)(struct pil_desc *pil, u32 phy_addr, size_t size);
+	int (*proxy_vote)(struct pil_desc *pil);
 	int (*auth_and_reset)(struct pil_desc *pil);
+	void (*proxy_unvote)(struct pil_desc *pil);
 	int (*shutdown)(struct pil_desc *pil);
 };