blob: da946fa4944f9be2c681b6c149772bb7c3b4fa2e [file] [log] [blame]
/*
* Copyright (c) 2014 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Data structure definition for Exynos IOMMU driver
*
* 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/kernel.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/debugfs.h>
#include <linux/dma-mapping.h>
#include "exynos-iommu-log.h"
int exynos_iommu_init_event_log(struct exynos_iommu_event_log *log,
unsigned int log_len)
{
struct page *page;
int i, order;
size_t fit_size = PAGE_ALIGN(sizeof(*(log->log)) * log_len);
int fit_pages = fit_size / PAGE_SIZE;
/* log_len must be power of 2 */
BUG_ON((log_len - 1) & log_len);
atomic_set(&log->index, 0);
order = get_order(fit_size);
page = alloc_pages(GFP_KERNEL | __GFP_ZERO, order);
if (!page)
return -ENOMEM;
split_page(page, order);
if ((1 << order) > fit_pages) {
int extra = (1 << order) - fit_pages;
for (i = 0; i < extra; i++)
__free_pages(page + fit_pages + i, 0);
}
log->log = page_address(page);
log->log_len = log_len;
return 0;
}
static const char * const sysmmu_event_name[] = {
"n/a", /* not an event */
"ENABLE",
"DISABLE",
"TLB_INV_RANGE",
"TLB_INV_ALL",
"POWERON",
"POWEROFF",
"IOMMU_ATTACH",
"IOMMU_DETACH",
"IOMMU_MAP",
"IOMMU_UNMAP",
"IOMMU_ALLOCSLPD",
"IOMMU_FREESLPD",
"IOVMM_MAP",
"IOVMM_UNMAP"
};
static void exynos_iommu_debug_log_show(struct seq_file *s,
struct sysmmu_event_log *log)
{
struct timeval tv = ktime_to_timeval(log->timestamp);
if (log->event == EVENT_SYSMMU_NONE)
return;
seq_printf(s, "%06ld.%06ld: %15s", tv.tv_sec, tv.tv_usec,
sysmmu_event_name[log->event]);
switch (log->event) {
case EVENT_SYSMMU_ENABLE:
case EVENT_SYSMMU_DISABLE:
case EVENT_SYSMMU_TLB_INV_ALL:
case EVENT_SYSMMU_POWERON:
case EVENT_SYSMMU_POWEROFF:
seq_puts(s, "\n");
break;
case EVENT_SYSMMU_IOMMU_ALLOCSLPD:
case EVENT_SYSMMU_IOMMU_FREESLPD:
seq_printf(s, " @ [iova:%#010x, entry:%#010x)\n",
log->eventdata.range.start,
log->eventdata.range.end);
break;
case EVENT_SYSMMU_TLB_INV_RANGE:
case EVENT_SYSMMU_IOMMU_UNMAP:
case EVENT_SYSMMU_IOVMM_UNMAP:
seq_printf(s, " @ [%#010x, %#010x)\n",
log->eventdata.range.start,
log->eventdata.range.end);
break;
case EVENT_SYSMMU_IOVMM_MAP:
seq_printf(s, " [%#010x, %#010x(+%#x))\n",
log->eventdata.iovmm.start,
log->eventdata.iovmm.end,
log->eventdata.iovmm.dummy);
break;
case EVENT_SYSMMU_IOMMU_MAP:
seq_printf(s, " [%#010x, %#010x) for PFN %#x\n",
log->eventdata.iommu.start,
log->eventdata.iommu.end,
log->eventdata.iommu.pfn);
break;
case EVENT_SYSMMU_IOMMU_ATTACH:
case EVENT_SYSMMU_IOMMU_DETACH:
seq_printf(s, " of %s\n", dev_name(log->eventdata.dev));
break;
default:
BUG();
}
}
static int exynos_iommu_debugfs_log_show(struct seq_file *s, void *unused)
{
struct exynos_iommu_event_log *plog = s->private;
unsigned int index = atomic_read(&plog->index) % plog->log_len;
unsigned int begin = index;
do {
exynos_iommu_debug_log_show(s, &plog->log[index++]);
if (index == plog->log_len)
index = 0;
} while (index != begin);
return 0;
}
static int exynos_iommu_debugfs_log_open(struct inode *inode, struct file *file)
{
return single_open(file, exynos_iommu_debugfs_log_show,
inode->i_private);
}
#define SYSMMU_DENTRY_LOG_ROOT_NAME "eventlog"
static struct dentry *sysmmu_debugfs_log_root;
static struct dentry *iommu_debugfs_log_root;
static const struct file_operations exynos_iommu_debugfs_fops = {
.open = exynos_iommu_debugfs_log_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static void __sysmmu_add_log_to_debugfs(struct dentry *debugfs_root,
struct dentry **debugfs_eventlog_root,
struct exynos_iommu_event_log *log, const char *name)
{
if (!debugfs_root)
return;
if (!(*debugfs_eventlog_root)) {
*debugfs_eventlog_root = debugfs_create_dir(
SYSMMU_DENTRY_LOG_ROOT_NAME, debugfs_root);
if (!(*debugfs_eventlog_root)) {
pr_err("%s: Failed to create 'eventlog' entry\n",
__func__);
return;
}
}
log->debugfs_root = debugfs_create_file(name, 0400,
*debugfs_eventlog_root, log,
&exynos_iommu_debugfs_fops);
if (!log->debugfs_root)
pr_err("%s: Failed to create '%s' entry of 'eventlog'\n",
__func__, name);
}
void sysmmu_add_log_to_debugfs(struct dentry *debugfs_root,
struct exynos_iommu_event_log *log, const char *name)
{
__sysmmu_add_log_to_debugfs(debugfs_root, &sysmmu_debugfs_log_root,
log, name);
}
void iommu_add_log_to_debugfs(struct dentry *debugfs_root,
struct exynos_iommu_event_log *log, const char *name)
{
__sysmmu_add_log_to_debugfs(debugfs_root, &iommu_debugfs_log_root,
log, name);
}
#if defined(CONFIG_EXYNOS_IOVMM)
static struct dentry *iovmm_debugfs_log_root;
void iovmm_add_log_to_debugfs(struct dentry *debugfs_root,
struct exynos_iommu_event_log *log, const char *name)
{
__sysmmu_add_log_to_debugfs(debugfs_root, &iovmm_debugfs_log_root,
log, name);
}
#endif