[COMMON] soc: samsung: seclog: Add Secure log driver
authorJunho Choi <junhosj.choi@samsung.com>
Mon, 12 Mar 2018 08:52:39 +0000 (17:52 +0900)
committerJunho Choi <junhosj.choi@samsung.com>
Wed, 23 May 2018 23:52:41 +0000 (08:52 +0900)
Change-Id: I08dcd4562893b9e4dc233db8cdd2556431c4d273
Signed-off-by: Junho Choi <junhosj.choi@samsung.com>
drivers/soc/samsung/Kconfig
drivers/soc/samsung/Makefile
drivers/soc/samsung/exynos-seclog.c [new file with mode: 0644]
include/soc/samsung/exynos-seclog.h [new file with mode: 0644]

index 855103cb3aa97fed44ff7636bfe9738a6f50d03f..3ba906cf066839739677a3d0af633d0fbe1028bc 100644 (file)
@@ -120,4 +120,9 @@ config EXYNOS_PD
                depends on PM
                select PM_GENERIC_DOMAINS
 
+config EXYNOS_SECURE_LOG
+       bool "Exynos Secure Log"
+       default y
+       help
+         Support Exynos Secure Log
 endif
index 980aa706dbc638ee263a5f3b8af5fe5a07e6fe18..f2722d59bcb05776d1e05310ac12c0b6c59a26f6 100644 (file)
@@ -45,3 +45,6 @@ obj-$(CONFIG_ARCH_EXYNOS)      += exynos-cpupm.o
 #PM
 obj-$(CONFIG_ARCH_EXYNOS)      += exynos-powermode.o
 obj-$(CONFIG_ARCH_EXYNOS)      += exynos-pm.o
