162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * NVIDIA Tegra Video decoder driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2016-2019 GRATE-DRIVER project
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/iommu.h>
962306a36Sopenharmony_ci#include <linux/iova.h>
1062306a36Sopenharmony_ci#include <linux/kernel.h>
1162306a36Sopenharmony_ci#include <linux/platform_device.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_ARM_DMA_USE_IOMMU)
1462306a36Sopenharmony_ci#include <asm/dma-iommu.h>
1562306a36Sopenharmony_ci#endif
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include "vde.h"
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ciint tegra_vde_iommu_map(struct tegra_vde *vde,
2062306a36Sopenharmony_ci			struct sg_table *sgt,
2162306a36Sopenharmony_ci			struct iova **iovap,
2262306a36Sopenharmony_ci			size_t size)
2362306a36Sopenharmony_ci{
2462306a36Sopenharmony_ci	struct iova *iova;
2562306a36Sopenharmony_ci	unsigned long shift;
2662306a36Sopenharmony_ci	unsigned long end;
2762306a36Sopenharmony_ci	dma_addr_t addr;
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci	end = vde->domain->geometry.aperture_end;
3062306a36Sopenharmony_ci	size = iova_align(&vde->iova, size);
3162306a36Sopenharmony_ci	shift = iova_shift(&vde->iova);
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	iova = alloc_iova(&vde->iova, size >> shift, end >> shift, true);
3462306a36Sopenharmony_ci	if (!iova)
3562306a36Sopenharmony_ci		return -ENOMEM;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	addr = iova_dma_addr(&vde->iova, iova);
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	size = iommu_map_sgtable(vde->domain, addr, sgt,
4062306a36Sopenharmony_ci				 IOMMU_READ | IOMMU_WRITE);
4162306a36Sopenharmony_ci	if (!size) {
4262306a36Sopenharmony_ci		__free_iova(&vde->iova, iova);
4362306a36Sopenharmony_ci		return -ENXIO;
4462306a36Sopenharmony_ci	}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	*iovap = iova;
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	return 0;
4962306a36Sopenharmony_ci}
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_civoid tegra_vde_iommu_unmap(struct tegra_vde *vde, struct iova *iova)
5262306a36Sopenharmony_ci{
5362306a36Sopenharmony_ci	unsigned long shift = iova_shift(&vde->iova);
5462306a36Sopenharmony_ci	unsigned long size = iova_size(iova) << shift;
5562306a36Sopenharmony_ci	dma_addr_t addr = iova_dma_addr(&vde->iova, iova);
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	iommu_unmap(vde->domain, addr, size);
5862306a36Sopenharmony_ci	__free_iova(&vde->iova, iova);
5962306a36Sopenharmony_ci}
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ciint tegra_vde_iommu_init(struct tegra_vde *vde)
6262306a36Sopenharmony_ci{
6362306a36Sopenharmony_ci	struct device *dev = vde->dev;
6462306a36Sopenharmony_ci	struct iova *iova;
6562306a36Sopenharmony_ci	unsigned long order;
6662306a36Sopenharmony_ci	unsigned long shift;
6762306a36Sopenharmony_ci	int err;
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	vde->group = iommu_group_get(dev);
7062306a36Sopenharmony_ci	if (!vde->group)
7162306a36Sopenharmony_ci		return 0;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_ARM_DMA_USE_IOMMU)
7462306a36Sopenharmony_ci	if (dev->archdata.mapping) {
7562306a36Sopenharmony_ci		struct dma_iommu_mapping *mapping = to_dma_iommu_mapping(dev);
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci		arm_iommu_detach_device(dev);
7862306a36Sopenharmony_ci		arm_iommu_release_mapping(mapping);
7962306a36Sopenharmony_ci	}
8062306a36Sopenharmony_ci#endif
8162306a36Sopenharmony_ci	vde->domain = iommu_domain_alloc(&platform_bus_type);
8262306a36Sopenharmony_ci	if (!vde->domain) {
8362306a36Sopenharmony_ci		err = -ENOMEM;
8462306a36Sopenharmony_ci		goto put_group;
8562306a36Sopenharmony_ci	}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	err = iova_cache_get();
8862306a36Sopenharmony_ci	if (err)
8962306a36Sopenharmony_ci		goto free_domain;
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	order = __ffs(vde->domain->pgsize_bitmap);
9262306a36Sopenharmony_ci	init_iova_domain(&vde->iova, 1UL << order, 0);
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	err = iommu_attach_group(vde->domain, vde->group);
9562306a36Sopenharmony_ci	if (err)
9662306a36Sopenharmony_ci		goto put_iova;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	/*
9962306a36Sopenharmony_ci	 * We're using some static addresses that are not accessible by VDE
10062306a36Sopenharmony_ci	 * to trap invalid memory accesses.
10162306a36Sopenharmony_ci	 */
10262306a36Sopenharmony_ci	shift = iova_shift(&vde->iova);
10362306a36Sopenharmony_ci	iova = reserve_iova(&vde->iova, 0x60000000 >> shift,
10462306a36Sopenharmony_ci			    0x70000000 >> shift);
10562306a36Sopenharmony_ci	if (!iova) {
10662306a36Sopenharmony_ci		err = -ENOMEM;
10762306a36Sopenharmony_ci		goto detach_group;
10862306a36Sopenharmony_ci	}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	vde->iova_resv_static_addresses = iova;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	/*
11362306a36Sopenharmony_ci	 * BSEV's end-address wraps around due to integer overflow during
11462306a36Sopenharmony_ci	 * of hardware context preparation if IOVA is allocated at the end
11562306a36Sopenharmony_ci	 * of address space and VDE can't handle that. Hence simply reserve
11662306a36Sopenharmony_ci	 * the last page to avoid the problem.
11762306a36Sopenharmony_ci	 */
11862306a36Sopenharmony_ci	iova = reserve_iova(&vde->iova, 0xffffffff >> shift,
11962306a36Sopenharmony_ci			    (0xffffffff >> shift) + 1);
12062306a36Sopenharmony_ci	if (!iova) {
12162306a36Sopenharmony_ci		err = -ENOMEM;
12262306a36Sopenharmony_ci		goto unreserve_iova;
12362306a36Sopenharmony_ci	}
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	vde->iova_resv_last_page = iova;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	return 0;
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ciunreserve_iova:
13062306a36Sopenharmony_ci	__free_iova(&vde->iova, vde->iova_resv_static_addresses);
13162306a36Sopenharmony_cidetach_group:
13262306a36Sopenharmony_ci	iommu_detach_group(vde->domain, vde->group);
13362306a36Sopenharmony_ciput_iova:
13462306a36Sopenharmony_ci	put_iova_domain(&vde->iova);
13562306a36Sopenharmony_ci	iova_cache_put();
13662306a36Sopenharmony_cifree_domain:
13762306a36Sopenharmony_ci	iommu_domain_free(vde->domain);
13862306a36Sopenharmony_ciput_group:
13962306a36Sopenharmony_ci	iommu_group_put(vde->group);
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	return err;
14262306a36Sopenharmony_ci}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_civoid tegra_vde_iommu_deinit(struct tegra_vde *vde)
14562306a36Sopenharmony_ci{
14662306a36Sopenharmony_ci	if (vde->domain) {
14762306a36Sopenharmony_ci		__free_iova(&vde->iova, vde->iova_resv_last_page);
14862306a36Sopenharmony_ci		__free_iova(&vde->iova, vde->iova_resv_static_addresses);
14962306a36Sopenharmony_ci		iommu_detach_group(vde->domain, vde->group);
15062306a36Sopenharmony_ci		put_iova_domain(&vde->iova);
15162306a36Sopenharmony_ci		iova_cache_put();
15262306a36Sopenharmony_ci		iommu_domain_free(vde->domain);
15362306a36Sopenharmony_ci		iommu_group_put(vde->group);
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci		vde->domain = NULL;
15662306a36Sopenharmony_ci	}
15762306a36Sopenharmony_ci}
158