]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
remoteproc: qcom-pil: Implementation of Qualcomm TZ backed PIL
authorBjorn Andersson <bjorn.andersson@sonymobile.com>
Thu, 27 Feb 2014 23:52:47 +0000 (15:52 -0800)
committerSrinivas Kandagatla <srinivas.kandagatla@linaro.org>
Mon, 11 Jan 2016 09:55:08 +0000 (09:55 +0000)
This implements the TrustZone based Peripheral Image Loader used in
Qualcomm platforms for loading and boot audio, video and wifi/bt remote
processors.

Signed-off-by: Bjorn Andersson <bjorn.andersson@sonymobile.com>
drivers/remoteproc/Kconfig
drivers/remoteproc/Makefile
drivers/remoteproc/qcom_tz_pil.c [new file with mode: 0644]

index 28c711f0ac6bd5c3d77c32018c64abad77c4bdfa..118950dc3cdcc0776d1feebafd9621819d5ef308 100644 (file)
@@ -77,4 +77,12 @@ config DA8XX_REMOTEPROC
          It's safe to say n here if you're not interested in multimedia
          offloading.
 
+config QCOM_TZ_PIL
+       tristate "Qualcomm TrustZone based Peripherial Image Loader"
+       depends on OF && ARCH_QCOM
+       select REMOTEPROC
+       help
+         Say y here to support the TrustZone based Qualcomm Peripherial Image
+         Loader.
+
 endmenu
index 81b04d1e2e5888b8ebdaca78a551045e3aa0db40..bf0b7af1e4289274dd92441c8fb47c12f01b19d0 100644 (file)
@@ -11,3 +11,4 @@ obj-$(CONFIG_OMAP_REMOTEPROC)         += omap_remoteproc.o
 obj-$(CONFIG_STE_MODEM_RPROC)          += ste_modem_rproc.o
 obj-$(CONFIG_WKUP_M3_RPROC)            += wkup_m3_rproc.o
 obj-$(CONFIG_DA8XX_REMOTEPROC)         += da8xx_remoteproc.o
