iommu: iommu-debug: Rework CONFIG_IOMMU_DEBUG_TRACKING
If CONFIG_IOMMU_DEBUG_TRACKING=y, a deadlock will occur if userspace writes to the debugfs file "attach": -001|context_switch(inline) -001|__schedule() -002|__preempt_count_sub(inline) -002|schedule() -003|schedule_timeout() -004|do_wait_for_common(inline) -004|__wait_for_common(inline) -004|wait_for_common() -005|wait_for_completion() -006|__synchronize_srcu() -007|synchronize_srcu() -008|debugfs_remove_recursive() -009|iommu_debug_detach_device() -010|__iommu_detach_device() -011|__iommu_group_for_each_dev(inline) -011|__iommu_detach_group() -012|iommu_detach_device() -013|iommu_debug_device_profiling() -014|iommu_debug_device_show() -015|seq_read() -016|full_proxy_read() -017|__vfs_read() -018|vfs_read() Since the files in the attachment directory are rarely used, remove them. Update the data saved by this config option to work properly with iommu groups. Change-Id: I3f0f1d0836f37412915ce3e78d8aefe92dc8d096 Signed-off-by: Patrick Daly <pdaly@codeaurora.org> [lmark@codeaurora.org: deadlock is not an issue on this branch but this change is backported because there are security benefits from removing the iommu debugfs attachments nodes, such as removing register access.] Signed-off-by: Liam Mark <lmark@codeaurora.org>
This commit is contained in:
parent
60be71604a
commit
1c2a08812e
3 changed files with 46 additions and 364 deletions
|
@ -28,6 +28,7 @@
|
|||
#include <linux/dma-mapping.h>
|
||||
#include <asm/cacheflush.h>
|
||||
#include <asm/dma-iommu.h>
|
||||
#include "iommu-debug.h"
|
||||
|
||||
#if defined(CONFIG_IOMMU_DEBUG_TRACKING) || defined(CONFIG_IOMMU_TESTS)
|
||||
|
||||
|
@ -84,371 +85,58 @@ static const char *iommu_debug_attr_to_string(enum iommu_attr attr)
|
|||
|
||||
static DEFINE_MUTEX(iommu_debug_attachments_lock);
|
||||
static LIST_HEAD(iommu_debug_attachments);
|
||||
static struct dentry *debugfs_attachments_dir;
|
||||
|
||||
/*
|
||||
* Each group may have more than one domain; but each domain may
|
||||
* only have one group.
|
||||
* Used by debug tools to display the name of the device(s) associated
|
||||
* with a particular domain.
|
||||
*/
|
||||
struct iommu_debug_attachment {
|
||||
struct iommu_domain *domain;
|
||||
struct device *dev;
|
||||
struct dentry *dentry;
|
||||
struct iommu_group *group;
|
||||
struct list_head list;
|
||||
unsigned long reg_offset;
|
||||
};
|
||||
|
||||
static int iommu_debug_attachment_info_show(struct seq_file *s, void *ignored)
|
||||
{
|
||||
struct iommu_debug_attachment *attach = s->private;
|
||||
phys_addr_t pt_phys;
|
||||
int secure_vmid;
|
||||
|
||||
seq_printf(s, "Domain: 0x%p\n", attach->domain);
|
||||
if (iommu_domain_get_attr(attach->domain, DOMAIN_ATTR_PT_BASE_ADDR,
|
||||
&pt_phys)) {
|
||||
seq_puts(s, "PT_BASE_ADDR: (Unknown)\n");
|
||||
} else {
|
||||
void *pt_virt = phys_to_virt(pt_phys);
|
||||
|
||||
seq_printf(s, "PT_BASE_ADDR: virt=0x%p phys=%pa\n",
|
||||
pt_virt, &pt_phys);
|
||||
}
|
||||
|
||||
seq_puts(s, "SECURE_VMID: ");
|
||||
if (iommu_domain_get_attr(attach->domain,
|
||||
DOMAIN_ATTR_SECURE_VMID,
|
||||
&secure_vmid))
|
||||
seq_puts(s, "(Unknown)\n");
|
||||
else
|
||||
seq_printf(s, "%s (0x%x)\n",
|
||||
msm_secure_vmid_to_string(secure_vmid), secure_vmid);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int iommu_debug_attachment_info_open(struct inode *inode,
|
||||
struct file *file)
|
||||
{
|
||||
return single_open(file, iommu_debug_attachment_info_show,
|
||||
inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations iommu_debug_attachment_info_fops = {
|
||||
.open = iommu_debug_attachment_info_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
static ssize_t iommu_debug_attachment_trigger_fault_write(
|
||||
struct file *file, const char __user *ubuf, size_t count,
|
||||
loff_t *offset)
|
||||
{
|
||||
struct iommu_debug_attachment *attach = file->private_data;
|
||||
unsigned long flags;
|
||||
|
||||
if (kstrtoul_from_user(ubuf, count, 0, &flags)) {
|
||||
pr_err("Invalid flags format\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
iommu_trigger_fault(attach->domain, flags);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct file_operations
|
||||
iommu_debug_attachment_trigger_fault_fops = {
|
||||
.open = simple_open,
|
||||
.write = iommu_debug_attachment_trigger_fault_write,
|
||||
};
|
||||
|
||||
static ssize_t iommu_debug_attachment_reg_offset_write(
|
||||
struct file *file, const char __user *ubuf, size_t count,
|
||||
loff_t *offset)
|
||||
{
|
||||
struct iommu_debug_attachment *attach = file->private_data;
|
||||
unsigned long reg_offset;
|
||||
|
||||
if (kstrtoul_from_user(ubuf, count, 0, ®_offset)) {
|
||||
pr_err("Invalid reg_offset format\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
attach->reg_offset = reg_offset;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct file_operations iommu_debug_attachment_reg_offset_fops = {
|
||||
.open = simple_open,
|
||||
.write = iommu_debug_attachment_reg_offset_write,
|
||||
};
|
||||
|
||||
static ssize_t iommu_debug_attachment_reg_read_read(
|
||||
struct file *file, char __user *ubuf, size_t count, loff_t *offset)
|
||||
{
|
||||
struct iommu_debug_attachment *attach = file->private_data;
|
||||
unsigned long val;
|
||||
char *val_str;
|
||||
ssize_t val_str_len;
|
||||
|
||||
if (*offset)
|
||||
return 0;
|
||||
|
||||
val = iommu_reg_read(attach->domain, attach->reg_offset);
|
||||
val_str = kasprintf(GFP_KERNEL, "0x%lx\n", val);
|
||||
if (!val_str)
|
||||
return -ENOMEM;
|
||||
val_str_len = strlen(val_str);
|
||||
|
||||
if (copy_to_user(ubuf, val_str, val_str_len)) {
|
||||
pr_err("copy_to_user failed\n");
|
||||
val_str_len = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
*offset = 1; /* non-zero means we're done */
|
||||
|
||||
out:
|
||||
kfree(val_str);
|
||||
return val_str_len;
|
||||
}
|
||||
|
||||
static const struct file_operations iommu_debug_attachment_reg_read_fops = {
|
||||
.open = simple_open,
|
||||
.read = iommu_debug_attachment_reg_read_read,
|
||||
};
|
||||
|
||||
static ssize_t iommu_debug_attachment_reg_write_write(
|
||||
struct file *file, const char __user *ubuf, size_t count,
|
||||
loff_t *offset)
|
||||
{
|
||||
struct iommu_debug_attachment *attach = file->private_data;
|
||||
unsigned long val;
|
||||
|
||||
if (kstrtoul_from_user(ubuf, count, 0, &val)) {
|
||||
pr_err("Invalid val format\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
iommu_reg_write(attach->domain, attach->reg_offset, val);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct file_operations iommu_debug_attachment_reg_write_fops = {
|
||||
.open = simple_open,
|
||||
.write = iommu_debug_attachment_reg_write_write,
|
||||
};
|
||||
|
||||
/* should be called with iommu_debug_attachments_lock locked */
|
||||
static int iommu_debug_attach_add_debugfs(
|
||||
struct iommu_debug_attachment *attach)
|
||||
{
|
||||
const char *attach_name;
|
||||
struct device *dev = attach->dev;
|
||||
struct iommu_domain *domain = attach->domain;
|
||||
int is_dynamic;
|
||||
|
||||
if (iommu_domain_get_attr(domain, DOMAIN_ATTR_DYNAMIC, &is_dynamic))
|
||||
is_dynamic = 0;
|
||||
|
||||
if (is_dynamic) {
|
||||
uuid_le uuid;
|
||||
|
||||
uuid_le_gen(&uuid);
|
||||
attach_name = kasprintf(GFP_KERNEL, "%s-%pUl", dev_name(dev),
|
||||
uuid.b);
|
||||
if (!attach_name)
|
||||
return -ENOMEM;
|
||||
} else {
|
||||
attach_name = dev_name(dev);
|
||||
}
|
||||
|
||||
attach->dentry = debugfs_create_dir(attach_name,
|
||||
debugfs_attachments_dir);
|
||||
if (!attach->dentry) {
|
||||
pr_err("Couldn't create iommu/attachments/%s debugfs directory for domain 0x%p\n",
|
||||
attach_name, domain);
|
||||
if (is_dynamic)
|
||||
kfree(attach_name);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (is_dynamic)
|
||||
kfree(attach_name);
|
||||
|
||||
if (!debugfs_create_file(
|
||||
"info", S_IRUSR, attach->dentry, attach,
|
||||
&iommu_debug_attachment_info_fops)) {
|
||||
pr_err("Couldn't create iommu/attachments/%s/info debugfs file for domain 0x%p\n",
|
||||
dev_name(dev), domain);
|
||||
goto err_rmdir;
|
||||
}
|
||||
|
||||
if (!debugfs_create_file(
|
||||
"trigger_fault", S_IRUSR, attach->dentry, attach,
|
||||
&iommu_debug_attachment_trigger_fault_fops)) {
|
||||
pr_err("Couldn't create iommu/attachments/%s/trigger_fault debugfs file for domain 0x%p\n",
|
||||
dev_name(dev), domain);
|
||||
goto err_rmdir;
|
||||
}
|
||||
|
||||
if (!debugfs_create_file(
|
||||
"reg_offset", S_IRUSR, attach->dentry, attach,
|
||||
&iommu_debug_attachment_reg_offset_fops)) {
|
||||
pr_err("Couldn't create iommu/attachments/%s/reg_offset debugfs file for domain 0x%p\n",
|
||||
dev_name(dev), domain);
|
||||
goto err_rmdir;
|
||||
}
|
||||
|
||||
if (!debugfs_create_file(
|
||||
"reg_read", S_IRUSR, attach->dentry, attach,
|
||||
&iommu_debug_attachment_reg_read_fops)) {
|
||||
pr_err("Couldn't create iommu/attachments/%s/reg_read debugfs file for domain 0x%p\n",
|
||||
dev_name(dev), domain);
|
||||
goto err_rmdir;
|
||||
}
|
||||
|
||||
if (!debugfs_create_file(
|
||||
"reg_write", S_IRUSR, attach->dentry, attach,
|
||||
&iommu_debug_attachment_reg_write_fops)) {
|
||||
pr_err("Couldn't create iommu/attachments/%s/reg_write debugfs file for domain 0x%p\n",
|
||||
dev_name(dev), domain);
|
||||
goto err_rmdir;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_rmdir:
|
||||
debugfs_remove_recursive(attach->dentry);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
void iommu_debug_domain_add(struct iommu_domain *domain)
|
||||
{
|
||||
struct iommu_debug_attachment *attach;
|
||||
|
||||
mutex_lock(&iommu_debug_attachments_lock);
|
||||
|
||||
attach = kmalloc(sizeof(*attach), GFP_KERNEL);
|
||||
if (!attach)
|
||||
goto out_unlock;
|
||||
|
||||
attach->domain = domain;
|
||||
attach->dev = NULL;
|
||||
list_add(&attach->list, &iommu_debug_attachments);
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&iommu_debug_attachments_lock);
|
||||
}
|
||||
|
||||
void iommu_debug_domain_remove(struct iommu_domain *domain)
|
||||
{
|
||||
struct iommu_debug_attachment *it;
|
||||
|
||||
mutex_lock(&iommu_debug_attachments_lock);
|
||||
list_for_each_entry(it, &iommu_debug_attachments, list)
|
||||
if (it->domain == domain && it->dev == NULL)
|
||||
break;
|
||||
|
||||
if (&it->list == &iommu_debug_attachments) {
|
||||
WARN(1, "Couldn't find debug attachment for domain=0x%p",
|
||||
domain);
|
||||
} else {
|
||||
list_del(&it->list);
|
||||
kfree(it);
|
||||
}
|
||||
mutex_unlock(&iommu_debug_attachments_lock);
|
||||
}
|
||||
|
||||
void iommu_debug_attach_device(struct iommu_domain *domain,
|
||||
struct device *dev)
|
||||
{
|
||||
struct iommu_debug_attachment *attach;
|
||||
struct iommu_group *group;
|
||||
|
||||
group = iommu_group_get(dev);
|
||||
if (!group)
|
||||
return;
|
||||
|
||||
attach = kzalloc(sizeof(*attach), GFP_KERNEL);
|
||||
if (!attach)
|
||||
return;
|
||||
|
||||
attach->domain = domain;
|
||||
attach->group = group;
|
||||
INIT_LIST_HEAD(&attach->list);
|
||||
|
||||
mutex_lock(&iommu_debug_attachments_lock);
|
||||
list_add(&attach->list, &iommu_debug_attachments);
|
||||
mutex_unlock(&iommu_debug_attachments_lock);
|
||||
}
|
||||
|
||||
list_for_each_entry(attach, &iommu_debug_attachments, list)
|
||||
if (attach->domain == domain && attach->dev == NULL)
|
||||
break;
|
||||
void iommu_debug_domain_remove(struct iommu_domain *domain)
|
||||
{
|
||||
struct iommu_debug_attachment *it, *tmp;
|
||||
|
||||
if (&attach->list == &iommu_debug_attachments) {
|
||||
WARN(1, "Couldn't find debug attachment for domain=0x%p dev=%s",
|
||||
domain, dev_name(dev));
|
||||
} else {
|
||||
attach->dev = dev;
|
||||
|
||||
/*
|
||||
* we might not init until after other drivers start calling
|
||||
* iommu_attach_device. Only set up the debugfs nodes if we've
|
||||
* already init'd to avoid polluting the top-level debugfs
|
||||
* directory (by calling debugfs_create_dir with a NULL
|
||||
* parent). These will be flushed out later once we init.
|
||||
*/
|
||||
|
||||
if (debugfs_attachments_dir)
|
||||
iommu_debug_attach_add_debugfs(attach);
|
||||
mutex_lock(&iommu_debug_attachments_lock);
|
||||
list_for_each_entry_safe(it, tmp, &iommu_debug_attachments, list) {
|
||||
if (it->domain != domain)
|
||||
continue;
|
||||
list_del(&it->list);
|
||||
iommu_group_put(it->group);
|
||||
kfree(it);
|
||||
}
|
||||
|
||||
mutex_unlock(&iommu_debug_attachments_lock);
|
||||
}
|
||||
|
||||
void iommu_debug_detach_device(struct iommu_domain *domain,
|
||||
struct device *dev)
|
||||
{
|
||||
struct iommu_debug_attachment *it;
|
||||
|
||||
mutex_lock(&iommu_debug_attachments_lock);
|
||||
list_for_each_entry(it, &iommu_debug_attachments, list)
|
||||
if (it->domain == domain && it->dev == dev)
|
||||
break;
|
||||
|
||||
if (&it->list == &iommu_debug_attachments) {
|
||||
WARN(1, "Couldn't find debug attachment for domain=0x%p dev=%s",
|
||||
domain, dev_name(dev));
|
||||
} else {
|
||||
/*
|
||||
* Just remove debugfs entry and mark dev as NULL on
|
||||
* iommu_detach call. We would remove the actual
|
||||
* attachment entry from the list only on domain_free call.
|
||||
* This is to ensure we keep track of unattached domains too.
|
||||
*/
|
||||
|
||||
debugfs_remove_recursive(it->dentry);
|
||||
it->dev = NULL;
|
||||
}
|
||||
mutex_unlock(&iommu_debug_attachments_lock);
|
||||
}
|
||||
|
||||
static int iommu_debug_init_tracking(void)
|
||||
{
|
||||
int ret = 0;
|
||||
struct iommu_debug_attachment *attach;
|
||||
|
||||
mutex_lock(&iommu_debug_attachments_lock);
|
||||
debugfs_attachments_dir = debugfs_create_dir("attachments",
|
||||
iommu_debugfs_top);
|
||||
if (!debugfs_attachments_dir) {
|
||||
pr_err("Couldn't create iommu/attachments debugfs directory\n");
|
||||
ret = -ENODEV;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
/* set up debugfs entries for attachments made during early boot */
|
||||
list_for_each_entry(attach, &iommu_debug_attachments, list)
|
||||
if (attach->dev)
|
||||
iommu_debug_attach_add_debugfs(attach);
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&iommu_debug_attachments_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void iommu_debug_destroy_tracking(void)
|
||||
{
|
||||
debugfs_remove_recursive(debugfs_attachments_dir);
|
||||
}
|
||||
#else
|
||||
static inline int iommu_debug_init_tracking(void) { return 0; }
|
||||
static inline void iommu_debug_destroy_tracking(void) { }
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_IOMMU_TESTS
|
||||
|
@ -2625,9 +2313,6 @@ static struct platform_driver iommu_debug_driver = {
|
|||
|
||||
static int iommu_debug_init(void)
|
||||
{
|
||||
if (iommu_debug_init_tracking())
|
||||
return -ENODEV;
|
||||
|
||||
if (iommu_debug_init_tests())
|
||||
return -ENODEV;
|
||||
|
||||
|
@ -2637,7 +2322,6 @@ static int iommu_debug_init(void)
|
|||
static void iommu_debug_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&iommu_debug_driver);
|
||||
iommu_debug_destroy_tracking();
|
||||
iommu_debug_destroy_tests();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,21 @@
|
|||
/* Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef IOMMU_DEBUG_H
|
||||
#define IOMMU_DEBUG_H
|
||||
|
||||
#ifdef CONFIG_IOMMU_DEBUG_TRACKING
|
||||
|
||||
void iommu_debug_attach_device(struct iommu_domain *domain, struct device *dev);
|
||||
void iommu_debug_detach_device(struct iommu_domain *domain, struct device *dev);
|
||||
void iommu_debug_domain_add(struct iommu_domain *domain);
|
||||
void iommu_debug_domain_remove(struct iommu_domain *domain);
|
||||
|
||||
#else /* !CONFIG_IOMMU_DEBUG_TRACKING */
|
||||
|
@ -15,15 +25,6 @@ static inline void iommu_debug_attach_device(struct iommu_domain *domain,
|
|||
{
|
||||
}
|
||||
|
||||
static inline void iommu_debug_detach_device(struct iommu_domain *domain,
|
||||
struct device *dev)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void iommu_debug_domain_add(struct iommu_domain *domain)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void iommu_debug_domain_remove(struct iommu_domain *domain)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -1118,8 +1118,6 @@ static struct iommu_domain *__iommu_domain_alloc(struct bus_type *bus,
|
|||
domain->ops = bus->iommu_ops;
|
||||
domain->type = type;
|
||||
|
||||
iommu_debug_domain_add(domain);
|
||||
|
||||
return domain;
|
||||
}
|
||||
|
||||
|
@ -1186,7 +1184,6 @@ static void __iommu_detach_device(struct iommu_domain *domain,
|
|||
if (unlikely(domain->ops->detach_dev == NULL))
|
||||
return;
|
||||
|
||||
iommu_debug_detach_device(domain, dev);
|
||||
domain->ops->detach_dev(domain, dev);
|
||||
trace_detach_device_from_domain(dev);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue