Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 65ae6d7b authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "iommu/iommu-debug: Add validation support for per-buffer coherent mappings"

parents 4a4dd0ec 7a0b4bb3
Loading
Loading
Loading
Loading
+21 −0
Original line number Diff line number Diff line
@@ -548,6 +548,9 @@ static void arm_smmu_unassign_table(struct arm_smmu_domain *smmu_domain);
static int arm_smmu_arch_init(struct arm_smmu_device *smmu);
static void arm_smmu_arch_device_reset(struct arm_smmu_device *smmu);

static uint64_t arm_smmu_iova_to_pte(struct iommu_domain *domain,
				    dma_addr_t iova);

static int arm_smmu_enable_s1_translations(struct arm_smmu_domain *smmu_domain);

static struct arm_smmu_domain *to_smmu_domain(struct iommu_domain *dom)
@@ -2189,6 +2192,23 @@ static int arm_smmu_map(struct iommu_domain *domain, unsigned long iova,
	return ret;
}

static uint64_t arm_smmu_iova_to_pte(struct iommu_domain *domain,
	      dma_addr_t iova)
{
	uint64_t ret;
	unsigned long flags;
	struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
	struct io_pgtable_ops *ops = smmu_domain->pgtbl_ops;

	if (!ops)
		return 0;

	spin_lock_irqsave(&smmu_domain->pgtbl_lock, flags);
	ret = ops->iova_to_pte(ops, iova);
	spin_unlock_irqrestore(&smmu_domain->pgtbl_lock, flags);
	return ret;
}

static size_t arm_smmu_unmap(struct iommu_domain *domain, unsigned long iova,
			     size_t size)
{
@@ -2976,6 +2996,7 @@ static struct iommu_ops arm_smmu_ops = {
	.enable_config_clocks	= arm_smmu_enable_config_clocks,
	.disable_config_clocks	= arm_smmu_disable_config_clocks,
	.is_iova_coherent	= arm_smmu_is_iova_coherent,
	.iova_to_pte = arm_smmu_iova_to_pte,
};

#define IMPL_DEF1_MICRO_MMU_CTRL	0
+14 −0
Original line number Diff line number Diff line
@@ -854,6 +854,19 @@ static int arm_lpae_iova_to_pte(struct arm_lpae_io_pgtable *data,
	return 0;
}

static uint64_t arm_lpae_iova_get_pte(struct io_pgtable_ops *ops,
					 unsigned long iova)
{
	struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops);
	arm_lpae_iopte pte;
	int lvl;

	if (!arm_lpae_iova_to_pte(data, iova, &lvl, &pte))
		return pte;

	return 0;
}

static phys_addr_t arm_lpae_iova_to_phys(struct io_pgtable_ops *ops,
					 unsigned long iova)
{
@@ -983,6 +996,7 @@ arm_lpae_alloc_pgtable(struct io_pgtable_cfg *cfg)
		.unmap		= arm_lpae_unmap,
		.iova_to_phys	= arm_lpae_iova_to_phys,
		.is_iova_coherent = arm_lpae_is_iova_coherent,
		.iova_to_pte	= arm_lpae_iova_get_pte,
	};

	return data;
+2 −0
Original line number Diff line number Diff line
@@ -149,6 +149,8 @@ struct io_pgtable_ops {
				    unsigned long iova);
	bool (*is_iova_coherent)(struct io_pgtable_ops *ops,
				unsigned long iova);
	uint64_t (*iova_to_pte)(struct io_pgtable_ops *ops,
		    unsigned long iova);

};

+536 −0
Original line number Diff line number Diff line
@@ -154,6 +154,7 @@ void iommu_debug_domain_remove(struct iommu_domain *domain)
static LIST_HEAD(iommu_debug_devices);
static struct dentry *debugfs_tests_dir;
static u32 iters_per_op = 1;
static void *test_virt_addr;

