aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/firmware/xilinx/zynqmp-secure.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/firmware/xilinx/zynqmp-secure.c')
-rw-r--r--drivers/firmware/xilinx/zynqmp-secure.c197
1 files changed, 197 insertions, 0 deletions
diff --git a/drivers/firmware/xilinx/zynqmp-secure.c b/drivers/firmware/xilinx/zynqmp-secure.c
new file mode 100644
index 000000000000..1d105e04239f
--- /dev/null
+++ b/drivers/firmware/xilinx/zynqmp-secure.c
@@ -0,0 +1,197 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Xilinx ZynqMP SecureFw Driver.
+ * Copyright (c) 2018 Xilinx Inc.
+ */
+
+#include <asm/cacheflush.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/firmware.h>
+#include <linux/firmware/xlnx-zynqmp.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of_device.h>
+
+#define ZYNQMP_AES_KEY_SIZE 64
+
+static u8 key[ZYNQMP_AES_KEY_SIZE] = {0};
+static dma_addr_t dma_addr;
+static u8 *keyptr;
+static size_t dma_size;
+static char *kbuf;
+
+static const struct zynqmp_eemi_ops *eemi_ops;
+
+static ssize_t secure_load_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ const struct firmware *fw;
+ char image_name[NAME_MAX];
+ u64 dst, ret;
+ int len;
+
+ if (!eemi_ops || !eemi_ops->secure_image)
+ return -EFAULT;
+
+ strncpy(image_name, buf, NAME_MAX);
+ len = strlen(image_name);
+ if (image_name[len - 1] == '\n')
+ image_name[len - 1] = 0;
+
+ ret = request_firmware(&fw, image_name, dev);
+ if (ret) {
+ dev_err(dev, "Error requesting firmware %s\n", image_name);
+ return ret;
+ }
+ dma_size = fw->size;
+
+ if (keyptr)
+ dma_size = fw->size + ZYNQMP_AES_KEY_SIZE;
+
+ kbuf = dma_alloc_coherent(dev, dma_size,
+ &dma_addr, GFP_KERNEL);
+ if (!kbuf)
+ return -ENOMEM;
+
+ memcpy(kbuf, fw->data, fw->size);
+
+ if (keyptr)
+ memcpy(kbuf + fw->size, key, ZYNQMP_AES_KEY_SIZE);
+
+ /* To ensure cache coherency */
+ __flush_cache_user_range((unsigned long)kbuf,
+ (unsigned long)kbuf + dma_size);
+ release_firmware(fw);
+
+ if (keyptr)
+ ret = eemi_ops->secure_image(dma_addr, dma_addr + fw->size,
+ &dst);
+ else
+ ret = eemi_ops->secure_image(dma_addr, 0, &dst);
+
+ if (ret) {
+ dev_info(dev, "Failed to load secure image \r\n");
+ return ret;
+ }
+ dev_info(dev, "Verified image at 0x%llx\n", dst);
+
+ return count;
+}
+
+static ssize_t key_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return snprintf(buf, ZYNQMP_AES_KEY_SIZE + 1, "%s\n", key);
+}
+
+static ssize_t key_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ memcpy(key, buf, count);
+ keyptr = &key[0];
+ return count;
+}
+
+static ssize_t secure_load_done_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ unsigned int value;
+
+ ret = kstrtouint(buf, 10, &value);
+ if (ret)
+ return ret;
+ if (value)
+ dma_free_coherent(dev, dma_size, kbuf, dma_addr);
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(key);
+static DEVICE_ATTR_WO(secure_load);
+static DEVICE_ATTR_WO(secure_load_done);
+
+static struct attribute *securefw_attrs[] = {
+ &dev_attr_secure_load_done.attr,
+ &dev_attr_secure_load.attr,
+ &dev_attr_key.attr,
+ NULL,
+};
+
+ATTRIBUTE_GROUPS(securefw);
+
+static int securefw_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct platform_device *securefw_pdev;
+
+ eemi_ops = zynqmp_pm_get_eemi_ops();
+ if (IS_ERR(eemi_ops))
+ return PTR_ERR(eemi_ops);
+
+ securefw_pdev = pdev;
+
+ securefw_pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
+
+ ret = of_dma_configure(&securefw_pdev->dev, NULL, true);
+ if (ret < 0) {
+ dev_info(&securefw_pdev->dev, "Cannot setup DMA ops\r\n");
+ return ret;
+ }
+
+ ret = sysfs_create_groups(&securefw_pdev->dev.kobj, securefw_groups);
+ if (ret)
+ return ret;
+
+ dev_info(&securefw_pdev->dev, "securefw probed\r\n");
+ return ret;
+}
+
+static int securefw_remove(struct platform_device *pdev)
+{
+ sysfs_remove_groups(&pdev->dev.kobj, securefw_groups);
+ return 0;
+}
+
+static struct platform_driver securefw_driver = {
+ .driver = {
+ .name = "securefw",
+ },
+ .probe = securefw_probe,
+ .remove = securefw_remove,
+};
+
+static struct platform_device *securefw_dev_reg;
+
+static int __init zynqmp_secure_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&securefw_driver);
+ if (ret)
+ return ret;
+
+ securefw_dev_reg = platform_device_register_simple("securefw", -1,
+ NULL, 0);
+ if (IS_ERR(securefw_dev_reg)) {
+ ret = PTR_ERR(securefw_dev_reg);
+ platform_driver_unregister(&securefw_driver);
+ return ret;
+ }
+ return 0;
+}
+
+static void __exit zynqmp_secure_exit(void)
+{
+ platform_device_unregister(securefw_dev_reg);
+ platform_driver_unregister(&securefw_driver);
+}
+
+module_init(zynqmp_secure_init);
+module_exit(zynqmp_secure_exit);