--- /dev/null
+/*
+ * 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");
--- /dev/null
+/*
+ * 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 */