204 lines
5.0 KiB
C
Executable File
204 lines
5.0 KiB
C
Executable File
/*
|
|
* 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
|