364 lines
9.4 KiB
C
Executable File
364 lines
9.4 KiB
C
Executable File
/*
|
|
* 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_ldfw_error(struct platform_device *pdev,
|
|
int error)
|
|
{
|
|
int err_ldfw, i;
|
|
|
|
for (i = 0; i < LDFW_MAX_NUM; i++) {
|
|
err_ldfw = (error >> (BITLEN_LDFW_ERROR * i))
|
|
& MASK_LDFW_ERROR;
|
|
|
|
switch (err_ldfw) {
|
|
case 0:
|
|
break;
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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_array(num_pages, sizeof(struct page *), 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, 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) {
|
|
exynos_ldfw_error(pdev, err);
|
|
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");
|