Merge "msm: pil: Add memory map tracking logic"
diff --git a/arch/arm/mach-msm/peripheral-loader.c b/arch/arm/mach-msm/peripheral-loader.c
index 2cbe32c..76592f2 100644
--- a/arch/arm/mach-msm/peripheral-loader.c
+++ b/arch/arm/mach-msm/peripheral-loader.c
@@ -24,6 +24,9 @@
 #include <linux/workqueue.h>
 #include <linux/jiffies.h>
 #include <linux/wakelock.h>
+#include <linux/err.h>
+#include <linux/list.h>
+#include <linux/list_sort.h>
 
 #include <asm/uaccess.h>
 #include <asm/setup.h>
@@ -45,11 +48,41 @@
 module_param(proxy_timeout_ms, int, S_IRUGO | S_IWUSR);
 
 /**
+ * struct pil_mdt - Representation of <name>.mdt file in memory
+ * @hdr: ELF32 header
+ * @phdr: ELF32 program headers
+ */
+struct pil_mdt {
+	struct elf32_hdr hdr;
+	struct elf32_phdr phdr[];
+};
+
+/**
+ * struct pil_seg - memory map representing one segment
+ * @next: points to next seg mentor NULL if last segment
+ * @paddr: start address of segment
+ * @sz: size of segment
+ * @filesz: size of segment on disk
+ * @num: segment number
+ *
+ * Loosely based on an elf program header. Contains all necessary information
+ * to load and initialize a segment of the image in memory.
+ */
+struct pil_seg {
+	phys_addr_t paddr;
+	unsigned long sz;
+	unsigned long filesz;
+	int num;
+	struct list_head list;
+};
+
+/**
  * struct pil_priv - Private state for a pil_desc
  * @proxy: work item used to run the proxy unvoting routine
  * @wlock: wakelock to prevent suspend during pil_boot
  * @wname: name of @wlock
  * @desc: pointer to pil_desc this is private data for
+ * @seg: list of segments sorted by physical address
  *
  * This struct contains data for a pil_desc that should not be exposed outside
  * of this file. This structure points to the descriptor and the descriptor
@@ -61,6 +94,7 @@
 	struct wake_lock wlock;
 	char wname[32];
 	struct pil_desc *desc;
+	struct list_head segs;
 };
 
 static void pil_proxy_work(struct work_struct *work)
@@ -102,24 +136,90 @@
 	}
 }
 
-#define IOMAP_SIZE SZ_4M
-
-static int load_segment(const struct elf32_phdr *phdr, unsigned num,
-		struct pil_desc *desc)
+static struct pil_seg *pil_init_seg(const struct pil_desc *desc,
+				  const struct elf32_phdr *phdr, int num)
 {
-	int ret = 0, count, paddr;
-	char fw_name[30];
-	const struct firmware *fw = NULL;
-	const u8 *data;
+	struct pil_seg *seg;
 
 	if (memblock_overlaps_memory(phdr->p_paddr, phdr->p_memsz)) {
 		pil_err(desc, "kernel memory would be overwritten [%#08lx, %#08lx)\n",
 			(unsigned long)phdr->p_paddr,
 			(unsigned long)(phdr->p_paddr + phdr->p_memsz));
-		return -EPERM;
+		return ERR_PTR(-EPERM);
 	}
 
-	if (phdr->p_filesz) {
+	seg = kmalloc(sizeof(*seg), GFP_KERNEL);
+	if (!seg)
+		return ERR_PTR(-ENOMEM);
+	seg->num = num;
+	seg->paddr = phdr->p_paddr;
+	seg->filesz = phdr->p_filesz;
+	seg->sz = phdr->p_memsz;
+	INIT_LIST_HEAD(&seg->list);
+
+	return seg;
+}
+
+#define segment_is_hash(flag) (((flag) & (0x7 << 24)) == (0x2 << 24))
+
+static int segment_is_loadable(const struct elf32_phdr *p)
+{
+	return (p->p_type == PT_LOAD) && !segment_is_hash(p->p_flags);
+}
+
+static int pil_cmp_seg(void *priv, struct list_head *a, struct list_head *b)
+{
+	struct pil_seg *seg_a = list_entry(a, struct pil_seg, list);
+	struct pil_seg *seg_b = list_entry(b, struct pil_seg, list);
+
+	return seg_a->paddr - seg_b->paddr;
+}
+
+static int pil_init_mmap(struct pil_desc *desc, const struct pil_mdt *mdt)
+{
+	const struct elf32_phdr *phdr;
+	struct pil_seg *seg;
+	struct pil_priv *priv = desc->priv;
+	int i;
+
+	for (i = 0; i < mdt->hdr.e_phnum; i++) {
+		phdr = &mdt->phdr[i];
+		if (!segment_is_loadable(phdr))
+			continue;
+
+		seg = pil_init_seg(desc, phdr, i);
+		if (IS_ERR(seg))
+			return PTR_ERR(seg);
+
+		list_add_tail(&seg->list, &priv->segs);
+	}
+	list_sort(NULL, &priv->segs, pil_cmp_seg);
+
+	return 0;
+}
+
+static void pil_release_mmap(struct pil_desc *desc)
+{
+	struct pil_priv *priv = desc->priv;
+	struct pil_seg *p, *tmp;
+
+	list_for_each_entry_safe(p, tmp, &priv->segs, list) {
+		list_del(&p->list);
+		kfree(p);
+	}
+}
+
+#define IOMAP_SIZE SZ_4M
+
+static int pil_load_seg(struct pil_desc *desc, struct pil_seg *seg)
+{
+	int ret = 0, count, paddr;
+	char fw_name[30];
+	const struct firmware *fw = NULL;
+	const u8 *data;
+	int num = seg->num;
+
+	if (seg->filesz) {
 		snprintf(fw_name, ARRAY_SIZE(fw_name), "%s.b%02d",
 				desc->name, num);
 		ret = request_firmware(&fw, fw_name, desc->dev);
@@ -128,17 +228,17 @@
 			return ret;
 		}
 
-		if (fw->size != phdr->p_filesz) {
-			pil_err(desc, "Blob size %u doesn't match %u\n",
-					fw->size, phdr->p_filesz);
+		if (fw->size != seg->filesz) {
+			pil_err(desc, "Blob size %u doesn't match %lu\n",
+					fw->size, seg->filesz);
 			ret = -EPERM;
 			goto release_fw;
 		}
 	}
 
 	/* Load the segment into memory */
