1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* Copyright (c) 2020 NVIDIA Corporation */ |
3 | |
4 | #include <linux/host1x.h> |
5 | #include <linux/iommu.h> |
6 | #include <linux/list.h> |
7 | |
8 | #include <drm/drm_drv.h> |
9 | #include <drm/drm_file.h> |
10 | #include <drm/drm_utils.h> |
11 | |
12 | #include "drm.h" |
13 | #include "uapi.h" |
14 | |
15 | static void tegra_drm_mapping_release(struct kref *ref) |
16 | { |
17 | struct tegra_drm_mapping *mapping = |
18 | container_of(ref, struct tegra_drm_mapping, ref); |
19 | |
20 | host1x_bo_unpin(map: mapping->map); |
21 | host1x_bo_put(bo: mapping->bo); |
22 | |
23 | kfree(objp: mapping); |
24 | } |
25 | |
26 | void tegra_drm_mapping_put(struct tegra_drm_mapping *mapping) |
27 | { |
28 | kref_put(kref: &mapping->ref, release: tegra_drm_mapping_release); |
29 | } |
30 | |
31 | static void tegra_drm_channel_context_close(struct tegra_drm_context *context) |
32 | { |
33 | struct tegra_drm_mapping *mapping; |
34 | unsigned long id; |
35 | |
36 | if (context->memory_context) |
37 | host1x_memory_context_put(cd: context->memory_context); |
38 | |
39 | xa_for_each(&context->mappings, id, mapping) |
40 | tegra_drm_mapping_put(mapping); |
41 | |
42 | xa_destroy(&context->mappings); |
43 | |
44 | host1x_channel_put(channel: context->channel); |
45 | |
46 | kfree(objp: context); |
47 | } |
48 | |
49 | void tegra_drm_uapi_close_file(struct tegra_drm_file *file) |
50 | { |
51 | struct tegra_drm_context *context; |
52 | struct host1x_syncpt *sp; |
53 | unsigned long id; |
54 | |
55 | xa_for_each(&file->contexts, id, context) |
56 | tegra_drm_channel_context_close(context); |
57 | |
58 | xa_for_each(&file->syncpoints, id, sp) |
59 | host1x_syncpt_put(sp); |
60 | |
61 | xa_destroy(&file->contexts); |
62 | xa_destroy(&file->syncpoints); |
63 | } |
64 | |
65 | static struct tegra_drm_client *tegra_drm_find_client(struct tegra_drm *tegra, u32 class) |
66 | { |
67 | struct tegra_drm_client *client; |
68 | |
69 | list_for_each_entry(client, &tegra->clients, list) |
70 | if (client->base.class == class) |
71 | return client; |
72 | |
73 | return NULL; |
74 | } |
75 | |
76 | int tegra_drm_ioctl_channel_open(struct drm_device *drm, void *data, struct drm_file *file) |
77 | { |
78 | struct host1x *host = tegra_drm_to_host1x(tegra: drm->dev_private); |
79 | struct tegra_drm_file *fpriv = file->driver_priv; |
80 | struct tegra_drm *tegra = drm->dev_private; |
81 | struct drm_tegra_channel_open *args = data; |
82 | struct tegra_drm_client *client = NULL; |
83 | struct tegra_drm_context *context; |
84 | int err; |
85 | |
86 | if (args->flags) |
87 | return -EINVAL; |
88 | |
89 | context = kzalloc(size: sizeof(*context), GFP_KERNEL); |
90 | if (!context) |
91 | return -ENOMEM; |
92 | |
93 | client = tegra_drm_find_client(tegra, class: args->host1x_class); |
94 | if (!client) { |
95 | err = -ENODEV; |
96 | goto free; |
97 | } |
98 | |
99 | if (client->shared_channel) { |
100 | context->channel = host1x_channel_get(channel: client->shared_channel); |
101 | } else { |
102 | context->channel = host1x_channel_request(client: &client->base); |
103 | if (!context->channel) { |
104 | err = -EBUSY; |
105 | goto free; |
106 | } |
107 | } |
108 | |
109 | /* Only allocate context if the engine supports context isolation. */ |
110 | if (device_iommu_mapped(dev: client->base.dev) && client->ops->can_use_memory_ctx) { |
111 | bool supported; |
112 | |
113 | err = client->ops->can_use_memory_ctx(client, &supported); |
114 | if (err) |
115 | goto put_channel; |
116 | |
117 | if (supported) |
118 | context->memory_context = host1x_memory_context_alloc( |
119 | host1x: host, dev: client->base.dev, pid: get_task_pid(current, type: PIDTYPE_TGID)); |
120 | |
121 | if (IS_ERR(ptr: context->memory_context)) { |
122 | if (PTR_ERR(ptr: context->memory_context) != -EOPNOTSUPP) { |
123 | err = PTR_ERR(ptr: context->memory_context); |
124 | goto put_channel; |
125 | } else { |
126 | /* |
127 | * OK, HW does not support contexts or contexts |
128 | * are disabled. |
129 | */ |
130 | context->memory_context = NULL; |
131 | } |
132 | } |
133 | } |
134 | |
135 | err = xa_alloc(xa: &fpriv->contexts, id: &args->context, entry: context, XA_LIMIT(1, U32_MAX), |
136 | GFP_KERNEL); |
137 | if (err < 0) |
138 | goto put_memctx; |
139 | |
140 | context->client = client; |
141 | xa_init_flags(xa: &context->mappings, XA_FLAGS_ALLOC1); |
142 | |
143 | args->version = client->version; |
144 | args->capabilities = 0; |
145 | |
146 | if (device_get_dma_attr(dev: client->base.dev) == DEV_DMA_COHERENT) |
147 | args->capabilities |= DRM_TEGRA_CHANNEL_CAP_CACHE_COHERENT; |
148 | |
149 | return 0; |
150 | |
151 | put_memctx: |
152 | if (context->memory_context) |
153 | host1x_memory_context_put(cd: context->memory_context); |
154 | put_channel: |
155 | host1x_channel_put(channel: context->channel); |
156 | free: |
157 | kfree(objp: context); |
158 | |
159 | return err; |
160 | } |
161 | |
162 | int tegra_drm_ioctl_channel_close(struct drm_device *drm, void *data, struct drm_file *file) |
163 | { |
164 | struct tegra_drm_file *fpriv = file->driver_priv; |
165 | struct drm_tegra_channel_close *args = data; |
166 | struct tegra_drm_context *context; |
167 | |
168 | mutex_lock(&fpriv->lock); |
169 | |
170 | context = xa_load(&fpriv->contexts, index: args->context); |
171 | if (!context) { |
172 | mutex_unlock(lock: &fpriv->lock); |
173 | return -EINVAL; |
174 | } |
175 | |
176 | xa_erase(&fpriv->contexts, index: args->context); |
177 | |
178 | mutex_unlock(lock: &fpriv->lock); |
179 | |
180 | tegra_drm_channel_context_close(context); |
181 | |
182 | return 0; |
183 | } |
184 | |
185 | int tegra_drm_ioctl_channel_map(struct drm_device *drm, void *data, struct drm_file *file) |
186 | { |
187 | struct tegra_drm_file *fpriv = file->driver_priv; |
188 | struct drm_tegra_channel_map *args = data; |
189 | struct tegra_drm_mapping *mapping; |
190 | struct tegra_drm_context *context; |
191 | enum dma_data_direction direction; |
192 | struct device *mapping_dev; |
193 | int err = 0; |
194 | |
195 | if (args->flags & ~DRM_TEGRA_CHANNEL_MAP_READ_WRITE) |
196 | return -EINVAL; |
197 | |
198 | mutex_lock(&fpriv->lock); |
199 | |
200 | context = xa_load(&fpriv->contexts, index: args->context); |
201 | if (!context) { |
202 | mutex_unlock(lock: &fpriv->lock); |
203 | return -EINVAL; |
204 | } |
205 | |
206 | mapping = kzalloc(size: sizeof(*mapping), GFP_KERNEL); |
207 | if (!mapping) { |
208 | err = -ENOMEM; |
209 | goto unlock; |
210 | } |
211 | |
212 | kref_init(kref: &mapping->ref); |
213 | |
214 | if (context->memory_context) |
215 | mapping_dev = &context->memory_context->dev; |
216 | else |
217 | mapping_dev = context->client->base.dev; |
218 | |
219 | mapping->bo = tegra_gem_lookup(file, handle: args->handle); |
220 | if (!mapping->bo) { |
221 | err = -EINVAL; |
222 | goto free; |
223 | } |
224 | |
225 | switch (args->flags & DRM_TEGRA_CHANNEL_MAP_READ_WRITE) { |
226 | case DRM_TEGRA_CHANNEL_MAP_READ_WRITE: |
227 | direction = DMA_BIDIRECTIONAL; |
228 | break; |
229 | |
230 | case DRM_TEGRA_CHANNEL_MAP_WRITE: |
231 | direction = DMA_FROM_DEVICE; |
232 | break; |
233 | |
234 | case DRM_TEGRA_CHANNEL_MAP_READ: |
235 | direction = DMA_TO_DEVICE; |
236 | break; |
237 | |
238 | default: |
239 | err = -EINVAL; |
240 | goto put_gem; |
241 | } |
242 | |
243 | mapping->map = host1x_bo_pin(dev: mapping_dev, bo: mapping->bo, dir: direction, NULL); |
244 | if (IS_ERR(ptr: mapping->map)) { |
245 | err = PTR_ERR(ptr: mapping->map); |
246 | goto put_gem; |
247 | } |
248 | |
249 | mapping->iova = mapping->map->phys; |
250 | mapping->iova_end = mapping->iova + host1x_to_tegra_bo(bo: mapping->bo)->gem.size; |
251 | |
252 | err = xa_alloc(xa: &context->mappings, id: &args->mapping, entry: mapping, XA_LIMIT(1, U32_MAX), |
253 | GFP_KERNEL); |
254 | if (err < 0) |
255 | goto unpin; |
256 | |
257 | mutex_unlock(lock: &fpriv->lock); |
258 | |
259 | return 0; |
260 | |
261 | unpin: |
262 | host1x_bo_unpin(map: mapping->map); |
263 | put_gem: |
264 | host1x_bo_put(bo: mapping->bo); |
265 | free: |
266 | kfree(objp: mapping); |
267 | unlock: |
268 | mutex_unlock(lock: &fpriv->lock); |
269 | return err; |
270 | } |
271 | |
272 | int tegra_drm_ioctl_channel_unmap(struct drm_device *drm, void *data, struct drm_file *file) |
273 | { |
274 | struct tegra_drm_file *fpriv = file->driver_priv; |
275 | struct drm_tegra_channel_unmap *args = data; |
276 | struct tegra_drm_mapping *mapping; |
277 | struct tegra_drm_context *context; |
278 | |
279 | mutex_lock(&fpriv->lock); |
280 | |
281 | context = xa_load(&fpriv->contexts, index: args->context); |
282 | if (!context) { |
283 | mutex_unlock(lock: &fpriv->lock); |
284 | return -EINVAL; |
285 | } |
286 | |
287 | mapping = xa_erase(&context->mappings, index: args->mapping); |
288 | |
289 | mutex_unlock(lock: &fpriv->lock); |
290 | |
291 | if (!mapping) |
292 | return -EINVAL; |
293 | |
294 | tegra_drm_mapping_put(mapping); |
295 | return 0; |
296 | } |
297 | |
298 | int tegra_drm_ioctl_syncpoint_allocate(struct drm_device *drm, void *data, struct drm_file *file) |
299 | { |
300 | struct host1x *host1x = tegra_drm_to_host1x(tegra: drm->dev_private); |
301 | struct tegra_drm_file *fpriv = file->driver_priv; |
302 | struct drm_tegra_syncpoint_allocate *args = data; |
303 | struct host1x_syncpt *sp; |
304 | int err; |
305 | |
306 | if (args->id) |
307 | return -EINVAL; |
308 | |
309 | sp = host1x_syncpt_alloc(host: host1x, HOST1X_SYNCPT_CLIENT_MANAGED, current->comm); |
310 | if (!sp) |
311 | return -EBUSY; |
312 | |
313 | args->id = host1x_syncpt_id(sp); |
314 | |
315 | err = xa_insert(xa: &fpriv->syncpoints, index: args->id, entry: sp, GFP_KERNEL); |
316 | if (err) { |
317 | host1x_syncpt_put(sp); |
318 | return err; |
319 | } |
320 | |
321 | return 0; |
322 | } |
323 | |
324 | int tegra_drm_ioctl_syncpoint_free(struct drm_device *drm, void *data, struct drm_file *file) |
325 | { |
326 | struct tegra_drm_file *fpriv = file->driver_priv; |
327 | struct drm_tegra_syncpoint_allocate *args = data; |
328 | struct host1x_syncpt *sp; |
329 | |
330 | mutex_lock(&fpriv->lock); |
331 | sp = xa_erase(&fpriv->syncpoints, index: args->id); |
332 | mutex_unlock(lock: &fpriv->lock); |
333 | |
334 | if (!sp) |
335 | return -EINVAL; |
336 | |
337 | host1x_syncpt_put(sp); |
338 | |
339 | return 0; |
340 | } |
341 | |
342 | int tegra_drm_ioctl_syncpoint_wait(struct drm_device *drm, void *data, struct drm_file *file) |
343 | { |
344 | struct host1x *host1x = tegra_drm_to_host1x(tegra: drm->dev_private); |
345 | struct drm_tegra_syncpoint_wait *args = data; |
346 | signed long timeout_jiffies; |
347 | struct host1x_syncpt *sp; |
348 | |
349 | if (args->padding != 0) |
350 | return -EINVAL; |
351 | |
352 | sp = host1x_syncpt_get_by_id_noref(host: host1x, id: args->id); |
353 | if (!sp) |
354 | return -EINVAL; |
355 | |
356 | timeout_jiffies = drm_timeout_abs_to_jiffies(timeout_nsec: args->timeout_ns); |
357 | |
358 | return host1x_syncpt_wait(sp, thresh: args->threshold, timeout: timeout_jiffies, value: &args->value); |
359 | } |
360 | |