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;
}