-	count = phdr->p_filesz;
-	paddr = phdr->p_paddr;
+	count = seg->filesz;
+	paddr = seg->paddr;
 	data = fw ? fw->data : NULL;
 	while (count > 0) {
 		int size;
@@ -160,7 +260,7 @@
 	}
 
 	/* Zero out trailing memory */
-	count = phdr->p_memsz - phdr->p_filesz;
+	count = seg->sz - seg->filesz;
 	while (count > 0) {
 		int size;
 		u8 __iomem *buf;
@@ -180,8 +280,7 @@
 	}
 
 	if (desc->ops->verify_blob) {
-		ret = desc->ops->verify_blob(desc, phdr->p_paddr,
-					  phdr->p_memsz);
+		ret = desc->ops->verify_blob(desc, seg->paddr, seg->sz);
 		if (ret)
 			pil_err(desc, "Blob%u failed verification\n", num);
 	}
@@ -191,13 +290,6 @@
 	return ret;
 }
 
-#define segment_is_hash(flag) (((flag) & (0x7 << 24)) == (0x2 << 24))
-
-static int segment_is_loadable(const struct elf32_phdr *p)
-{
-	return (p->p_type == PT_LOAD) && !segment_is_hash(p->p_flags);
-}
-
 /* Synchronize request_firmware() with suspend */
 static DECLARE_RWSEM(pil_pm_rwsem);
 
@@ -209,13 +301,17 @@
  */
 int pil_boot(struct pil_desc *desc)
 {
-	int i, ret;
+	int ret;
 	char fw_name[30];
-	struct elf32_hdr *ehdr;
-	const struct elf32_phdr *phdr;
+	const struct pil_mdt *mdt;
+	const struct elf32_hdr *ehdr;
+	struct pil_seg *seg;
 	const struct firmware *fw;
 	unsigned long proxy_timeout = desc->proxy_timeout;
 
+	/* Reinitialize for new image */
+	pil_release_mmap(desc);
+
 	down_read(&pil_pm_rwsem);
 	snprintf(fw_name, sizeof(fw_name), "%s.mdt", desc->name);
 	ret = request_firmware(&fw, fw_name, desc->dev);
@@ -230,7 +326,9 @@
 		goto release_fw;
 	}
 
-	ehdr = (struct elf32_hdr *)fw->data;
+	mdt = (const struct pil_mdt *)fw->data;
+	ehdr = &mdt->hdr;
+
 	if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) {
 		pil_err(desc, "Not an elf header\n");
 		ret = -EIO;
@@ -249,22 +347,20 @@
 		goto release_fw;
 	}
 
+	ret = pil_init_mmap(desc, mdt);
+	if (ret)
+		goto release_fw;
+
 	ret = desc->ops->init_image(desc, fw->data, fw->size);
 	if (ret) {
 		pil_err(desc, "Invalid firmware metadata\n");
 		goto release_fw;
 	}
 
-	phdr = (const struct elf32_phdr *)(fw->data + sizeof(struct elf32_hdr));
-	for (i = 0; i < ehdr->e_phnum; i++, phdr++) {
-		if (!segment_is_loadable(phdr))
-			continue;
-
-		ret = load_segment(phdr, i, desc);
-		if (ret) {
-			pil_err(desc, "Failed to load segment %d\n", i);
+	list_for_each_entry(seg, &desc->priv->segs, list) {
+		ret = pil_load_seg(desc, seg);
+		if (ret)
 			goto release_fw;
-		}
 	}
 
 	ret = pil_proxy_vote(desc);
@@ -286,6 +382,8 @@
 	release_firmware(fw);
 out:
 	up_read(&pil_pm_rwsem);
+	if (ret)
+		pil_release_mmap(desc);
 	return ret;
 }
 EXPORT_SYMBOL(pil_boot);
@@ -334,6 +432,7 @@
 	snprintf(priv->wname, sizeof(priv->wname), "pil-%s", desc->name);
 	wake_lock_init(&priv->wlock, WAKE_LOCK_SUSPEND, priv->wname);
 	INIT_DELAYED_WORK(&priv->proxy, pil_proxy_work);
+	INIT_LIST_HEAD(&priv->segs);
 
 	return 0;
 }