+obj-$(CONFIG_QCOM_TZ_PIL)              += qcom_tz_pil.o
diff --git a/drivers/remoteproc/qcom_tz_pil.c b/drivers/remoteproc/qcom_tz_pil.c
new file mode 100644 (file)
index 0000000..e681525
--- /dev/null
@@ -0,0 +1,653 @@
+/*
+ * Qualcomm Peripheral Image Loader
+ *
+ * Copyright (C) 2014 Sony Mobile Communications AB
+ * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/firmware.h>
+#include <linux/remoteproc.h>
+#include <linux/memblock.h>
+#include <linux/of.h>
+#include <linux/elf.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/regulator/consumer.h>
+
+#include "remoteproc_internal.h"
+
+#include "../../arch/arm/mach-qcom/scm.h"
+
+extern int scm_is_call_available(u32 svc_id, u32 cmd_id);
+
+#define PAS_INIT_IMAGE_CMD      1
+#define PAS_MEM_SETUP_CMD       2
+#define PAS_AUTH_AND_RESET_CMD  5
+#define PAS_SHUTDOWN_CMD        6
+#define PAS_IS_SUPPORTED_CMD    7
+
+struct qproc {
+       struct device *dev;
+       struct rproc *rproc;
+
+       int pas_id;
+
+       const char *name;
+       struct regulator *pll;
+
+       unsigned proxy_clk_count;
+       struct clk *scm_core_clk;
+       struct clk *scm_iface_clk;
+       struct clk *scm_bus_clk;
+
+       struct clk **proxy_clks;
+};
+
+static int pas_supported(int id)
+{
+       u32 periph = id;
+       u32 ret_val;
+       int ret;
+
+       ret = scm_is_call_available(SCM_SVC_PIL, PAS_IS_SUPPORTED_CMD);
+       if (ret <= 0)
+               return 0;
+
+        ret = scm_call(SCM_SVC_PIL, PAS_IS_SUPPORTED_CMD,
+                      &periph, sizeof(periph),
+                      &ret_val, sizeof(ret_val));
+
+       return ret ? : ret_val;
+}
+
+static int pas_init_image(int id, const char *metadata, size_t size)
+{
+       dma_addr_t mdata_phys;
+       void *mdata_buf;
+       u32 scm_ret;
+       int ret;
+       struct pas_init_image_req {
+               u32 proc;
+               u32 image_addr;
+       } request;
+
+       mdata_buf = dma_alloc_coherent(NULL, size, &mdata_phys, GFP_KERNEL);
+       if (!mdata_buf) {
+               pr_err("Allocation for metadata failed.\n");
+               return -ENOMEM;
+       }
+
+       memcpy(mdata_buf, metadata, size);
+       outer_flush_range(request.image_addr, request.image_addr + size);
+
+       request.proc = id;
+       request.image_addr = mdata_phys;
+
+       ret = scm_call(SCM_SVC_PIL, PAS_INIT_IMAGE_CMD,
+                      &request, sizeof(request),
+                      &scm_ret, sizeof(scm_ret));
+
+       dma_free_coherent(NULL, size, mdata_buf, mdata_phys);
+
+       return ret ? : scm_ret;
+}
+
+static int pas_mem_setup(int id, phys_addr_t start_addr, phys_addr_t size)
+{
+       u32 scm_ret;
+       int ret;
+       struct pas_init_image_req {
+               u32 proc;
+               u32 start_addr;
+               u32 len;
+       } request;
+
+       request.proc = id;
+       request.start_addr = start_addr;
+       request.len = size;
+
+       ret = scm_call(SCM_SVC_PIL, PAS_MEM_SETUP_CMD,
+                      &request, sizeof(request),
+                      &scm_ret, sizeof(scm_ret));
+
+       return ret ? : scm_ret;
+}
+
+static int pas_auth_and_reset(int id)
+{
+       u32 proc = id;
+       u32 scm_ret;
+       int ret;
+
+       ret = scm_call(SCM_SVC_PIL, PAS_AUTH_AND_RESET_CMD,
+                      &proc, sizeof(proc),
+                      &scm_ret, sizeof(scm_ret));
+
+       return ret ? : scm_ret;
+}
+
+static int pas_shutdown(int id)
+{
+       u32 scm_ret;
+       u32 proc = id;
+       int ret;
+
+       ret = scm_call(SCM_SVC_PIL, PAS_SHUTDOWN_CMD,
+                      &proc, sizeof(proc),
+                      &scm_ret, sizeof(scm_ret));
+
+       return ret ? : scm_ret;
+}
+
+static int qproc_scm_clk_enable(struct qproc *qproc)
+{
+       int ret;
+
+       ret = clk_prepare_enable(qproc->scm_core_clk);
+       if (ret)
+               goto bail;
+       ret = clk_prepare_enable(qproc->scm_iface_clk);
+       if (ret)
+               goto disable_core;
+       ret = clk_prepare_enable(qproc->scm_bus_clk);
+       if (ret)
+               goto disable_iface;
+
+       return 0;
+
+disable_iface:
+       clk_disable_unprepare(qproc->scm_iface_clk);
+disable_core:
+       clk_disable_unprepare(qproc->scm_core_clk);
+bail:
+       return ret;
+}
+
+static void qproc_scm_clk_disable(struct qproc *qproc)
+{
+       clk_disable_unprepare(qproc->scm_core_clk);
+       clk_disable_unprepare(qproc->scm_iface_clk);
+       clk_disable_unprepare(qproc->scm_bus_clk);
+}
+
+/**
+ * struct pil_mdt - Representation of <name>.mdt file in memory
+ * @hdr: ELF32 header
+ * @phdr: ELF32 program headers
+ */
+struct mdt_hdr {
+       struct elf32_hdr hdr;
+       struct elf32_phdr phdr[];
+};
+
+#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) &&
+              p->p_memsz;
+}
+
+static bool segment_is_relocatable(const struct elf32_phdr *p)
+{
+       return !!(p->p_flags & BIT(27));
+}
+
+/**
+ * rproc_mdt_sanity_check() - sanity check mdt firmware header
+ * @rproc: the remote processor handle
+ * @fw: the mdt header firmware image
+ */
+static int qproc_sanity_check(struct rproc *rproc,
+                                 const struct firmware *fw)
+{
+       struct elf32_hdr *ehdr;
+       struct mdt_hdr *mdt;
+
+       if (!fw) {
+               dev_err(&rproc->dev, "failed to load %s\n", rproc->name);
+               return -EINVAL;
+       }
+
+       if (fw->size < sizeof(struct elf32_hdr)) {
+               dev_err(&rproc->dev, "image is too small\n");
+               return -EINVAL;
+       }
+
+       mdt = (struct mdt_hdr *)fw->data;
+       ehdr = &mdt->hdr;
+
+       if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) {
+               dev_err(&rproc->dev, "image is corrupted (bad magic)\n");
+               return -EINVAL;
+       }
+
+       if (ehdr->e_phnum == 0) {
+               dev_err(&rproc->dev, "no loadable segments\n");
+               return -EINVAL;
+       }
+
+       if (sizeof(struct elf32_phdr) * ehdr->e_phnum +
+           sizeof(struct elf32_hdr) > fw->size) {
+               dev_err(&rproc->dev, "firmware size is too small\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static struct resource_table * qproc_find_rsc_table(struct rproc *rproc,
+                                                   const struct firmware *fw,
+                                                   int *tablesz)
+{
+       static struct resource_table table = { .ver = 1, };
+
+       *tablesz = sizeof(table);
+       return &table;
+}
+
+static int qproc_load_segment(struct rproc *rproc, const char *fw_name, const struct elf32_phdr *phdr)
+{
+       const struct firmware *fw;
+       void *ptr;
+       int ret = 0;
+
+       ptr = ioremap(phdr->p_paddr, phdr->p_memsz);
+       if (!ptr) {
+               dev_err(&rproc->dev, "failed to ioremap segment area (0x%x+0x%x)\n", phdr->p_paddr, phdr->p_memsz);
+               return -EBUSY;
+       }
+
+       if (phdr->p_filesz) {
+               ret = request_firmware(&fw, fw_name, &rproc->dev);
+               if (ret) {
+                       dev_err(&rproc->dev, "failed to load %s\n", fw_name);
+                       goto out;
+               }
+
+               memcpy(ptr, fw->data, fw->size);
+
+               release_firmware(fw);
+       }
+
+       if (phdr->p_memsz > phdr->p_filesz)
+               memset(ptr + phdr->p_filesz, 0, phdr->p_memsz - phdr->p_filesz);
+
+out:
+       iounmap(ptr);
+       return ret;
+}
+
+static int qproc_load(struct rproc *rproc, const struct firmware *fw)
+{
+       const struct elf32_phdr *phdr;
+       const struct elf32_hdr *ehdr;
+       const struct mdt_hdr *mdt;
+       phys_addr_t min_addr = (phys_addr_t)ULLONG_MAX;
+       phys_addr_t max_addr = 0;
+       struct qproc *qproc = rproc->priv;
+       char *fw_name;
+       int ext;
+       int ret;
+       int i;
+
+       ret = qproc_scm_clk_enable(qproc);
+       if (ret)
+               return ret;
+
+       mdt = (struct mdt_hdr *)fw->data;
+       ehdr = &mdt->hdr;
+
+       for (i = 0; i < ehdr->e_phnum; i++) {
+               phdr = &mdt->phdr[i];
+
+               if (!segment_is_loadable(phdr))
+                       continue;
+
+               if (segment_is_relocatable(phdr)) {
+                       dev_err(&rproc->dev, "relocation unsupported\n");
+                       return -EINVAL;
+               }
+
+               if (phdr->p_paddr < min_addr)
+                       min_addr = phdr->p_paddr;
+
+               if (phdr->p_paddr + phdr->p_memsz > max_addr)
+                       max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K);
+       }
+
+       ret = pas_init_image(qproc->pas_id, fw->data, fw->size);
+       if (ret) {
+               dev_err(qproc->dev, "Invalid firmware metadata\n");
+               return -EINVAL;
+       }
+
+       dev_dbg(qproc->dev, "pas_mem_setup(0x%x, 0x%x)\n", min_addr, max_addr - min_addr);
+
+       ret = pas_mem_setup(qproc->pas_id, min_addr, max_addr - min_addr);
+       if (ret) {
+               dev_err(qproc->dev, "unable to setup memory for image\n");
+               return -EINVAL;
+       }
+
+       ext = strlen(rproc->firmware) - 4;
+       if (ext < 0)
+               return -EINVAL;
+
+       if (strcmp(rproc->firmware + ext, ".mdt"))
+               return -EINVAL;
+
+       fw_name = kstrdup(rproc->firmware, GFP_KERNEL);
+       if (!fw_name)
+               return -ENOMEM;
+
+       for (i = 0; i < ehdr->e_phnum; i++) {
+               phdr = &mdt->phdr[i];
+
+               if (!segment_is_loadable(phdr))
+                       continue;
+
+               sprintf(fw_name + ext, ".b%02d", i);
+               ret = qproc_load_segment(rproc, fw_name, phdr);
+               if (ret)
+                       break;
+       }
+
+       kfree(fw_name);
+
+       qproc_scm_clk_disable(qproc);
+
+       return 0;
+}
+
+const struct rproc_fw_ops qproc_fw_ops = {
+       .find_rsc_table = qproc_find_rsc_table,
+       .load = qproc_load,
+       .sanity_check = qproc_sanity_check,
+};
+
+static int qproc_start(struct rproc *rproc)
+{
+       struct qproc *qproc = (struct qproc *)rproc->priv;
+       int ret;
+
+       ret = regulator_enable(qproc->pll);
+       if (ret) {
+               dev_err(qproc->dev, "failed to enable pll supply\n");
+               return ret;
+       }
+
+       ret = qproc_scm_clk_enable(qproc);
+       if (ret)
+               goto disable_regulator;
+
+       ret = pas_auth_and_reset(qproc->pas_id);
+       if (ret) {
+               dev_err(qproc->dev,
+                               "failed to authenticate image and release reset\n");
+               goto unroll_clocks;
+       }
+
+       return 0;
+
+unroll_clocks:
+       qproc_scm_clk_disable(qproc);
+
+disable_regulator:
+       regulator_disable(qproc->pll);
+
+       return ret;
+}
+
+static int qproc_stop(struct rproc *rproc)
+{
+       struct qproc *qproc = (struct qproc *)rproc->priv;
+       int ret;
+       int i;
+
+       ret = pas_shutdown(qproc->pas_id);
+       if (ret) {
+               dev_err(qproc->dev, "failed to shutdown: %d\n", ret);
+               return ret;
+       }
+
+       for (i = 0; i < qproc->proxy_clk_count; i++)
+               clk_disable_unprepare(qproc->proxy_clks[i]);
+
+       ret = regulator_disable(qproc->pll);
+       if (ret)
+               dev_warn(qproc->dev, "failed to disable pll supply\n");
+
+       return ret;
+}
+
+static const struct rproc_ops qproc_ops = {
+       .start = qproc_start,
+       .stop = qproc_stop,
+};
+
+static ssize_t qproc_boot_store(struct device *dev,
+                               struct device_attribute *attr,
+                               const char *buf, size_t size)
+{
+       struct qproc *qproc = dev_get_drvdata(dev);
+       int ret;
+
+       ret = rproc_boot(qproc->rproc);
+
+       return ret ? : size;
+}
+
+static ssize_t qproc_shutdown_store(struct device *dev,
+                                   struct device_attribute *attr,
+                                   const char *buf, size_t size)
+{
+       struct qproc *qproc = dev_get_drvdata(dev);
+
+       rproc_shutdown(qproc->rproc);
+
+       return size;
+}
+
+static const struct device_attribute qproc_attrs[] = {
+       __ATTR(boot, S_IWUSR, 0, qproc_boot_store),
+       __ATTR(shutdown, S_IWUSR, 0, qproc_shutdown_store),
+};
+
+static int qproc_init_pas(struct qproc *qproc)
+{
+       char *key;
+       int ret;
+
+       key = "qcom,pas-id";
+       ret = of_property_read_u32(qproc->dev->of_node, key, &qproc->pas_id);
+       if (ret) {
+               dev_err(qproc->dev, "Missing or incorrect %s\n", key);
+               return -EINVAL;
+       }
+
+       if (!pas_supported(qproc->pas_id)) {
+               dev_err(qproc->dev, "PAS is not available for %d\n", qproc->pas_id);
+               return -EIO;
+       }
+
+       return 0;
+}
+
+static int qproc_init_clocks(struct qproc *qproc)
+{
+       long rate;
+       int ret;
+
+       qproc->scm_core_clk = devm_clk_get(qproc->dev, "scm_core_clk");
+       if (IS_ERR(qproc->scm_core_clk)) {
+               if (PTR_ERR(qproc->scm_core_clk) != -EPROBE_DEFER)
+                       dev_err(qproc->dev, "failed to acquire scm_core_clk\n");
+               return PTR_ERR(qproc->scm_core_clk);
+       }
+
+       qproc->scm_iface_clk = devm_clk_get(qproc->dev, "scm_iface_clk");
+       if (IS_ERR(qproc->scm_iface_clk)) {
+               if (PTR_ERR(qproc->scm_iface_clk) != -EPROBE_DEFER)
+                       dev_err(qproc->dev, "failed to acquire scm_iface_clk\n");
+               return PTR_ERR(qproc->scm_iface_clk);
+       }
+
+       qproc->scm_bus_clk = devm_clk_get(qproc->dev, "scm_bus_clk");
+       if (IS_ERR(qproc->scm_bus_clk)) {
+               if (PTR_ERR(qproc->scm_bus_clk) != -EPROBE_DEFER)
+                       dev_err(qproc->dev, "failed to acquire scm_bus_clk\n");
+               return PTR_ERR(qproc->scm_bus_clk);
+       }
+
+       rate = clk_round_rate(qproc->scm_core_clk, 50000000);
+       ret = clk_set_rate(qproc->scm_core_clk, rate);
+       if (ret) {
+               dev_err(qproc->dev, "failed to set rate of scm_core_clk\n");
+               return ret;
+       }
+
+       return 0;
+}
+
+static int qproc_init_regulators(struct qproc *qproc)
+{
+       int ret;
+       u32 uA;
+       u32 uV;
+
+       qproc->pll = devm_regulator_get(qproc->dev, "qcom,pll");
+       if (IS_ERR(qproc->pll)) {
+               if (PTR_ERR(qproc->pll) != -EPROBE_DEFER)
+                       dev_err(qproc->dev, "failed to aquire regulator\n");
+               return PTR_ERR(qproc->pll);
+       }
+
+       ret = of_property_read_u32(qproc->dev->of_node, "qcom,pll-uV", &uV);
+       if (ret)
+               dev_warn(qproc->dev, "failed to read qcom,pll_uV, skipping\n");
+       else
+               regulator_set_voltage(qproc->pll, uV, uV);
+
+       ret = of_property_read_u32(qproc->dev->of_node, "qcom,pll-uA", &uA);
+       if (ret)
+               dev_warn(qproc->dev, "failed to read qcom,pll_uA, skipping\n");
+       else
+               regulator_set_optimum_mode(qproc->pll, uA);
+
+       return 0;
+}
+
+static int qproc_probe(struct platform_device *pdev)
+{
+       struct qproc *qproc;
+       struct rproc *rproc;
+       char *fw_name;
+       const char *name;
+       const char *key;
+       int ret;
+       int i;
+
+       key = "qcom,firmware-name";
+       ret = of_property_read_string(pdev->dev.of_node, key, &name);
+       if (ret) {
+               dev_err(&pdev->dev, "missing or incorrect %s\n", key);
+               return -EINVAL;
+       }
+
+       fw_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s.mdt", name);
+       if (!fw_name)
+               return -ENOMEM;
+
+       rproc = rproc_alloc(&pdev->dev, pdev->name, &qproc_ops,
+                           fw_name, sizeof(*qproc));
+       if (!rproc)
+               return -ENOMEM;
+
+       rproc->fw_ops = &qproc_fw_ops;
+
+       qproc = (struct qproc *)rproc->priv;
+       qproc->dev = &pdev->dev;
+       qproc->rproc = rproc;
+       qproc->name = name;
+       platform_set_drvdata(pdev, qproc);
+
+       ret = qproc_init_pas(qproc);
+       if (ret)
+               goto free_rproc;
+
+       ret = qproc_init_clocks(qproc);
+       if (ret)
+               goto free_rproc;
+
+       ret = qproc_init_regulators(qproc);
+       if (ret)
+               goto free_rproc;
+
+       for (i = 0; i < ARRAY_SIZE(qproc_attrs); i++) {
+               ret = device_create_file(&pdev->dev, &qproc_attrs[i]);
+               if (ret) {
+                       dev_err(&pdev->dev, "unable to create sysfs file\n");
+                       goto remove_device_files;
+               }
+       }
+
+       ret = rproc_add(rproc);
+       if (ret)
+               goto remove_device_files;
+
+       return 0;
+
+remove_device_files:
+       for (i--; i >= 0; i--)
+               device_remove_file(&pdev->dev, &qproc_attrs[i]);
+
+free_rproc:
+       rproc_put(rproc);
+
+       return ret;
+}
+
+static int qproc_remove(struct platform_device *pdev)
+{
+       struct qproc *qproc = platform_get_drvdata(pdev);
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(qproc_attrs); i++)
+               device_remove_file(&pdev->dev, &qproc_attrs[i]);
+
+       rproc_put(qproc->rproc);
+
+       return 0;
+}
+
+static const struct of_device_id qproc_of_match[] = {
+       { .compatible = "qcom,tz-pil", },
+       { },
+};
+
+static struct platform_driver qproc_driver = {
+       .probe = qproc_probe,
+       .remove = qproc_remove,
+       .driver = {
+               .name = "qcom-tz-pil",
+               .owner = THIS_MODULE,
+               .of_match_table = qproc_of_match,
+       },
+};
+
+module_platform_driver(qproc_driver);