/* * omap iommu: debugfs interface * * Copyright (C) 2008-2009 Nokia Corporation * * Written by Hiroshi DOYU <Hiroshi.DOYU@nokia.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/module.h> #include <linux/err.h> #include <linux/clk.h> #include <linux/io.h> #include <linux/slab.h> #include <linux/uaccess.h> #include <linux/platform_device.h> #include <linux/debugfs.h> #include <linux/omap-iommu.h> #include <linux/platform_data/iommu-omap.h> #include "omap-iopgtable.h" #include "omap-iommu.h" #define MAXCOLUMN 100 /* for short messages */ static DEFINE_MUTEX(iommu_debug_lock); static struct dentry *iommu_debug_root; static ssize_t debug_read_ver(struct file *file, char __user *userbuf, size_t count, loff_t *ppos) { u32 ver = omap_iommu_arch_version(); char buf[MAXCOLUMN], *p = buf; p += sprintf(p, "H/W version: %d.%d\n", (ver >> 4) & 0xf , ver & 0xf); return simple_read_from_buffer(userbuf, count, ppos, buf, p - buf); } static ssize_t debug_read_regs(struct file *file, char __user *userbuf, size_t count, loff_t *ppos) { struct device *dev = file->private_data; struct omap_iommu *obj = dev_to_omap_iommu(dev); char *p, *buf; ssize_t bytes; buf = kmalloc(count, GFP_KERNEL); if (!buf) return -ENOMEM; p = buf; mutex_lock(&iommu_debug_lock); bytes = omap_iommu_dump_ctx(obj, p, count); bytes = simple_read_from_buffer(userbuf, count, ppos, buf, bytes); mutex_unlock(&iommu_debug_lock); kfree(buf); return bytes; } static ssize_t debug_read_tlb(struct file *file, char __user *userbuf, size_t count, loff_t *ppos) { struct device *dev = file->private_data; struct omap_iommu *obj = dev_to_omap_iommu(dev); char *p, *buf; ssize_t bytes, rest; buf = kmalloc(count, GFP_KERNEL); if (!buf) return -ENOMEM; p = buf; mutex_lock(&iommu_debug_lock); p += sprintf(p, "%8s %8s\n", "cam:", "ram:"); p += sprintf(p, "-----------------------------------------\n"); rest = count - (p - buf); p += omap_dump_tlb_entries(obj, p, rest); bytes = simple_read_from_buffer(userbuf, count, ppos, buf, p - buf); mutex_unlock(&iommu_debug_lock); kfree(buf); return bytes; } static ssize_t debug_write_pagetable(struct file *file, const char __user *userbuf, size_t count, loff_t *ppos) { struct iotlb_entry e; struct cr_regs cr; int err; struct device *dev = file->private_data; struct omap_iommu *obj = dev_to_omap_iommu(dev); char buf[MAXCOLUMN], *p = buf; count = min(count, sizeof(buf)); mutex_lock(&iommu_debug_lock); if (copy_from_user(p, userbuf, count)) { mutex_unlock(&iommu_debug_lock); return -EFAULT; } sscanf(p, "%x %x", &cr.cam, &cr.ram); if (!cr.cam || !cr.ram) { mutex_unlock(&iommu_debug_lock); return -EINVAL; } omap_iotlb_cr_to_e(&cr, &e); err = omap_iopgtable_store_entry(obj, &e); if (err) dev_err(obj->dev, "%s: fail to store cr\n", __func__); mutex_unlock(&iommu_debug_lock); return count; } #define dump_ioptable_entry_one(lv, da, val) \ ({ \ int __err = 0; \ ssize_t bytes; \ const int maxcol = 22; \ const char *str = "%d: %08x %08x\n"; \ bytes = snprintf(p, maxcol, str, lv, da, val); \ p += bytes; \ len -= bytes; \ if (len < maxcol) \ __err = -ENOMEM; \ __err; \ }) static ssize_t dump_ioptable(struct omap_iommu *obj, char *buf, ssize_t len) { int i; u32 *iopgd; char *p = buf; spin_lock(&obj->page_table_lock); iopgd = iopgd_offset(obj, 0); for (i = 0; i < PTRS_PER_IOPGD; i++, iopgd++) { int j, err; u32 *iopte; u32 da; if (!*iopgd) continue; if (!(*iopgd & IOPGD_TABLE)) { da = i << IOPGD_SHIFT; err = dump_ioptable_entry_one(1, da, *iopgd); if (err) goto out; continue; } iopte = iopte_offset(iopgd, 0); for (j = 0; j < PTRS_PER_IOPTE; j++, iopte++) { if (!*iopte) continue; da = (i << IOPGD_SHIFT) + (j << IOPTE_SHIFT); err = dump_ioptable_entry_one(2, da, *iopgd); if (err) goto out; } } out: spin_unlock(&obj->page_table_lock); return p - buf; } static ssize_t debug_read_pagetable(struct file *file, char __user *userbuf, size_t count, loff_t *ppos) { struct device *dev = file->private_data; struct omap_iommu *obj = dev_to_omap_iommu(dev); char *p, *buf; size_t bytes; buf = (char *)__get_free_page(GFP_KERNEL); if (!buf) return -ENOMEM; p = buf; p += sprintf(p, "L: %8s %8s\n", "da:", "pa:"); p += sprintf(p, "-----------------------------------------\n"); mutex_lock(&iommu_debug_lock); bytes = PAGE_SIZE - (p - buf); p += dump_ioptable(obj, p, bytes); bytes = simple_read_from_buffer(userbuf, count, ppos, buf, p - buf); mutex_unlock(&iommu_debug_lock); free_page((unsigned long)buf); return bytes; } static ssize_t debug_read_mmap(struct file *file, char __user *userbuf, size_t count, loff_t *ppos) { struct device *dev = file->private_data; struct omap_iommu *obj = dev_to_omap_iommu(dev); char *p, *buf; struct iovm_struct *tmp; int uninitialized_var(i); ssize_t bytes; buf = (char *)__get_free_page(GFP_KERNEL); if (!buf) return -ENOMEM; p = buf; p += sprintf(p, "%-3s %-8s %-8s %6s %8s\n", "No", "start", "end", "size", "flags"); p += sprintf(p, "-------------------------------------------------\n"); mutex_lock(&iommu_debug_lock); list_for_each_entry(tmp, &obj->mmap, list) { size_t len; const char *str = "%3d %08x-%08x %6x %8x\n"; const int maxcol = 39; len = tmp->da_end - tmp->da_start; p += snprintf(p, maxcol, str, i, tmp->da_start, tmp->da_end, len, tmp->flags); if (PAGE_SIZE - (p - buf) < maxcol) break; i++; } bytes = simple_read_from_buffer(userbuf, count, ppos, buf, p - buf); mutex_unlock(&iommu_debug_lock); free_page((unsigned long)buf); return bytes; } static ssize_t debug_read_mem(struct file *file, char __user *userbuf, size_t count, loff_t *ppos) { struct device *dev = file->private_data; char *p, *buf; struct iovm_struct *area; ssize_t bytes; count = min_t(ssize_t, count, PAGE_SIZE); buf = (char *)__get_free_page(GFP_KERNEL); if (!buf) return -ENOMEM; p = buf; mutex_lock(&iommu_debug_lock); area = omap_find_iovm_area(dev, (u32)ppos); if (!area) { bytes = -EINVAL; goto err_out; } memcpy(p, area->va, count); p += count; bytes = simple_read_from_buffer(userbuf, count, ppos, buf, p - buf); err_out: mutex_unlock(&iommu_debug_lock); free_page((unsigned long)buf); return bytes; } static ssize_t debug_write_mem(struct file *file, const char __user *userbuf, size_t count, loff_t *ppos) { struct device *dev = file->private_data; struct iovm_struct *area; char *p, *buf; count = min_t(size_t, count, PAGE_SIZE); buf = (char *)__get_free_page(GFP_KERNEL); if (!buf) return -ENOMEM; p = buf; mutex_lock(&iommu_debug_lock); if (copy_from_user(p, userbuf, count)) { count = -EFAULT; goto err_out; } area = omap_find_iovm_area(dev, (u32)ppos); if (!area) { count = -EINVAL; goto err_out; } memcpy(area->va, p, count); err_out: mutex_unlock(&iommu_debug_lock); free_page((unsigned long)buf); return count; } #define DEBUG_FOPS(name) \ static const struct file_operations debug_##name##_fops = { \ .open = simple_open, \ .read = debug_read_##name, \ .write = debug_write_##name, \ .llseek = generic_file_llseek, \ }; #define DEBUG_FOPS_RO(name) \ static const struct file_operations debug_##name##_fops = { \ .open = simple_open, \ .read = debug_read_##name, \ .llseek = generic_file_llseek, \ }; DEBUG_FOPS_RO(ver); DEBUG_FOPS_RO(regs); DEBUG_FOPS_RO(tlb); DEBUG_FOPS(pagetable); DEBUG_FOPS_RO(mmap); DEBUG_FOPS(mem); #define __DEBUG_ADD_FILE(attr, mode) \ { \ struct dentry *dent; \ dent = debugfs_create_file(#attr, mode, parent, \ dev, &debug_##attr##_fops); \ if (!dent) \ return -ENOMEM; \ } #define DEBUG_ADD_FILE(name) __DEBUG_ADD_FILE(name, 600) #define DEBUG_ADD_FILE_RO(name) __DEBUG_ADD_FILE(name, 400) static int iommu_debug_register(struct device *dev, void *data) { struct platform_device *pdev = to_platform_device(dev); struct omap_iommu *obj = platform_get_drvdata(pdev); struct omap_iommu_arch_data *arch_data; struct dentry *d, *parent; if (!obj || !obj->dev) return -EINVAL; arch_data = kzalloc(sizeof(*arch_data), GFP_KERNEL); if (!arch_data) return -ENOMEM; arch_data->iommu_dev = obj; dev->archdata.iommu = arch_data; d = debugfs_create_dir(obj->name, iommu_debug_root); if (!d) goto nomem; parent = d; d = debugfs_create_u8("nr_tlb_entries", 400, parent, (u8 *)&obj->nr_tlb_entries); if (!d) goto nomem; DEBUG_ADD_FILE_RO(ver); DEBUG_ADD_FILE_RO(regs); DEBUG_ADD_FILE_RO(tlb); DEBUG_ADD_FILE(pagetable); DEBUG_ADD_FILE_RO(mmap); DEBUG_ADD_FILE(mem); return 0; nomem: kfree(arch_data); return -ENOMEM; } static int iommu_debug_unregister(struct device *dev, void *data) { if (!dev->archdata.iommu) return 0; kfree(dev->archdata.iommu); dev->archdata.iommu = NULL; return 0; } static int __init iommu_debug_init(void) { struct dentry *d; int err; d = debugfs_create_dir("iommu", NULL); if (!d) return -ENOMEM; iommu_debug_root = d; err = omap_foreach_iommu_device(d, iommu_debug_register); if (err) goto err_out; return 0; err_out: debugfs_remove_recursive(iommu_debug_root); return err; } module_init(iommu_debug_init) static void __exit iommu_debugfs_exit(void) { debugfs_remove_recursive(iommu_debug_root); omap_foreach_iommu_device(NULL, iommu_debug_unregister); } module_exit(iommu_debugfs_exit) MODULE_DESCRIPTION("omap iommu: debugfs interface"); MODULE_AUTHOR("Hiroshi DOYU <Hiroshi.DOYU@nokia.com>"); MODULE_LICENSE("GPL v2");