xref: /linux/drivers/media/platform/nvidia/tegra-vde/iommu.c (revision 0526b56cbc3c489642bd6a5fe4b718dea7ef0ee8)
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * NVIDIA Tegra Video decoder driver
4  *
5  * Copyright (C) 2016-2019 GRATE-DRIVER project
6  */
7 
8 #include <linux/iommu.h>
9 #include <linux/iova.h>
10 #include <linux/kernel.h>
11 #include <linux/platform_device.h>
12 
13 #if IS_ENABLED(CONFIG_ARM_DMA_USE_IOMMU)
14 #include <asm/dma-iommu.h>
15 #endif
16 
17 #include "vde.h"
18 
19 int tegra_vde_iommu_map(struct tegra_vde *vde,
20 			struct sg_table *sgt,
21 			struct iova **iovap,
22 			size_t size)
23 {
24 	struct iova *iova;
25 	unsigned long shift;
26 	unsigned long end;
27 	dma_addr_t addr;
28 
29 	end = vde->domain->geometry.aperture_end;
30 	size = iova_align(&vde->iova, size);
31 	shift = iova_shift(&vde->iova);
32 
33 	iova = alloc_iova(&vde->iova, size >> shift, end >> shift, true);
34 	if (!iova)
35 		return -ENOMEM;
36 
37 	addr = iova_dma_addr(&vde->iova, iova);
38 
39 	size = iommu_map_sgtable(vde->domain, addr, sgt,
40 				 IOMMU_READ | IOMMU_WRITE);
41 	if (!size) {
42 		__free_iova(&vde->iova, iova);
43 		return -ENXIO;
44 	}
45 
46 	*iovap = iova;
47 
48 	return 0;
49 }
50 
51 void tegra_vde_iommu_unmap(struct tegra_vde *vde, struct iova *iova)
52 {
53 	unsigned long shift = iova_shift(&vde->iova);
54 	unsigned long size = iova_size(iova) << shift;
55 	dma_addr_t addr = iova_dma_addr(&vde->iova, iova);
56 
57 	iommu_unmap(vde->domain, addr, size);
58 	__free_iova(&vde->iova, iova);
59 }
60 
61 int tegra_vde_iommu_init(struct tegra_vde *vde)
62 {
63 	struct device *dev = vde->dev;
64 	struct iova *iova;
65 	unsigned long order;
66 	unsigned long shift;
67 	int err;
68 
69 	vde->group = iommu_group_get(dev);
70 	if (!vde->group)
71 		return 0;
72 
73 #if IS_ENABLED(CONFIG_ARM_DMA_USE_IOMMU)
74 	if (dev->archdata.mapping) {
75 		struct dma_iommu_mapping *mapping = to_dma_iommu_mapping(dev);
76 
77 		arm_iommu_detach_device(dev);
78 		arm_iommu_release_mapping(mapping);
79 	}
80 #endif
81 	vde->domain = iommu_domain_alloc(&platform_bus_type);
82 	if (!vde->domain) {
83 		err = -ENOMEM;
84 		goto put_group;
85 	}
86 
87 	err = iova_cache_get();
88 	if (err)
89 		goto free_domain;
90 
91 	order = __ffs(vde->domain->pgsize_bitmap);
92 	init_iova_domain(&vde->iova, 1UL << order, 0);
93 
94 	err = iommu_attach_group(vde->domain, vde->group);
95 	if (err)
96 		goto put_iova;
97 
98 	/*
99 	 * We're using some static addresses that are not accessible by VDE
100 	 * to trap invalid memory accesses.
101 	 */
102 	shift = iova_shift(&vde->iova);
103 	iova = reserve_iova(&vde->iova, 0x60000000 >> shift,
104 			    0x70000000 >> shift);
105 	if (!iova) {
106 		err = -ENOMEM;
107 		goto detach_group;
108 	}
109 
110 	vde->iova_resv_static_addresses = iova;
111 
112 	/*
113 	 * BSEV's end-address wraps around due to integer overflow during
114 	 * of hardware context preparation if IOVA is allocated at the end
115 	 * of address space and VDE can't handle that. Hence simply reserve
116 	 * the last page to avoid the problem.
117 	 */
118 	iova = reserve_iova(&vde->iova, 0xffffffff >> shift,
119 			    (0xffffffff >> shift) + 1);
120 	if (!iova) {
121 		err = -ENOMEM;
122 		goto unreserve_iova;
123 	}
124 
125 	vde->iova_resv_last_page = iova;
126 
127 	return 0;
128 
129 unreserve_iova:
130 	__free_iova(&vde->iova, vde->iova_resv_static_addresses);
131 detach_group:
132 	iommu_detach_group(vde->domain, vde->group);
133 put_iova:
134 	put_iova_domain(&vde->iova);
135 	iova_cache_put();
136 free_domain:
137 	iommu_domain_free(vde->domain);
138 put_group:
139 	iommu_group_put(vde->group);
140 
141 	return err;
142 }
143 
144 void tegra_vde_iommu_deinit(struct tegra_vde *vde)
145 {
146 	if (vde->domain) {
147 		__free_iova(&vde->iova, vde->iova_resv_last_page);
148 		__free_iova(&vde->iova, vde->iova_resv_static_addresses);
149 		iommu_detach_group(vde->domain, vde->group);
150 		put_iova_domain(&vde->iova);
151 		iova_cache_put();
152 		iommu_domain_free(vde->domain);
153 		iommu_group_put(vde->group);
154 
155 		vde->domain = NULL;
156 	}
157 }
158