struct iommu_debug_device {
	struct device *dev;
@@ -1207,6 +1208,68 @@ static int iommu_debug_attach_do_attach(struct iommu_debug_device *ddev,
	return -EIO;
}

static ssize_t __iommu_debug_dma_attach_write(struct file *file,
					  const char __user *ubuf,
					  size_t count, loff_t *offset)
{
	struct iommu_debug_device *ddev = file->private_data;
	struct device *dev = ddev->dev;
	struct dma_iommu_mapping *dma_mapping;
	ssize_t retval = -EINVAL;
	int val;

	if (kstrtoint_from_user(ubuf, count, 0, &val)) {
		pr_err("Invalid format. Expected a hex or decimal integer");
		retval = -EFAULT;
		goto out;
	}

	if (val) {
		if (dev->archdata.mapping)
			if (dev->archdata.mapping->domain) {
				pr_err("Already attached.\n");
				retval = -EINVAL;
				goto out;
			}
		if (WARN(dev->archdata.iommu,
			"Attachment tracking out of sync with device\n")) {
			retval = -EINVAL;
			goto out;
		}

		dma_mapping = arm_iommu_create_mapping(&platform_bus_type, 0,
				(SZ_1G * 4ULL));

		if (!dma_mapping)
			goto out;

		if (arm_iommu_attach_device(dev, dma_mapping))
			goto out_release_mapping;
		pr_err("Attached\n");
	} else {
		if (!dev->archdata.mapping) {
			pr_err("No mapping. Did you already attach?\n");
			retval = -EINVAL;
			goto out;
		}
		if (!dev->archdata.mapping->domain) {
			pr_err("No domain. Did you already attach?\n");
			retval = -EINVAL;
			goto out;
		}
		arm_iommu_detach_device(dev);
		arm_iommu_release_mapping(dev->archdata.mapping);
		pr_err("Detached\n");
	}
	retval = count;
	return retval;

out_release_mapping:
	arm_iommu_release_mapping(dma_mapping);
out:
	return retval;
}

static ssize_t __iommu_debug_attach_write(struct file *file,
					  const char __user *ubuf,
					  size_t count, loff_t *offset,
@@ -1260,6 +1323,81 @@ static ssize_t __iommu_debug_attach_write(struct file *file,
	return retval;
}

static ssize_t iommu_debug_dma_attach_write(struct file *file,
					  const char __user *ubuf,
					  size_t count, loff_t *offset)
{
	return __iommu_debug_dma_attach_write(file, ubuf, count, offset);

}

static ssize_t iommu_debug_dma_attach_read(struct file *file, char __user *ubuf,
				       size_t count, loff_t *offset)
{
	struct iommu_debug_device *ddev = file->private_data;
	struct device *dev = ddev->dev;
	char c[2];

	if (*offset)
		return 0;

	if (!dev->archdata.mapping)
		c[0] = '0';
	else
		c[0] = dev->archdata.mapping->domain ? '1' : '0';

	c[1] = '\n';
	if (copy_to_user(ubuf, &c, 2)) {
		pr_err("copy_to_user failed\n");
		return -EFAULT;
	}
	*offset = 1;		/* non-zero means we're done */

	return 2;
}

static const struct file_operations iommu_debug_dma_attach_fops = {
	.open	= simple_open,
	.write	= iommu_debug_dma_attach_write,
	.read	= iommu_debug_dma_attach_read,
};

static ssize_t iommu_debug_test_virt_addr_read(struct file *file,
					       char __user *ubuf,
					       size_t count, loff_t *offset)
{
	char buf[100];
	ssize_t retval;
	size_t buflen;
	int buf_len = sizeof(buf);

	if (*offset)
		return 0;

	memset(buf, 0, buf_len);

	if (!test_virt_addr)
		strlcpy(buf, "FAIL\n", buf_len);
	else
		snprintf(buf, buf_len, "0x%pK\n", test_virt_addr);

	buflen = strlen(buf);
	if (copy_to_user(ubuf, buf, buflen)) {
		pr_err("Couldn't copy_to_user\n");
		retval = -EFAULT;
	} else {
		*offset = 1;	/* non-zero means we're done */
		retval = buflen;
	}

	return retval;
}

static const struct file_operations iommu_debug_test_virt_addr_fops = {
	.open	= simple_open,
	.read	= iommu_debug_test_virt_addr_read,
};

static ssize_t iommu_debug_attach_write(struct file *file,
					  const char __user *ubuf,
					  size_t count, loff_t *offset)
@@ -1309,6 +1447,75 @@ static const struct file_operations iommu_debug_secure_attach_fops = {
	.read	= iommu_debug_attach_read,
};

static ssize_t iommu_debug_pte_write(struct file *file,
				      const char __user *ubuf,
				      size_t count, loff_t *offset)
{
	struct iommu_debug_device *ddev = file->private_data;
	dma_addr_t iova;

	if (kstrtox_from_user(ubuf, count, 0, &iova)) {
		pr_err("Invalid format for iova\n");
		ddev->iova = 0;
		return -EINVAL;
	}

	ddev->iova = iova;
	pr_err("Saved iova=%pa for future PTE commands\n", &iova);
	return count;
}


static ssize_t iommu_debug_pte_read(struct file *file, char __user *ubuf,
				     size_t count, loff_t *offset)
{
	struct iommu_debug_device *ddev = file->private_data;
	struct device *dev = ddev->dev;
	uint64_t pte;
	char buf[100];
	ssize_t retval;
	size_t buflen;

	if (!dev->archdata.mapping) {
		pr_err("No mapping. Did you already attach?\n");
		return -EINVAL;
	}
	if (!dev->archdata.mapping->domain) {
		pr_err("No domain. Did you already attach?\n");
		return -EINVAL;
	}

	if (*offset)
		return 0;

	memset(buf, 0, sizeof(buf));

	pte = iommu_iova_to_pte(dev->archdata.mapping->domain,
			ddev->iova);

	if (!pte)
		strlcpy(buf, "FAIL\n", sizeof(buf));
	else
		snprintf(buf, sizeof(buf), "pte=%016llx\n", pte);

	buflen = strlen(buf);
	if (copy_to_user(ubuf, buf, buflen)) {
		pr_err("Couldn't copy_to_user\n");
		retval = -EFAULT;
	} else {
		*offset = 1;	/* non-zero means we're done */
		retval = buflen;
	}

	return retval;
}

static const struct file_operations iommu_debug_pte_fops = {
	.open	= simple_open,
	.write	= iommu_debug_pte_write,
	.read	= iommu_debug_pte_read,
};

static ssize_t iommu_debug_atos_write(struct file *file,
				      const char __user *ubuf,
				      size_t count, loff_t *offset)
@@ -1370,6 +1577,55 @@ static const struct file_operations iommu_debug_atos_fops = {
	.read	= iommu_debug_atos_read,
};

static ssize_t iommu_debug_dma_atos_read(struct file *file, char __user *ubuf,
				     size_t count, loff_t *offset)
{
	struct iommu_debug_device *ddev = file->private_data;
	struct device *dev = ddev->dev;
	phys_addr_t phys;
	char buf[100];
	ssize_t retval;
	size_t buflen;

	if (!dev->archdata.mapping) {
		pr_err("No mapping. Did you already attach?\n");
		return -EINVAL;
	}
	if (!dev->archdata.mapping->domain) {
		pr_err("No domain. Did you already attach?\n");
		return -EINVAL;
	}

	if (*offset)
		return 0;

	memset(buf, 0, sizeof(buf));

	phys = iommu_iova_to_phys_hard(dev->archdata.mapping->domain,
			ddev->iova);
	if (!phys)
		strlcpy(buf, "FAIL\n", sizeof(buf));
	else
		snprintf(buf, sizeof(buf), "%pa\n", &phys);

	buflen = strlen(buf);
	if (copy_to_user(ubuf, buf, buflen)) {
		pr_err("Couldn't copy_to_user\n");
		retval = -EFAULT;
	} else {
		*offset = 1;	/* non-zero means we're done */
		retval = buflen;
	}

	return retval;
}

static const struct file_operations iommu_debug_dma_atos_fops = {
	.open	= simple_open,
	.write	= iommu_debug_atos_write,
	.read	= iommu_debug_dma_atos_read,
};

static ssize_t iommu_debug_map_write(struct file *file, const char __user *ubuf,
				     size_t count, loff_t *offset)
{
@@ -1450,6 +1706,156 @@ static const struct file_operations iommu_debug_map_fops = {
	.write	= iommu_debug_map_write,
};

/*
 * Performs DMA mapping of a given virtual address and size to an iova address.
 * User input format: (addr,len,dma attr) where dma attr is:
 *				0: normal mapping
 *				1: force coherent mapping
 *				2: force non-cohernet mapping
 */
static ssize_t iommu_debug_dma_map_write(struct file *file,
		const char __user *ubuf, size_t count, loff_t *offset)
{
	ssize_t retval = -EINVAL;
	int ret;
	char *comma1, *comma2;
	char buf[100];
	unsigned long addr;
	void *v_addr;
	dma_addr_t iova;
	size_t size;
	unsigned int attr;
	unsigned long dma_attrs;
	struct iommu_debug_device *ddev = file->private_data;
	struct device *dev = ddev->dev;

	if (count >= sizeof(buf)) {
		pr_err("Value too large\n");
		return -EINVAL;
	}

	if (!dev->archdata.mapping) {
		pr_err("No mapping. Did you already attach?\n");
		retval = -EINVAL;
		goto out;
	}
	if (!dev->archdata.mapping->domain) {
		pr_err("No domain. Did you already attach?\n");
		retval = -EINVAL;
		goto out;
	}

	memset(buf, 0, sizeof(buf));

	if (copy_from_user(buf, ubuf, count)) {
		pr_err("Couldn't copy from user\n");
		retval = -EFAULT;
		goto out;
	}

	comma1 = strnchr(buf, count, ',');
	if (!comma1)
		goto invalid_format;

	comma2 = strnchr(comma1 + 1, count, ',');
	if (!comma2)
		goto invalid_format;

	*comma1 = *comma2 = '\0';

	if (kstrtoul(buf, 0, &addr))
		goto invalid_format;
	v_addr = (void *)addr;

	if (kstrtosize_t(comma1 + 1, 0, &size))
		goto invalid_format;

	if (kstrtouint(comma2 + 1, 0, &attr))
		goto invalid_format;

	if (v_addr < test_virt_addr || v_addr > (test_virt_addr + SZ_1M - 1))
		goto invalid_addr;

	if (attr == 0)
		dma_attrs = 0;
	else if (attr == 1)
		dma_attrs = DMA_ATTR_FORCE_COHERENT;
	else if (attr == 2)
		dma_attrs = DMA_ATTR_FORCE_NON_COHERENT;
	else
		goto invalid_format;

	iova = dma_map_single_attrs(dev, v_addr, size,
					DMA_TO_DEVICE, dma_attrs);

	if (dma_mapping_error(dev, iova)) {
		pr_err("Failed to perform dma_map_single\n");
		ret = -EINVAL;
		goto out;
	}

	retval = count;
	pr_err("Mapped 0x%p to %pa (len=0x%zx)\n",
			v_addr, &iova, size);
	ddev->iova = iova;
		pr_err("Saved iova=%pa for future PTE commands\n", &iova);
out:
	return retval;

invalid_format:
	pr_err("Invalid format. Expected: addr,len,dma attr where 'dma attr' is\n0: normal mapping\n1: force coherent\n2: force non-cohernet\n");
	return retval;

invalid_addr:
	pr_err("Invalid addr given! Address should be within 1MB size from start addr returned by doing 'cat test_virt_addr'.\n");
	return retval;
}

static ssize_t iommu_debug_dma_map_read(struct file *file, char __user *ubuf,
	     size_t count, loff_t *offset)
{
	struct iommu_debug_device *ddev = file->private_data;
	struct device *dev = ddev->dev;
	char buf[100];
	ssize_t retval;
	size_t buflen;
	dma_addr_t iova;

	if (!dev->archdata.mapping) {
		pr_err("No mapping. Did you already attach?\n");
		return -EINVAL;
	}
	if (!dev->archdata.mapping->domain) {
		pr_err("No domain. Did you already attach?\n");
		return -EINVAL;
	}

	if (*offset)
		return 0;

	memset(buf, 0, sizeof(buf));

	iova = ddev->iova;
	snprintf(buf, sizeof(buf), "%pa\n", &iova);

	buflen = strlen(buf);
	if (copy_to_user(ubuf, buf, buflen)) {
		pr_err("Couldn't copy_to_user\n");
		retval = -EFAULT;
	} else {
		*offset = 1;	/* non-zero means we're done */
		retval = buflen;
	}

	return retval;
}

static const struct file_operations iommu_debug_dma_map_fops = {
	.open	= simple_open,
	.write	= iommu_debug_dma_map_write,
	.read	= iommu_debug_dma_map_read,
};

static ssize_t iommu_debug_unmap_write(struct file *file,
				       const char __user *ubuf,
				       size_t count, loff_t *offset)
@@ -1515,6 +1921,89 @@ static const struct file_operations iommu_debug_unmap_fops = {
	.write	= iommu_debug_unmap_write,
};

static ssize_t iommu_debug_dma_unmap_write(struct file *file,
				       const char __user *ubuf,
				       size_t count, loff_t *offset)
{
	ssize_t retval = 0;
	char *comma1, *comma2;
	char buf[100];
	size_t size;
	unsigned int attr;
	dma_addr_t iova;
	unsigned long dma_attrs;
	struct iommu_debug_device *ddev = file->private_data;
	struct device *dev = ddev->dev;

	if (count >= sizeof(buf)) {
		pr_err("Value too large\n");
		return -EINVAL;
	}

	if (!dev->archdata.mapping) {
		pr_err("No mapping. Did you already attach?\n");
		retval = -EINVAL;
		goto out;
	}
	if (!dev->archdata.mapping->domain) {
		pr_err("No domain. Did you already attach?\n");
		retval = -EINVAL;
		goto out;
	}

	memset(buf, 0, sizeof(buf));

	if (copy_from_user(buf, ubuf, count)) {
		pr_err("Couldn't copy from user\n");
		retval = -EFAULT;
		goto out;
	}

	comma1 = strnchr(buf, count, ',');
	if (!comma1)
		goto invalid_format;

	comma2 = strnchr(comma1 + 1, count, ',');
	if (!comma2)
		goto invalid_format;

	*comma1 = *comma2 = '\0';

	if (kstrtoux(buf, 0, &iova))
		goto invalid_format;

	if (kstrtosize_t(comma1 + 1, 0, &size))
		goto invalid_format;

	if (kstrtouint(comma2 + 1, 0, &attr))
		goto invalid_format;

	if (attr == 0)
		dma_attrs = 0;
	else if (attr == 1)
		dma_attrs = DMA_ATTR_FORCE_COHERENT;
	else if (attr == 2)
		dma_attrs = DMA_ATTR_FORCE_NON_COHERENT;
	else
		goto invalid_format;

	dma_unmap_single_attrs(dev, iova, size, DMA_TO_DEVICE, dma_attrs);

	retval = count;
	pr_err("Unmapped %pa (len=0x%zx)\n", &iova, size);
out:
	return retval;

invalid_format:
	pr_err("Invalid format. Expected: iova,len, dma attr\n");
	return retval;
}

static const struct file_operations iommu_debug_dma_unmap_fops = {
	.open	= simple_open,
	.write	= iommu_debug_dma_unmap_write,
};

static ssize_t iommu_debug_config_clocks_write(struct file *file,
					       const char __user *ubuf,
					       size_t count, loff_t *offset)
@@ -1624,6 +2113,13 @@ static int snarf_iommu_devices(struct device *dev, void *ignored)
		goto err_rmdir;
	}

	if (!debugfs_create_file("test_virt_addr", 0400, dir, ddev,
				&iommu_debug_test_virt_addr_fops)) {
		pr_err("Couldn't create iommu/devices/%s/test_virt_addr debugfs file\n",
		       dev_name(dev));
		goto err_rmdir;
	}

	if (!debugfs_create_file("profiling", S_IRUSR, dir, ddev,
				 &iommu_debug_profiling_fops)) {
		pr_err("Couldn't create iommu/devices/%s/profiling debugfs file\n",
@@ -1666,6 +2162,13 @@ static int snarf_iommu_devices(struct device *dev, void *ignored)
		goto err_rmdir;
	}

	if (!debugfs_create_file("dma_attach", 0600, dir, ddev,
				 &iommu_debug_dma_attach_fops)) {
		pr_err("Couldn't create iommu/devices/%s/dma_attach debugfs file\n",
		       dev_name(dev));
		goto err_rmdir;
	}

	if (!debugfs_create_file("attach", S_IRUSR, dir, ddev,
				 &iommu_debug_attach_fops)) {
		pr_err("Couldn't create iommu/devices/%s/attach debugfs file\n",
@@ -1687,6 +2190,13 @@ static int snarf_iommu_devices(struct device *dev, void *ignored)
		goto err_rmdir;
	}

	if (!debugfs_create_file("dma_atos", 0600, dir, ddev,
				 &iommu_debug_dma_atos_fops)) {
		pr_err("Couldn't create iommu/devices/%s/dma_atos debugfs file\n",
		       dev_name(dev));
		goto err_rmdir;
	}

	if (!debugfs_create_file("map", S_IWUSR, dir, ddev,
				 &iommu_debug_map_fops)) {
		pr_err("Couldn't create iommu/devices/%s/map debugfs file\n",
@@ -1694,6 +2204,13 @@ static int snarf_iommu_devices(struct device *dev, void *ignored)
		goto err_rmdir;
	}

	if (!debugfs_create_file("dma_map", 0600, dir, ddev,
					 &iommu_debug_dma_map_fops)) {
		pr_err("Couldn't create iommu/devices/%s/dma_map debugfs file\n",
		       dev_name(dev));
			goto err_rmdir;
	}

	if (!debugfs_create_file("unmap", S_IWUSR, dir, ddev,
				 &iommu_debug_unmap_fops)) {
		pr_err("Couldn't create iommu/devices/%s/unmap debugfs file\n",
@@ -1701,6 +2218,20 @@ static int snarf_iommu_devices(struct device *dev, void *ignored)
		goto err_rmdir;
	}

	if (!debugfs_create_file("dma_unmap", 0200, dir, ddev,
					 &iommu_debug_dma_unmap_fops)) {
		pr_err("Couldn't create iommu/devices/%s/dma_unmap debugfs file\n",
		       dev_name(dev));
			goto err_rmdir;
	}

	if (!debugfs_create_file("pte", 0600, dir, ddev,
			&iommu_debug_pte_fops)) {
		pr_err("Couldn't create iommu/devices/%s/pte debugfs file\n",
		       dev_name(dev));
		goto err_rmdir;
	}

	if (!debugfs_create_file("config_clocks", S_IWUSR, dir, ddev,
				 &iommu_debug_config_clocks_fops)) {
		pr_err("Couldn't create iommu/devices/%s/config_clocks debugfs file\n",
@@ -1734,6 +2265,11 @@ static int iommu_debug_init_tests(void)
		return -ENODEV;
	}

	test_virt_addr = kzalloc(SZ_1M, GFP_KERNEL);

	if (!test_virt_addr)
		return -ENOMEM;

	return bus_for_each_dev(&platform_bus_type, NULL, NULL,
				snarf_iommu_devices);
}
+9 −0
Original line number Diff line number Diff line
@@ -1282,6 +1282,15 @@ phys_addr_t iommu_iova_to_phys_hard(struct iommu_domain *domain,
	return domain->ops->iova_to_phys_hard(domain, iova);
}

uint64_t iommu_iova_to_pte(struct iommu_domain *domain,
				    dma_addr_t iova)
{
	if (unlikely(domain->ops->iova_to_pte == NULL))
		return 0;

	return domain->ops->iova_to_pte(domain, iova);
}

bool iommu_is_iova_coherent(struct iommu_domain *domain, dma_addr_t iova)
{
	if (unlikely(domain->ops->is_iova_coherent == NULL))
Loading