158 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			158 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0+
 | |
| /*
 | |
|  * NVIDIA Tegra Video decoder driver
 | |
|  *
 | |
|  * Copyright (C) 2016-2019 GRATE-DRIVER project
 | |
|  */
 | |
| 
 | |
| #include <linux/iommu.h>
 | |
| #include <linux/iova.h>
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/platform_device.h>
 | |
| 
 | |
| #if IS_ENABLED(CONFIG_ARM_DMA_USE_IOMMU)
 | |
| #include <asm/dma-iommu.h>
 | |
| #endif
 | |
| 
 | |
| #include "vde.h"
 | |
| 
 | |
| int tegra_vde_iommu_map(struct tegra_vde *vde,
 | |
| 			struct sg_table *sgt,
 | |
| 			struct iova **iovap,
 | |
| 			size_t size)
 | |
| {
 | |
| 	struct iova *iova;
 | |
| 	unsigned long shift;
 | |
| 	unsigned long end;
 | |
| 	dma_addr_t addr;
 | |
| 
 | |
| 	end = vde->domain->geometry.aperture_end;
 | |
| 	size = iova_align(&vde->iova, size);
 | |
| 	shift = iova_shift(&vde->iova);
 | |
| 
 | |
| 	iova = alloc_iova(&vde->iova, size >> shift, end >> shift, true);
 | |
| 	if (!iova)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	addr = iova_dma_addr(&vde->iova, iova);
 | |
| 
 | |
| 	size = iommu_map_sgtable(vde->domain, addr, sgt,
 | |
| 				 IOMMU_READ | IOMMU_WRITE);
 | |
| 	if (!size) {
 | |
| 		__free_iova(&vde->iova, iova);
 | |
| 		return -ENXIO;
 | |
| 	}
 | |
| 
 | |
| 	*iovap = iova;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| void tegra_vde_iommu_unmap(struct tegra_vde *vde, struct iova *iova)
 | |
| {
 | |
| 	unsigned long shift = iova_shift(&vde->iova);
 | |
| 	unsigned long size = iova_size(iova) << shift;
 | |
| 	dma_addr_t addr = iova_dma_addr(&vde->iova, iova);
 | |
| 
 | |
| 	iommu_unmap(vde->domain, addr, size);
 | |
| 	__free_iova(&vde->iova, iova);
 | |
| }
 | |
| 
 | |
| int tegra_vde_iommu_init(struct tegra_vde *vde)
 | |
| {
 | |
| 	struct device *dev = vde->dev;
 | |
| 	struct iova *iova;
 | |
| 	unsigned long order;
 | |
| 	unsigned long shift;
 | |
| 	int err;
 | |
| 
 | |
| 	vde->group = iommu_group_get(dev);
 | |
| 	if (!vde->group)
 | |
| 		return 0;
 | |
| 
 | |
| #if IS_ENABLED(CONFIG_ARM_DMA_USE_IOMMU)
 | |
| 	if (dev->archdata.mapping) {
 | |
| 		struct dma_iommu_mapping *mapping = to_dma_iommu_mapping(dev);
 | |
| 
 | |
| 		arm_iommu_detach_device(dev);
 | |
| 		arm_iommu_release_mapping(mapping);
 | |
| 	}
 | |
| #endif
 | |
| 	vde->domain = iommu_domain_alloc(&platform_bus_type);
 | |
| 	if (!vde->domain) {
 | |
| 		err = -ENOMEM;
 | |
| 		goto put_group;
 | |
| 	}
 | |
| 
 | |
| 	err = iova_cache_get();
 | |
| 	if (err)
 | |
| 		goto free_domain;
 | |
| 
 | |
| 	order = __ffs(vde->domain->pgsize_bitmap);
 | |
| 	init_iova_domain(&vde->iova, 1UL << order, 0);
 | |
| 
 | |
| 	err = iommu_attach_group(vde->domain, vde->group);
 | |
| 	if (err)
 | |
| 		goto put_iova;
 | |
| 
 | |
| 	/*
 | |
| 	 * We're using some static addresses that are not accessible by VDE
 | |
| 	 * to trap invalid memory accesses.
 | |
| 	 */
 | |
| 	shift = iova_shift(&vde->iova);
 | |
| 	iova = reserve_iova(&vde->iova, 0x60000000 >> shift,
 | |
| 			    0x70000000 >> shift);
 | |
| 	if (!iova) {
 | |
| 		err = -ENOMEM;
 | |
| 		goto detach_group;
 | |
| 	}
 | |
| 
 | |
| 	vde->iova_resv_static_addresses = iova;
 | |
| 
 | |
| 	/*
 | |
| 	 * BSEV's end-address wraps around due to integer overflow during
 | |
| 	 * of hardware context preparation if IOVA is allocated at the end
 | |
| 	 * of address space and VDE can't handle that. Hence simply reserve
 | |
| 	 * the last page to avoid the problem.
 | |
| 	 */
 | |
| 	iova = reserve_iova(&vde->iova, 0xffffffff >> shift,
 | |
| 			    (0xffffffff >> shift) + 1);
 | |
| 	if (!iova) {
 | |
| 		err = -ENOMEM;
 | |
| 		goto unreserve_iova;
 | |
| 	}
 | |
| 
 | |
| 	vde->iova_resv_last_page = iova;
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| unreserve_iova:
 | |
| 	__free_iova(&vde->iova, vde->iova_resv_static_addresses);
 | |
| detach_group:
 | |
| 	iommu_detach_group(vde->domain, vde->group);
 | |
| put_iova:
 | |
| 	put_iova_domain(&vde->iova);
 | |
| 	iova_cache_put();
 | |
| free_domain:
 | |
| 	iommu_domain_free(vde->domain);
 | |
| put_group:
 | |
| 	iommu_group_put(vde->group);
 | |
| 
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| void tegra_vde_iommu_deinit(struct tegra_vde *vde)
 | |
| {
 | |
| 	if (vde->domain) {
 | |
| 		__free_iova(&vde->iova, vde->iova_resv_last_page);
 | |
| 		__free_iova(&vde->iova, vde->iova_resv_static_addresses);
 | |
| 		iommu_detach_group(vde->domain, vde->group);
 | |
| 		put_iova_domain(&vde->iova);
 | |
| 		iova_cache_put();
 | |
| 		iommu_domain_free(vde->domain);
 | |
| 		iommu_group_put(vde->group);
 | |
| 
 | |
| 		vde->domain = NULL;
 | |
| 	}
 | |
| }
 |