+
+# Exynos Secure Log
+obj-$(CONFIG_EXYNOS_SECURE_LOG)        += exynos-seclog.o
diff --git a/drivers/soc/samsung/exynos-seclog.c b/drivers/soc/samsung/exynos-seclog.c
new file mode 100644 (file)
index 0000000..101a5a6
--- /dev/null
@@ -0,0 +1,362 @@
+/*
+ * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ *           http://www.samsung.com/
+ *
+ * EXYNOS - Logging message from Secure World
+ * Author: Junho Choi <junhosj.choi@samsung.com>
+ *
+ * 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.
+ */
+
+#include <linux/err.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqreturn.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/slab.h>
+#include <linux/dma-buf.h>
+#include <linux/vmalloc.h>
+#include <linux/workqueue.h>
+#include <linux/debugfs.h>
+#include <linux/smc.h>
+
+#include <soc/samsung/exynos-seclog.h>
+
+/*
+ * Macro for converting physical address to
+ * virtual address that is mapped by vmap
+ */
+#define SECLOG_PHYS_TO_VIRT(addr)              ((unsigned long)ldata.virt_addr \
+                                               + ((unsigned long)(addr)        \
+                                               - ldata.phys_addr))
+
+static struct seclog_data ldata;
+static struct seclog_ctx slog_ctx;
+static struct sec_log_info *sec_log[NR_CPUS];
+
+
+static void *exynos_seclog_request_region(unsigned long addr,
+                                       unsigned int size)
+{
+       int i;
+       unsigned int num_pages = (size >> PAGE_SHIFT);
+       pgprot_t prot = pgprot_writecombine(PAGE_KERNEL);
+       struct page **pages = NULL;
+       void *v_addr = NULL;
+
+       if (!addr)
+               return NULL;
+
+       pages = kmalloc(sizeof(struct page *) * num_pages, GFP_ATOMIC);
+       if (!pages)
+               return NULL;
+
+       for (i = 0; i < num_pages; i++) {
+               pages[i] = phys_to_page(addr);
+               addr += PAGE_SIZE;
+       }
+
+       v_addr = vmap(pages, num_pages, VM_MAP, prot);
+       kfree(pages);
+
+       return v_addr;
+}
+
+static void exynos_seclog_worker(struct work_struct *work)
+{
+       struct log_header_info *v_log_h = NULL;
+       char *v_log = NULL;
+       unsigned long v_log_addr = 0;
+       unsigned int cpu = 0;
+
+       pr_debug("%s: Start seclog_worker\n", __func__);
+
+       /* Print log message in a message buffer */
+       for (cpu = 0; cpu < NR_CPUS; cpu++) {
+               v_log_addr = SECLOG_PHYS_TO_VIRT(sec_log[cpu]->start_log_addr);
+
+               while (sec_log[cpu]->log_read_cnt != sec_log[cpu]->log_write_cnt) {
+                       /* Check the log message is reached the end of log buffer */
+                       if (sec_log[cpu]->log_return_cnt) {
+                               if (sec_log[cpu]->log_read_cnt
+                                       == sec_log[cpu]->log_return_cnt) {
+                                       sec_log[cpu]->log_return_cnt = 0;
+                                       v_log_addr = SECLOG_PHYS_TO_VIRT(sec_log[cpu]->initial_log_addr);
+                               }
+                       }
+
+                       /* For debug */
+                       pr_debug("[SECLOG_DEBUG C%d] read_cnt[%d]\n",
+                                       cpu, sec_log[cpu]->log_read_cnt);
+                       pr_debug("[SECLOG_DEBUG C%d] write_cnt[%d]\n",
+                                       cpu, sec_log[cpu]->log_write_cnt);
+                       pr_debug("[SECLOG_DEBUG C%d] return_cnt[%d]\n",
+                                       cpu, sec_log[cpu]->log_return_cnt);
+                       pr_debug("[SECLOG_DEBUG C%d] v_log_addr[%#lx]\n",
+                                       cpu, v_log_addr);
+                       pr_debug("[SECLOG_DEBUG C%d] p_log_addr[%#lx]\n",
+                                       cpu,
+                                       v_log_addr
+                                       - (unsigned long)ldata.virt_addr
+                                       + ldata.phys_addr);
+
+                       /* Set log address and log's header address */
+                       v_log_h = (struct log_header_info *)v_log_addr;
+                       v_log = (char *)v_log_addr + sizeof(struct log_header_info);
+
+                       /* For debug */
+                       pr_debug("[SECLOG_DEBUG C%d] v_log_h[%#lx]\n",
+                                       cpu, (unsigned long)v_log_h);
+                       pr_debug("[SECLOG_DEBUG C%d] v_log[%#lx]\n",
+                                       cpu, (unsigned long)v_log);
+                       pr_debug("[SECLOG_DEBUG C%d] log_len = %d\n",
+                                       cpu, v_log_h->log_len);
+
+                       /* Print logs from SWd */
+                       pr_info("[SECLOG C%d] [%06d.%06d] %s",
+                               cpu,
+                               v_log_h->tv_sec,
+                               v_log_h->tv_usec,
+                               v_log);
+
+                       /* v_log_addr is moved to next log */
+                       v_log_addr += (sizeof(struct log_header_info) + v_log_h->log_len);
+                       CHECK_AND_ALIGN_4BYTES(v_log_addr);
+
+                       /* Increase read count */
+                       (sec_log[cpu]->log_read_cnt)++;
+               }
+       }
+}
+
+static irqreturn_t exynos_seclog_irq_handler(int irq, void *dev_id)
+{
+       unsigned int cpu = 0;
+
+       if (slog_ctx.enabled) {
+               schedule_work(&slog_ctx.work);
+       } else {
+               /* Skip all log messages */
+               for (cpu = 0; cpu < NR_CPUS; cpu++) {
+                       sec_log[cpu]->log_read_cnt = sec_log[cpu]->log_write_cnt;
+                       sec_log[cpu]->log_return_cnt = 0;
+               }
+       }
+
+       pr_debug("ISR for Secure log is implemented!\n");
+
+       return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_OF_RESERVED_MEM
+static int __init exynos_seclog_reserved_mem_setup(struct reserved_mem *remem)
+{
+       ldata.phys_addr = remem->base;
+       ldata.size = remem->size;
+
+       pr_err("%s: Reserved memory for seclog: addr=%lx, size=%lx\n",
+                       __func__, ldata.phys_addr, ldata.size);
+
+       return 0;
+}
+RESERVEDMEM_OF_DECLARE(seclog_mem, "exynos,seclog", exynos_seclog_reserved_mem_setup);
+#endif /* CONFIG_OF_RESERVED_MEM */
+
+static int exynos_seclog_probe(struct platform_device *pdev)
+{
+       struct irq_data *seclog_irqd = NULL;
+       irq_hw_number_t hwirq = 0;
+       int err, err_ldfw, i;
+
+       /* Translate PA to VA of message buffer */
+       ldata.virt_addr = exynos_seclog_request_region(ldata.phys_addr, ldata.size);
+       if (!ldata.virt_addr) {
+               dev_err(&pdev->dev, "Fail to translate message buffer\n");
+               return -EFAULT;
+       }
+
+       slog_ctx.irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
+       if (!slog_ctx.irq) {
+               dev_err(&pdev->dev, "Fail to get irq from dt\n");
+               vunmap(ldata.virt_addr);
+               return -EINVAL;
+       }
+
+       /* Get irq_data for secure log */
+       seclog_irqd = irq_get_irq_data(slog_ctx.irq);
+       if (!seclog_irqd) {
+               dev_err(&pdev->dev, "Fail to get irq_data\n");
+               vunmap(ldata.virt_addr);
+               return -EINVAL;
+       }
+
+       /* Get hardware interrupt number */
+       hwirq = irqd_to_hwirq(seclog_irqd);
+
+       dev_dbg(&pdev->dev,
+               "hwirq for seclog (%ld)\n",
+               hwirq);
+
+       err = devm_request_irq(&pdev->dev, slog_ctx.irq,
+                               exynos_seclog_irq_handler,
+                               IRQF_TRIGGER_RISING, pdev->name, NULL);
+       if (err) {
+               dev_err(&pdev->dev,
+                       "Fail to request IRQ handler. err(%d) irq(%d)\n",
+                       err, slog_ctx.irq);
+               vunmap(ldata.virt_addr);
+               return err;
+       }
+
+       /* Set workqueue for Secure log as bottom half */
+       INIT_WORK(&slog_ctx.work, exynos_seclog_worker);
+       slog_ctx.enabled = true;
+
+       /* Create debugfs for Secure log */
+       slog_ctx.debug_dir = debugfs_create_dir("seclog", NULL);
+       debugfs_create_bool("seclog_debug", 0600, slog_ctx.debug_dir,
+                       &slog_ctx.enabled);
+
+       /* Send message buffer information to EL3 Monitor */
+       dev_dbg(&pdev->dev,
+               "SMC arguments(%#x, %#lx, %#lx, %ld)\n",
+               SMC_CMD_SEC_LOG_INFO, ldata.phys_addr, ldata.size, hwirq);
+
+       err = exynos_smc(SMC_CMD_SEC_LOG_INFO, ldata.phys_addr, ldata.size, hwirq);
+       if (err) {
+               switch (err) {
+               case ERROR_INVALID_LOG_LEN:
+                       dev_err(&pdev->dev,
+                               "[ERROR] Invalid log length [Message buffer length = %#lx]\n",
+                               ldata.size);
+                       break;
+               case ERROR_INVALID_LOG_ADDR:
+                       dev_err(&pdev->dev,
+                               "[ERROR] Invalid log address [Message buffer address = %#lx]\n",
+                               ldata.phys_addr);
+                       break;
+               case ERROR_INVALID_INTR_NUM:
+                       dev_err(&pdev->dev,
+                               "[ERROR] Invalid interrupt number [Interrupt number = %ld]\n",
+                               hwirq);
+                       break;
+               case ERROR_ALREADY_INITIALIZED:
+                       dev_err(&pdev->dev, "[ERROR] Already initialized\n");
+                       break;
+               case SMC_CMD_SEC_LOG_INFO:
+                       dev_err(&pdev->dev, "[ERROR] EL3 Monitor doesn't support Secure log\n");
+                       break;
+               default:
+                       /* Error cases by LDFW */
+                       if ((err & MASK_LDFW_MAGIC) == LDFW_MAGIC) {
+                               for (i = 0; i < LDFW_MAX_NUM; i++) {
+                                       err_ldfw = (err >> (BITLEN_LDFW_ERROR * i))
+                                                       & MASK_LDFW_ERROR;
+
+                                       if (err_ldfw) {
+                                               switch (err_ldfw) {
+                                               case ERROR_NOT_SUPPORT_LDFW_SEC_LOG:
+                                                       dev_err(&pdev->dev,
+                                                               "[ERROR] LDFW[%d]"
+                                                               " doesn't support Secure log\n",
+                                                               i);
+                                                       break;
+                                               case ERROR_LDFW_ALREADY_INITIALIZED:
+                                                       dev_err(&pdev->dev,
+                                                               "[ERROR] LDFW[%d]"
+                                                               " already initialized Secure log\n",
+                                                               i);
+                                                       break;
+                                               case ERROR_NOT_SUPPORT_LDFW_ERR_VALUE:
+                                                       dev_err(&pdev->dev,
+                                                               "[ERROR] LDFW[%d]"
+                                                               " returns unsupported error value\n",
+                                                               i);
+                                                       break;
+                                               default:
+                                                       dev_err(&pdev->dev,
+                                                               "[ERROR] LDFW[%d]"
+                                                               " Unknown error value from LDFW "
+                                                               "[err = %#x]\n",
+                                                               i, err_ldfw);
+                                                       break;
+                                               }
+                                       }
+                               }
+
+                               goto detect_ldfw_err;
+                       } else {
+                               dev_err(&pdev->dev,
+                                       "[ERROR] Unknown error value [err = %#x]\n",
+                                       err);
+                               break;
+                       }
+               }
+
+               dev_err(&pdev->dev, "Fail to initialize Secure log\n");
+
+               devm_free_irq(&pdev->dev, slog_ctx.irq, NULL);
+               vunmap(ldata.virt_addr);
+
+               return -EINVAL;
+       }
+
+detect_ldfw_err:
+       /* Setup virtual address of message buffer of each core */
+       for (i = 0; i < NR_CPUS; i++) {
+               sec_log[i] = (struct sec_log_info *)((unsigned long)ldata.virt_addr
+                                                               + (SECLOG_LOG_BUF_SIZE * i));
+               dev_dbg(&pdev->dev,
+                       "sec_log[C%d]: %#lx\n",
+                       i, (unsigned long)sec_log[i]);
+       }
+
+       dev_info(&pdev->dev,
+               "Message buffer address[%#lx], Message buffer size[%#lx]\n",
+               ldata.phys_addr, ldata.size);
+       dev_info(&pdev->dev, "Exynos Secure Log driver probe done!\n");
+
+       return 0;
+}
+
+static const struct of_device_id exynos_seclog_of_match_table[] = {
+       { .compatible = "samsung,exynos-seclog", },
+       { },
+};
+
+static struct platform_driver exynos_seclog_driver = {
+       .probe = exynos_seclog_probe,
+       .driver = {
+               .name = "exynos-seclog",
+               .owner = THIS_MODULE,
+               .of_match_table = of_match_ptr(exynos_seclog_of_match_table),
+       }
+};
+
+static int __init exynos_seclog_init(void)
+{
+       return platform_driver_register(&exynos_seclog_driver);
+}
+
+static void __exit exynos_seclog_exit(void)
+{
+       if (slog_ctx.enabled)
+               schedule_work(&slog_ctx.work);
+       flush_work(&slog_ctx.work);
+
+       platform_driver_unregister(&exynos_seclog_driver);
+}
+
+module_init(exynos_seclog_init);
+module_exit(exynos_seclog_exit);
+
+MODULE_DESCRIPTION("Exynos Secure log printing driver");
+MODULE_AUTHOR("<junhosj.choi@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/soc/samsung/exynos-seclog.h b/include/soc/samsung/exynos-seclog.h
new file mode 100644 (file)
index 0000000..283e761
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ *           http://www.samsung.com/
+ *
+ * EXYNOS - Logging message from Secure World
+ * Author: Junho Choi <junhosj.choi@samsung.com>
+ *
+ * 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.
+ */
+
+#ifndef __EXYNOS_SECLOG_H
+#define __EXYNOS_SECLOG_H
+
+/* Error code */
+#define ERROR_INVALID_LOG_LEN                  (-1)
+#define ERROR_INVALID_LOG_ADDR                 (-2)
+#define ERROR_INVALID_INTR_NUM                 (-3)
+#define ERROR_ALREADY_INITIALIZED              (-4)
+
+/* Error code from LDFW */
+#define ERROR_LDFW_ALREADY_INITIALIZED         (1)
+#define ERROR_LDFW_CALL_FROM_NON_SECURE                (2)
+#define ERROR_NOT_SUPPORT_LDFW_ERR_VALUE       (0xE)
+#define ERROR_NOT_SUPPORT_LDFW_SEC_LOG         (0xF)
+
+#define BITLEN_LDFW_ERROR                      (4)
+#define MASK_LDFW_ERROR                                (0xF)
+
+#define SHIFT_LDFW_MAGIC                       (28)
+#define MASK_LDFW_MAGIC                                (0xF << SHIFT_LDFW_MAGIC)
+#define LDFW_MAGIC                             (0xA << SHIFT_LDFW_MAGIC)
+
+#define LDFW_MAX_NUM                           (7)
+
+/* Secure log buffer information */
+#define SECLOG_LOG_BUF_SIZE                    (0x10000)
+
+/* Alignment with 4 bytes */
+#define FOUR_BYTES_SHIFT                       (2)
+#define FOUR_BYTES_MASK                                ((1 << FOUR_BYTES_SHIFT) - 1)
+
+/*
+ * If input address is not aligned with 4 bytes,
+ * it makes this address be aligned with next 4 bytes.
+ * Otherwise, there is no action.
+ */
+#define CHECK_AND_ALIGN_4BYTES(addr)           do {            \
+               if ((addr) & FOUR_BYTES_MASK) {                 \
+                       addr &= ~FOUR_BYTES_MASK;               \
+                       addr += (1 << FOUR_BYTES_SHIFT);        \
+               }                                               \
+       } while (0)
+
+#ifndef __ASSEMBLY__
+/* Reserved memory data */
+struct seclog_data {
+       void *virt_addr;
+       unsigned long phys_addr;
+       unsigned long size;
+};
+
+/* Context for Secure log */
+struct seclog_ctx {
+       struct work_struct work;
+       /* debugfs root */
+       struct dentry *debug_dir;
+       /* seclog can be disabled via debugfs */
+       bool enabled;
+       unsigned int irq;
+};
+
+/* Log header information */
+struct log_header_info {
+       unsigned int log_len;
+       unsigned int tv_sec;
+       unsigned int tv_usec;
+};
+
+/* Secure log information shared with EL3 Monitor and LDFWs */
+struct sec_log_info {
+       /* The count to write log */
+       unsigned int log_write_cnt;
+       /* The count to read log */
+       unsigned int log_read_cnt;
+       /*
+        * The log count when log_buf
+        * returns to initial_log_buf
+        */
+       unsigned int log_return_cnt;
+       /* Start log buffer address */
+       unsigned long start_log_addr;
+       /* Initial log buffer address */
+       unsigned long initial_log_addr;
+};
+#endif /* __ASSEMBLY__ */
+
+#endif /* __EXYNOS_SECLOG_H */