1 | /* |
2 | * Copyright (C) 2017 Samsung Electronics Co.Ltd |
3 | * Authors: |
4 | * Marek Szyprowski <m.szyprowski@samsung.com> |
5 | * |
6 | * Exynos DRM Image Post Processing (IPP) related functions |
7 | * |
8 | * Permission is hereby granted, free of charge, to any person obtaining a |
9 | * copy of this software and associated documentation files (the "Software"), |
10 | * to deal in the Software without restriction, including without limitation |
11 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
12 | * and/or sell copies of the Software, and to permit persons to whom the |
13 | * Software is furnished to do so, subject to the following conditions: |
14 | * |
15 | * The above copyright notice and this permission notice shall be included in |
16 | * all copies or substantial portions of the Software. |
17 | */ |
18 | |
19 | #include <linux/uaccess.h> |
20 | |
21 | #include <drm/drm_blend.h> |
22 | #include <drm/drm_file.h> |
23 | #include <drm/drm_fourcc.h> |
24 | #include <drm/drm_mode.h> |
25 | #include <drm/exynos_drm.h> |
26 | |
27 | #include "exynos_drm_drv.h" |
28 | #include "exynos_drm_gem.h" |
29 | #include "exynos_drm_ipp.h" |
30 | |
31 | static int num_ipp; |
32 | static LIST_HEAD(ipp_list); |
33 | |
34 | /** |
35 | * exynos_drm_ipp_register - Register a new picture processor hardware module |
36 | * @dev: DRM device |
37 | * @ipp: ipp module to init |
38 | * @funcs: callbacks for the new ipp object |
39 | * @caps: bitmask of ipp capabilities (%DRM_EXYNOS_IPP_CAP_*) |
40 | * @formats: array of supported formats |
41 | * @num_formats: size of the supported formats array |
42 | * @name: name (for debugging purposes) |
43 | * |
44 | * Initializes a ipp module. |
45 | * |
46 | * Returns: |
47 | * Zero on success, error code on failure. |
48 | */ |
49 | int exynos_drm_ipp_register(struct device *dev, struct exynos_drm_ipp *ipp, |
50 | const struct exynos_drm_ipp_funcs *funcs, unsigned int caps, |
51 | const struct exynos_drm_ipp_formats *formats, |
52 | unsigned int num_formats, const char *name) |
53 | { |
54 | WARN_ON(!ipp); |
55 | WARN_ON(!funcs); |
56 | WARN_ON(!formats); |
57 | WARN_ON(!num_formats); |
58 | |
59 | spin_lock_init(&ipp->lock); |
60 | INIT_LIST_HEAD(list: &ipp->todo_list); |
61 | init_waitqueue_head(&ipp->done_wq); |
62 | ipp->dev = dev; |
63 | ipp->funcs = funcs; |
64 | ipp->capabilities = caps; |
65 | ipp->name = name; |
66 | ipp->formats = formats; |
67 | ipp->num_formats = num_formats; |
68 | |
69 | /* ipp_list modification is serialized by component framework */ |
70 | list_add_tail(new: &ipp->head, head: &ipp_list); |
71 | ipp->id = num_ipp++; |
72 | |
73 | DRM_DEV_DEBUG_DRIVER(dev, "Registered ipp %d\n" , ipp->id); |
74 | |
75 | return 0; |
76 | } |
77 | |
78 | /** |
79 | * exynos_drm_ipp_unregister - Unregister the picture processor module |
80 | * @dev: DRM device |
81 | * @ipp: ipp module |
82 | */ |
83 | void exynos_drm_ipp_unregister(struct device *dev, |
84 | struct exynos_drm_ipp *ipp) |
85 | { |
86 | WARN_ON(ipp->task); |
87 | WARN_ON(!list_empty(&ipp->todo_list)); |
88 | list_del(entry: &ipp->head); |
89 | } |
90 | |
91 | /** |
92 | * exynos_drm_ipp_get_res_ioctl - enumerate all ipp modules |
93 | * @dev: DRM device |
94 | * @data: ioctl data |
95 | * @file_priv: DRM file info |
96 | * |
97 | * Construct a list of ipp ids. |
98 | * |
99 | * Called by the user via ioctl. |
100 | * |
101 | * Returns: |
102 | * Zero on success, negative errno on failure. |
103 | */ |
104 | int exynos_drm_ipp_get_res_ioctl(struct drm_device *dev, void *data, |
105 | struct drm_file *file_priv) |
106 | { |
107 | struct drm_exynos_ioctl_ipp_get_res *resp = data; |
108 | struct exynos_drm_ipp *ipp; |
109 | uint32_t __user *ipp_ptr = (uint32_t __user *) |
110 | (unsigned long)resp->ipp_id_ptr; |
111 | unsigned int count = num_ipp, copied = 0; |
112 | |
113 | /* |
114 | * This ioctl is called twice, once to determine how much space is |
115 | * needed, and the 2nd time to fill it. |
116 | */ |
117 | if (count && resp->count_ipps >= count) { |
118 | list_for_each_entry(ipp, &ipp_list, head) { |
119 | if (put_user(ipp->id, ipp_ptr + copied)) |
120 | return -EFAULT; |
121 | copied++; |
122 | } |
123 | } |
124 | resp->count_ipps = count; |
125 | |
126 | return 0; |
127 | } |
128 | |
129 | static inline struct exynos_drm_ipp *__ipp_get(uint32_t id) |
130 | { |
131 | struct exynos_drm_ipp *ipp; |
132 | |
133 | list_for_each_entry(ipp, &ipp_list, head) |
134 | if (ipp->id == id) |
135 | return ipp; |
136 | return NULL; |
137 | } |
138 | |
139 | /** |
140 | * exynos_drm_ipp_get_caps_ioctl - get ipp module capabilities and formats |
141 | * @dev: DRM device |
142 | * @data: ioctl data |
143 | * @file_priv: DRM file info |
144 | * |
145 | * Construct a structure describing ipp module capabilities. |
146 | * |
147 | * Called by the user via ioctl. |
148 | * |
149 | * Returns: |
150 | * Zero on success, negative errno on failure. |
151 | */ |
152 | int exynos_drm_ipp_get_caps_ioctl(struct drm_device *dev, void *data, |
153 | struct drm_file *file_priv) |
154 | { |
155 | struct drm_exynos_ioctl_ipp_get_caps *resp = data; |
156 | void __user *ptr = (void __user *)(unsigned long)resp->formats_ptr; |
157 | struct exynos_drm_ipp *ipp; |
158 | int i; |
159 | |
160 | ipp = __ipp_get(id: resp->ipp_id); |
161 | if (!ipp) |
162 | return -ENOENT; |
163 | |
164 | resp->ipp_id = ipp->id; |
165 | resp->capabilities = ipp->capabilities; |
166 | |
167 | /* |
168 | * This ioctl is called twice, once to determine how much space is |
169 | * needed, and the 2nd time to fill it. |
170 | */ |
171 | if (resp->formats_count >= ipp->num_formats) { |
172 | for (i = 0; i < ipp->num_formats; i++) { |
173 | struct drm_exynos_ipp_format tmp = { |
174 | .fourcc = ipp->formats[i].fourcc, |
175 | .type = ipp->formats[i].type, |
176 | .modifier = ipp->formats[i].modifier, |
177 | }; |
178 | |
179 | if (copy_to_user(to: ptr, from: &tmp, n: sizeof(tmp))) |
180 | return -EFAULT; |
181 | ptr += sizeof(tmp); |
182 | } |
183 | } |
184 | resp->formats_count = ipp->num_formats; |
185 | |
186 | return 0; |
187 | } |
188 | |
189 | static inline const struct exynos_drm_ipp_formats *__ipp_format_get( |
190 | struct exynos_drm_ipp *ipp, uint32_t fourcc, |
191 | uint64_t mod, unsigned int type) |
192 | { |
193 | int i; |
194 | |
195 | for (i = 0; i < ipp->num_formats; i++) { |
196 | if ((ipp->formats[i].type & type) && |
197 | ipp->formats[i].fourcc == fourcc && |
198 | ipp->formats[i].modifier == mod) |
199 | return &ipp->formats[i]; |
200 | } |
201 | return NULL; |
202 | } |
203 | |
204 | /** |
205 | * exynos_drm_ipp_get_limits_ioctl - get ipp module limits |
206 | * @dev: DRM device |
207 | * @data: ioctl data |
208 | * @file_priv: DRM file info |
209 | * |
210 | * Construct a structure describing ipp module limitations for provided |
211 | * picture format. |
212 | * |
213 | * Called by the user via ioctl. |
214 | * |
215 | * Returns: |
216 | * Zero on success, negative errno on failure. |
217 | */ |
218 | int exynos_drm_ipp_get_limits_ioctl(struct drm_device *dev, void *data, |
219 | struct drm_file *file_priv) |
220 | { |
221 | struct drm_exynos_ioctl_ipp_get_limits *resp = data; |
222 | void __user *ptr = (void __user *)(unsigned long)resp->limits_ptr; |
223 | const struct exynos_drm_ipp_formats *format; |
224 | struct exynos_drm_ipp *ipp; |
225 | |
226 | if (resp->type != DRM_EXYNOS_IPP_FORMAT_SOURCE && |
227 | resp->type != DRM_EXYNOS_IPP_FORMAT_DESTINATION) |
228 | return -EINVAL; |
229 | |
230 | ipp = __ipp_get(id: resp->ipp_id); |
231 | if (!ipp) |
232 | return -ENOENT; |
233 | |
234 | format = __ipp_format_get(ipp, fourcc: resp->fourcc, mod: resp->modifier, |
235 | type: resp->type); |
236 | if (!format) |
237 | return -EINVAL; |
238 | |
239 | /* |
240 | * This ioctl is called twice, once to determine how much space is |
241 | * needed, and the 2nd time to fill it. |
242 | */ |
243 | if (format->num_limits && resp->limits_count >= format->num_limits) |
244 | if (copy_to_user(to: (void __user *)ptr, from: format->limits, |
245 | n: sizeof(*format->limits) * format->num_limits)) |
246 | return -EFAULT; |
247 | resp->limits_count = format->num_limits; |
248 | |
249 | return 0; |
250 | } |
251 | |
252 | struct drm_pending_exynos_ipp_event { |
253 | struct drm_pending_event base; |
254 | struct drm_exynos_ipp_event event; |
255 | }; |
256 | |
257 | static inline struct exynos_drm_ipp_task * |
258 | exynos_drm_ipp_task_alloc(struct exynos_drm_ipp *ipp) |
259 | { |
260 | struct exynos_drm_ipp_task *task; |
261 | |
262 | task = kzalloc(size: sizeof(*task), GFP_KERNEL); |
263 | if (!task) |
264 | return NULL; |
265 | |
266 | task->dev = ipp->dev; |
267 | task->ipp = ipp; |
268 | |
269 | /* some defaults */ |
270 | task->src.rect.w = task->dst.rect.w = UINT_MAX; |
271 | task->src.rect.h = task->dst.rect.h = UINT_MAX; |
272 | task->transform.rotation = DRM_MODE_ROTATE_0; |
273 | |
274 | DRM_DEV_DEBUG_DRIVER(task->dev, "Allocated task %pK\n" , task); |
275 | |
276 | return task; |
277 | } |
278 | |
279 | static const struct exynos_drm_param_map { |
280 | unsigned int id; |
281 | unsigned int size; |
282 | unsigned int offset; |
283 | } exynos_drm_ipp_params_maps[] = { |
284 | { |
285 | DRM_EXYNOS_IPP_TASK_BUFFER | DRM_EXYNOS_IPP_TASK_TYPE_SOURCE, |
286 | sizeof(struct drm_exynos_ipp_task_buffer), |
287 | offsetof(struct exynos_drm_ipp_task, src.buf), |
288 | }, { |
289 | DRM_EXYNOS_IPP_TASK_BUFFER | |
290 | DRM_EXYNOS_IPP_TASK_TYPE_DESTINATION, |
291 | sizeof(struct drm_exynos_ipp_task_buffer), |
292 | offsetof(struct exynos_drm_ipp_task, dst.buf), |
293 | }, { |
294 | DRM_EXYNOS_IPP_TASK_RECTANGLE | DRM_EXYNOS_IPP_TASK_TYPE_SOURCE, |
295 | sizeof(struct drm_exynos_ipp_task_rect), |
296 | offsetof(struct exynos_drm_ipp_task, src.rect), |
297 | }, { |
298 | DRM_EXYNOS_IPP_TASK_RECTANGLE | |
299 | DRM_EXYNOS_IPP_TASK_TYPE_DESTINATION, |
300 | sizeof(struct drm_exynos_ipp_task_rect), |
301 | offsetof(struct exynos_drm_ipp_task, dst.rect), |
302 | }, { |
303 | DRM_EXYNOS_IPP_TASK_TRANSFORM, |
304 | sizeof(struct drm_exynos_ipp_task_transform), |
305 | offsetof(struct exynos_drm_ipp_task, transform), |
306 | }, { |
307 | DRM_EXYNOS_IPP_TASK_ALPHA, |
308 | sizeof(struct drm_exynos_ipp_task_alpha), |
309 | offsetof(struct exynos_drm_ipp_task, alpha), |
310 | }, |
311 | }; |
312 | |
313 | static int exynos_drm_ipp_task_set(struct exynos_drm_ipp_task *task, |
314 | struct drm_exynos_ioctl_ipp_commit *arg) |
315 | { |
316 | const struct exynos_drm_param_map *map = exynos_drm_ipp_params_maps; |
317 | void __user *params = (void __user *)(unsigned long)arg->params_ptr; |
318 | unsigned int size = arg->params_size; |
319 | uint32_t id; |
320 | int i; |
321 | |
322 | while (size) { |
323 | if (get_user(id, (uint32_t __user *)params)) |
324 | return -EFAULT; |
325 | |
326 | for (i = 0; i < ARRAY_SIZE(exynos_drm_ipp_params_maps); i++) |
327 | if (map[i].id == id) |
328 | break; |
329 | if (i == ARRAY_SIZE(exynos_drm_ipp_params_maps) || |
330 | map[i].size > size) |
331 | return -EINVAL; |
332 | |
333 | if (copy_from_user(to: (void *)task + map[i].offset, from: params, |
334 | n: map[i].size)) |
335 | return -EFAULT; |
336 | |
337 | params += map[i].size; |
338 | size -= map[i].size; |
339 | } |
340 | |
341 | DRM_DEV_DEBUG_DRIVER(task->dev, |
342 | "Got task %pK configuration from userspace\n" , |
343 | task); |
344 | return 0; |
345 | } |
346 | |
347 | static int exynos_drm_ipp_task_setup_buffer(struct exynos_drm_ipp_buffer *buf, |
348 | struct drm_file *filp) |
349 | { |
350 | int ret = 0; |
351 | int i; |
352 | |
353 | /* get GEM buffers and check their size */ |
354 | for (i = 0; i < buf->format->num_planes; i++) { |
355 | unsigned int height = (i == 0) ? buf->buf.height : |
356 | DIV_ROUND_UP(buf->buf.height, buf->format->vsub); |
357 | unsigned long size = height * buf->buf.pitch[i]; |
358 | struct exynos_drm_gem *gem = exynos_drm_gem_get(filp, |
359 | gem_handle: buf->buf.gem_id[i]); |
360 | if (!gem) { |
361 | ret = -ENOENT; |
362 | goto gem_free; |
363 | } |
364 | buf->exynos_gem[i] = gem; |
365 | |
366 | if (size + buf->buf.offset[i] > buf->exynos_gem[i]->size) { |
367 | i++; |
368 | ret = -EINVAL; |
369 | goto gem_free; |
370 | } |
371 | buf->dma_addr[i] = buf->exynos_gem[i]->dma_addr + |
372 | buf->buf.offset[i]; |
373 | } |
374 | |
375 | return 0; |
376 | gem_free: |
377 | while (i--) { |
378 | exynos_drm_gem_put(exynos_gem: buf->exynos_gem[i]); |
379 | buf->exynos_gem[i] = NULL; |
380 | } |
381 | return ret; |
382 | } |
383 | |
384 | static void exynos_drm_ipp_task_release_buf(struct exynos_drm_ipp_buffer *buf) |
385 | { |
386 | int i; |
387 | |
388 | if (!buf->exynos_gem[0]) |
389 | return; |
390 | for (i = 0; i < buf->format->num_planes; i++) |
391 | exynos_drm_gem_put(exynos_gem: buf->exynos_gem[i]); |
392 | } |
393 | |
394 | static void exynos_drm_ipp_task_free(struct exynos_drm_ipp *ipp, |
395 | struct exynos_drm_ipp_task *task) |
396 | { |
397 | DRM_DEV_DEBUG_DRIVER(task->dev, "Freeing task %pK\n" , task); |
398 | |
399 | exynos_drm_ipp_task_release_buf(buf: &task->src); |
400 | exynos_drm_ipp_task_release_buf(buf: &task->dst); |
401 | if (task->event) |
402 | drm_event_cancel_free(dev: ipp->drm_dev, p: &task->event->base); |
403 | kfree(objp: task); |
404 | } |
405 | |
406 | struct drm_ipp_limit { |
407 | struct drm_exynos_ipp_limit_val h; |
408 | struct drm_exynos_ipp_limit_val v; |
409 | }; |
410 | |
411 | enum drm_ipp_size_id { |
412 | IPP_LIMIT_BUFFER, IPP_LIMIT_AREA, IPP_LIMIT_ROTATED, IPP_LIMIT_MAX |
413 | }; |
414 | |
415 | static const enum drm_exynos_ipp_limit_type limit_id_fallback[IPP_LIMIT_MAX][4] = { |
416 | [IPP_LIMIT_BUFFER] = { DRM_EXYNOS_IPP_LIMIT_SIZE_BUFFER }, |
417 | [IPP_LIMIT_AREA] = { DRM_EXYNOS_IPP_LIMIT_SIZE_AREA, |
418 | DRM_EXYNOS_IPP_LIMIT_SIZE_BUFFER }, |
419 | [IPP_LIMIT_ROTATED] = { DRM_EXYNOS_IPP_LIMIT_SIZE_ROTATED, |
420 | DRM_EXYNOS_IPP_LIMIT_SIZE_AREA, |
421 | DRM_EXYNOS_IPP_LIMIT_SIZE_BUFFER }, |
422 | }; |
423 | |
424 | static inline void __limit_set_val(unsigned int *ptr, unsigned int val) |
425 | { |
426 | if (!*ptr) |
427 | *ptr = val; |
428 | } |
429 | |
430 | static void __get_size_limit(const struct drm_exynos_ipp_limit *limits, |
431 | unsigned int num_limits, enum drm_ipp_size_id id, |
432 | struct drm_ipp_limit *res) |
433 | { |
434 | const struct drm_exynos_ipp_limit *l = limits; |
435 | int i = 0; |
436 | |
437 | memset(res, 0, sizeof(*res)); |
438 | for (i = 0; limit_id_fallback[id][i]; i++) |
439 | for (l = limits; l - limits < num_limits; l++) { |
440 | if (((l->type & DRM_EXYNOS_IPP_LIMIT_TYPE_MASK) != |
441 | DRM_EXYNOS_IPP_LIMIT_TYPE_SIZE) || |
442 | ((l->type & DRM_EXYNOS_IPP_LIMIT_SIZE_MASK) != |
443 | limit_id_fallback[id][i])) |
444 | continue; |
445 | __limit_set_val(ptr: &res->h.min, val: l->h.min); |
446 | __limit_set_val(ptr: &res->h.max, val: l->h.max); |
447 | __limit_set_val(ptr: &res->h.align, val: l->h.align); |
448 | __limit_set_val(ptr: &res->v.min, val: l->v.min); |
449 | __limit_set_val(ptr: &res->v.max, val: l->v.max); |
450 | __limit_set_val(ptr: &res->v.align, val: l->v.align); |
451 | } |
452 | } |
453 | |
454 | static inline bool __align_check(unsigned int val, unsigned int align) |
455 | { |
456 | if (align && (val & (align - 1))) { |
457 | DRM_DEBUG_DRIVER("Value %d exceeds HW limits (align %d)\n" , |
458 | val, align); |
459 | return false; |
460 | } |
461 | return true; |
462 | } |
463 | |
464 | static inline bool __size_limit_check(unsigned int val, |
465 | struct drm_exynos_ipp_limit_val *l) |
466 | { |
467 | if ((l->min && val < l->min) || (l->max && val > l->max)) { |
468 | DRM_DEBUG_DRIVER("Value %d exceeds HW limits (min %d, max %d)\n" , |
469 | val, l->min, l->max); |
470 | return false; |
471 | } |
472 | return __align_check(val, align: l->align); |
473 | } |
474 | |
475 | static int exynos_drm_ipp_check_size_limits(struct exynos_drm_ipp_buffer *buf, |
476 | const struct drm_exynos_ipp_limit *limits, unsigned int num_limits, |
477 | bool rotate, bool swap) |
478 | { |
479 | enum drm_ipp_size_id id = rotate ? IPP_LIMIT_ROTATED : IPP_LIMIT_AREA; |
480 | struct drm_ipp_limit l; |
481 | struct drm_exynos_ipp_limit_val *lh = &l.h, *lv = &l.v; |
482 | int real_width = buf->buf.pitch[0] / buf->format->cpp[0]; |
483 | |
484 | if (!limits) |
485 | return 0; |
486 | |
487 | __get_size_limit(limits, num_limits, id: IPP_LIMIT_BUFFER, res: &l); |
488 | if (!__size_limit_check(val: real_width, l: &l.h) || |
489 | !__size_limit_check(val: buf->buf.height, l: &l.v)) |
490 | return -EINVAL; |
491 | |
492 | if (swap) { |
493 | lv = &l.h; |
494 | lh = &l.v; |
495 | } |
496 | __get_size_limit(limits, num_limits, id, res: &l); |
497 | if (!__size_limit_check(val: buf->rect.w, l: lh) || |
498 | !__align_check(val: buf->rect.x, align: lh->align) || |
499 | !__size_limit_check(val: buf->rect.h, l: lv) || |
500 | !__align_check(val: buf->rect.y, align: lv->align)) |
501 | return -EINVAL; |
502 | |
503 | return 0; |
504 | } |
505 | |
506 | static inline bool __scale_limit_check(unsigned int src, unsigned int dst, |
507 | unsigned int min, unsigned int max) |
508 | { |
509 | if ((max && (dst << 16) > src * max) || |
510 | (min && (dst << 16) < src * min)) { |
511 | DRM_DEBUG_DRIVER("Scale from %d to %d exceeds HW limits (ratio min %d.%05d, max %d.%05d)\n" , |
512 | src, dst, |
513 | min >> 16, 100000 * (min & 0xffff) / (1 << 16), |
514 | max >> 16, 100000 * (max & 0xffff) / (1 << 16)); |
515 | return false; |
516 | } |
517 | return true; |
518 | } |
519 | |
520 | static int exynos_drm_ipp_check_scale_limits( |
521 | struct drm_exynos_ipp_task_rect *src, |
522 | struct drm_exynos_ipp_task_rect *dst, |
523 | const struct drm_exynos_ipp_limit *limits, |
524 | unsigned int num_limits, bool swap) |
525 | { |
526 | const struct drm_exynos_ipp_limit_val *lh, *lv; |
527 | int dw, dh; |
528 | |
529 | for (; num_limits; limits++, num_limits--) |
530 | if ((limits->type & DRM_EXYNOS_IPP_LIMIT_TYPE_MASK) == |
531 | DRM_EXYNOS_IPP_LIMIT_TYPE_SCALE) |
532 | break; |
533 | if (!num_limits) |
534 | return 0; |
535 | |
536 | lh = (!swap) ? &limits->h : &limits->v; |
537 | lv = (!swap) ? &limits->v : &limits->h; |
538 | dw = (!swap) ? dst->w : dst->h; |
539 | dh = (!swap) ? dst->h : dst->w; |
540 | |
541 | if (!__scale_limit_check(src: src->w, dst: dw, min: lh->min, max: lh->max) || |
542 | !__scale_limit_check(src: src->h, dst: dh, min: lv->min, max: lv->max)) |
543 | return -EINVAL; |
544 | |
545 | return 0; |
546 | } |
547 | |
548 | static int exynos_drm_ipp_check_format(struct exynos_drm_ipp_task *task, |
549 | struct exynos_drm_ipp_buffer *buf, |
550 | struct exynos_drm_ipp_buffer *src, |
551 | struct exynos_drm_ipp_buffer *dst, |
552 | bool rotate, bool swap) |
553 | { |
554 | const struct exynos_drm_ipp_formats *fmt; |
555 | int ret, i; |
556 | |
557 | fmt = __ipp_format_get(ipp: task->ipp, fourcc: buf->buf.fourcc, mod: buf->buf.modifier, |
558 | type: buf == src ? DRM_EXYNOS_IPP_FORMAT_SOURCE : |
559 | DRM_EXYNOS_IPP_FORMAT_DESTINATION); |
560 | if (!fmt) { |
561 | DRM_DEV_DEBUG_DRIVER(task->dev, |
562 | "Task %pK: %s format not supported\n" , |
563 | task, buf == src ? "src" : "dst" ); |
564 | return -EINVAL; |
565 | } |
566 | |
567 | /* basic checks */ |
568 | if (buf->buf.width == 0 || buf->buf.height == 0) |
569 | return -EINVAL; |
570 | |
571 | buf->format = drm_format_info(format: buf->buf.fourcc); |
572 | for (i = 0; i < buf->format->num_planes; i++) { |
573 | unsigned int width = (i == 0) ? buf->buf.width : |
574 | DIV_ROUND_UP(buf->buf.width, buf->format->hsub); |
575 | |
576 | if (buf->buf.pitch[i] == 0) |
577 | buf->buf.pitch[i] = width * buf->format->cpp[i]; |
578 | if (buf->buf.pitch[i] < width * buf->format->cpp[i]) |
579 | return -EINVAL; |
580 | if (!buf->buf.gem_id[i]) |
581 | return -ENOENT; |
582 | } |
583 | |
584 | /* pitch for additional planes must match */ |
585 | if (buf->format->num_planes > 2 && |
586 | buf->buf.pitch[1] != buf->buf.pitch[2]) |
587 | return -EINVAL; |
588 | |
589 | /* check driver limits */ |
590 | ret = exynos_drm_ipp_check_size_limits(buf, limits: fmt->limits, |
591 | num_limits: fmt->num_limits, |
592 | rotate, |
593 | swap: buf == dst ? swap : false); |
594 | if (ret) |
595 | return ret; |
596 | ret = exynos_drm_ipp_check_scale_limits(src: &src->rect, dst: &dst->rect, |
597 | limits: fmt->limits, |
598 | num_limits: fmt->num_limits, swap); |
599 | return ret; |
600 | } |
601 | |
602 | static int exynos_drm_ipp_task_check(struct exynos_drm_ipp_task *task) |
603 | { |
604 | struct exynos_drm_ipp *ipp = task->ipp; |
605 | struct exynos_drm_ipp_buffer *src = &task->src, *dst = &task->dst; |
606 | unsigned int rotation = task->transform.rotation; |
607 | int ret = 0; |
608 | bool swap = drm_rotation_90_or_270(rotation); |
609 | bool rotate = (rotation != DRM_MODE_ROTATE_0); |
610 | bool scale = false; |
611 | |
612 | DRM_DEV_DEBUG_DRIVER(task->dev, "Checking task %pK\n" , task); |
613 | |
614 | if (src->rect.w == UINT_MAX) |
615 | src->rect.w = src->buf.width; |
616 | if (src->rect.h == UINT_MAX) |
617 | src->rect.h = src->buf.height; |
618 | if (dst->rect.w == UINT_MAX) |
619 | dst->rect.w = dst->buf.width; |
620 | if (dst->rect.h == UINT_MAX) |
621 | dst->rect.h = dst->buf.height; |
622 | |
623 | if (src->rect.x + src->rect.w > (src->buf.width) || |
624 | src->rect.y + src->rect.h > (src->buf.height) || |
625 | dst->rect.x + dst->rect.w > (dst->buf.width) || |
626 | dst->rect.y + dst->rect.h > (dst->buf.height)) { |
627 | DRM_DEV_DEBUG_DRIVER(task->dev, |
628 | "Task %pK: defined area is outside provided buffers\n" , |
629 | task); |
630 | return -EINVAL; |
631 | } |
632 | |
633 | if ((!swap && (src->rect.w != dst->rect.w || |
634 | src->rect.h != dst->rect.h)) || |
635 | (swap && (src->rect.w != dst->rect.h || |
636 | src->rect.h != dst->rect.w))) |
637 | scale = true; |
638 | |
639 | if ((!(ipp->capabilities & DRM_EXYNOS_IPP_CAP_CROP) && |
640 | (src->rect.x || src->rect.y || dst->rect.x || dst->rect.y)) || |
641 | (!(ipp->capabilities & DRM_EXYNOS_IPP_CAP_ROTATE) && rotate) || |
642 | (!(ipp->capabilities & DRM_EXYNOS_IPP_CAP_SCALE) && scale) || |
643 | (!(ipp->capabilities & DRM_EXYNOS_IPP_CAP_CONVERT) && |
644 | src->buf.fourcc != dst->buf.fourcc)) { |
645 | DRM_DEV_DEBUG_DRIVER(task->dev, "Task %pK: hw capabilities exceeded\n" , |
646 | task); |
647 | return -EINVAL; |
648 | } |
649 | |
650 | ret = exynos_drm_ipp_check_format(task, buf: src, src, dst, rotate, swap); |
651 | if (ret) |
652 | return ret; |
653 | |
654 | ret = exynos_drm_ipp_check_format(task, buf: dst, src, dst, rotate: false, swap); |
655 | if (ret) |
656 | return ret; |
657 | |
658 | DRM_DEV_DEBUG_DRIVER(ipp->dev, "Task %pK: all checks done.\n" , |
659 | task); |
660 | |
661 | return ret; |
662 | } |
663 | |
664 | static int exynos_drm_ipp_task_setup_buffers(struct exynos_drm_ipp_task *task, |
665 | struct drm_file *filp) |
666 | { |
667 | struct exynos_drm_ipp_buffer *src = &task->src, *dst = &task->dst; |
668 | int ret = 0; |
669 | |
670 | DRM_DEV_DEBUG_DRIVER(task->dev, "Setting buffer for task %pK\n" , |
671 | task); |
672 | |
673 | ret = exynos_drm_ipp_task_setup_buffer(buf: src, filp); |
674 | if (ret) { |
675 | DRM_DEV_DEBUG_DRIVER(task->dev, |
676 | "Task %pK: src buffer setup failed\n" , |
677 | task); |
678 | return ret; |
679 | } |
680 | ret = exynos_drm_ipp_task_setup_buffer(buf: dst, filp); |
681 | if (ret) { |
682 | DRM_DEV_DEBUG_DRIVER(task->dev, |
683 | "Task %pK: dst buffer setup failed\n" , |
684 | task); |
685 | return ret; |
686 | } |
687 | |
688 | DRM_DEV_DEBUG_DRIVER(task->dev, "Task %pK: buffers prepared.\n" , |
689 | task); |
690 | |
691 | return ret; |
692 | } |
693 | |
694 | |
695 | static int exynos_drm_ipp_event_create(struct exynos_drm_ipp_task *task, |
696 | struct drm_file *file_priv, uint64_t user_data) |
697 | { |
698 | struct drm_pending_exynos_ipp_event *e = NULL; |
699 | int ret; |
700 | |
701 | e = kzalloc(size: sizeof(*e), GFP_KERNEL); |
702 | if (!e) |
703 | return -ENOMEM; |
704 | |
705 | e->event.base.type = DRM_EXYNOS_IPP_EVENT; |
706 | e->event.base.length = sizeof(e->event); |
707 | e->event.user_data = user_data; |
708 | |
709 | ret = drm_event_reserve_init(dev: task->ipp->drm_dev, file_priv, p: &e->base, |
710 | e: &e->event.base); |
711 | if (ret) |
712 | goto free; |
713 | |
714 | task->event = e; |
715 | return 0; |
716 | free: |
717 | kfree(objp: e); |
718 | return ret; |
719 | } |
720 | |
721 | static void exynos_drm_ipp_event_send(struct exynos_drm_ipp_task *task) |
722 | { |
723 | struct timespec64 now; |
724 | |
725 | ktime_get_ts64(ts: &now); |
726 | task->event->event.tv_sec = now.tv_sec; |
727 | task->event->event.tv_usec = now.tv_nsec / NSEC_PER_USEC; |
728 | task->event->event.sequence = atomic_inc_return(v: &task->ipp->sequence); |
729 | |
730 | drm_send_event(dev: task->ipp->drm_dev, e: &task->event->base); |
731 | } |
732 | |
733 | static int exynos_drm_ipp_task_cleanup(struct exynos_drm_ipp_task *task) |
734 | { |
735 | int ret = task->ret; |
736 | |
737 | if (ret == 0 && task->event) { |
738 | exynos_drm_ipp_event_send(task); |
739 | /* ensure event won't be canceled on task free */ |
740 | task->event = NULL; |
741 | } |
742 | |
743 | exynos_drm_ipp_task_free(ipp: task->ipp, task); |
744 | return ret; |
745 | } |
746 | |
747 | static void exynos_drm_ipp_cleanup_work(struct work_struct *work) |
748 | { |
749 | struct exynos_drm_ipp_task *task = container_of(work, |
750 | struct exynos_drm_ipp_task, cleanup_work); |
751 | |
752 | exynos_drm_ipp_task_cleanup(task); |
753 | } |
754 | |
755 | static void exynos_drm_ipp_next_task(struct exynos_drm_ipp *ipp); |
756 | |
757 | /** |
758 | * exynos_drm_ipp_task_done - finish given task and set return code |
759 | * @task: ipp task to finish |
760 | * @ret: error code or 0 if operation has been performed successfully |
761 | */ |
762 | void exynos_drm_ipp_task_done(struct exynos_drm_ipp_task *task, int ret) |
763 | { |
764 | struct exynos_drm_ipp *ipp = task->ipp; |
765 | unsigned long flags; |
766 | |
767 | DRM_DEV_DEBUG_DRIVER(task->dev, "ipp: %d, task %pK done: %d\n" , |
768 | ipp->id, task, ret); |
769 | |
770 | spin_lock_irqsave(&ipp->lock, flags); |
771 | if (ipp->task == task) |
772 | ipp->task = NULL; |
773 | task->flags |= DRM_EXYNOS_IPP_TASK_DONE; |
774 | task->ret = ret; |
775 | spin_unlock_irqrestore(lock: &ipp->lock, flags); |
776 | |
777 | exynos_drm_ipp_next_task(ipp); |
778 | wake_up(&ipp->done_wq); |
779 | |
780 | if (task->flags & DRM_EXYNOS_IPP_TASK_ASYNC) { |
781 | INIT_WORK(&task->cleanup_work, exynos_drm_ipp_cleanup_work); |
782 | schedule_work(work: &task->cleanup_work); |
783 | } |
784 | } |
785 | |
786 | static void exynos_drm_ipp_next_task(struct exynos_drm_ipp *ipp) |
787 | { |
788 | struct exynos_drm_ipp_task *task; |
789 | unsigned long flags; |
790 | int ret; |
791 | |
792 | DRM_DEV_DEBUG_DRIVER(ipp->dev, "ipp: %d, try to run new task\n" , |
793 | ipp->id); |
794 | |
795 | spin_lock_irqsave(&ipp->lock, flags); |
796 | |
797 | if (ipp->task || list_empty(head: &ipp->todo_list)) { |
798 | spin_unlock_irqrestore(lock: &ipp->lock, flags); |
799 | return; |
800 | } |
801 | |
802 | task = list_first_entry(&ipp->todo_list, struct exynos_drm_ipp_task, |
803 | head); |
804 | list_del_init(entry: &task->head); |
805 | ipp->task = task; |
806 | |
807 | spin_unlock_irqrestore(lock: &ipp->lock, flags); |
808 | |
809 | DRM_DEV_DEBUG_DRIVER(ipp->dev, |
810 | "ipp: %d, selected task %pK to run\n" , ipp->id, |
811 | task); |
812 | |
813 | ret = ipp->funcs->commit(ipp, task); |
814 | if (ret) |
815 | exynos_drm_ipp_task_done(task, ret); |
816 | } |
817 | |
818 | static void exynos_drm_ipp_schedule_task(struct exynos_drm_ipp *ipp, |
819 | struct exynos_drm_ipp_task *task) |
820 | { |
821 | unsigned long flags; |
822 | |
823 | spin_lock_irqsave(&ipp->lock, flags); |
824 | list_add(new: &task->head, head: &ipp->todo_list); |
825 | spin_unlock_irqrestore(lock: &ipp->lock, flags); |
826 | |
827 | exynos_drm_ipp_next_task(ipp); |
828 | } |
829 | |
830 | static void exynos_drm_ipp_task_abort(struct exynos_drm_ipp *ipp, |
831 | struct exynos_drm_ipp_task *task) |
832 | { |
833 | unsigned long flags; |
834 | |
835 | spin_lock_irqsave(&ipp->lock, flags); |
836 | if (task->flags & DRM_EXYNOS_IPP_TASK_DONE) { |
837 | /* already completed task */ |
838 | exynos_drm_ipp_task_cleanup(task); |
839 | } else if (ipp->task != task) { |
840 | /* task has not been scheduled for execution yet */ |
841 | list_del_init(entry: &task->head); |
842 | exynos_drm_ipp_task_cleanup(task); |
843 | } else { |
844 | /* |
845 | * currently processed task, call abort() and perform |
846 | * cleanup with async worker |
847 | */ |
848 | task->flags |= DRM_EXYNOS_IPP_TASK_ASYNC; |
849 | spin_unlock_irqrestore(lock: &ipp->lock, flags); |
850 | if (ipp->funcs->abort) |
851 | ipp->funcs->abort(ipp, task); |
852 | return; |
853 | } |
854 | spin_unlock_irqrestore(lock: &ipp->lock, flags); |
855 | } |
856 | |
857 | /** |
858 | * exynos_drm_ipp_commit_ioctl - perform image processing operation |
859 | * @dev: DRM device |
860 | * @data: ioctl data |
861 | * @file_priv: DRM file info |
862 | * |
863 | * Construct a ipp task from the set of properties provided from the user |
864 | * and try to schedule it to framebuffer processor hardware. |
865 | * |
866 | * Called by the user via ioctl. |
867 | * |
868 | * Returns: |
869 | * Zero on success, negative errno on failure. |
870 | */ |
871 | int exynos_drm_ipp_commit_ioctl(struct drm_device *dev, void *data, |
872 | struct drm_file *file_priv) |
873 | { |
874 | struct drm_exynos_ioctl_ipp_commit *arg = data; |
875 | struct exynos_drm_ipp *ipp; |
876 | struct exynos_drm_ipp_task *task; |
877 | int ret = 0; |
878 | |
879 | if ((arg->flags & ~DRM_EXYNOS_IPP_FLAGS) || arg->reserved) |
880 | return -EINVAL; |
881 | |
882 | /* can't test and expect an event at the same time */ |
883 | if ((arg->flags & DRM_EXYNOS_IPP_FLAG_TEST_ONLY) && |
884 | (arg->flags & DRM_EXYNOS_IPP_FLAG_EVENT)) |
885 | return -EINVAL; |
886 | |
887 | ipp = __ipp_get(id: arg->ipp_id); |
888 | if (!ipp) |
889 | return -ENOENT; |
890 | |
891 | task = exynos_drm_ipp_task_alloc(ipp); |
892 | if (!task) |
893 | return -ENOMEM; |
894 | |
895 | ret = exynos_drm_ipp_task_set(task, arg); |
896 | if (ret) |
897 | goto free; |
898 | |
899 | ret = exynos_drm_ipp_task_check(task); |
900 | if (ret) |
901 | goto free; |
902 | |
903 | ret = exynos_drm_ipp_task_setup_buffers(task, filp: file_priv); |
904 | if (ret || arg->flags & DRM_EXYNOS_IPP_FLAG_TEST_ONLY) |
905 | goto free; |
906 | |
907 | if (arg->flags & DRM_EXYNOS_IPP_FLAG_EVENT) { |
908 | ret = exynos_drm_ipp_event_create(task, file_priv, |
909 | user_data: arg->user_data); |
910 | if (ret) |
911 | goto free; |
912 | } |
913 | |
914 | /* |
915 | * Queue task for processing on the hardware. task object will be |
916 | * then freed after exynos_drm_ipp_task_done() |
917 | */ |
918 | if (arg->flags & DRM_EXYNOS_IPP_FLAG_NONBLOCK) { |
919 | DRM_DEV_DEBUG_DRIVER(ipp->dev, |
920 | "ipp: %d, nonblocking processing task %pK\n" , |
921 | ipp->id, task); |
922 | |
923 | task->flags |= DRM_EXYNOS_IPP_TASK_ASYNC; |
924 | exynos_drm_ipp_schedule_task(ipp: task->ipp, task); |
925 | ret = 0; |
926 | } else { |
927 | DRM_DEV_DEBUG_DRIVER(ipp->dev, "ipp: %d, processing task %pK\n" , |
928 | ipp->id, task); |
929 | exynos_drm_ipp_schedule_task(ipp, task); |
930 | ret = wait_event_interruptible(ipp->done_wq, |
931 | task->flags & DRM_EXYNOS_IPP_TASK_DONE); |
932 | if (ret) |
933 | exynos_drm_ipp_task_abort(ipp, task); |
934 | else |
935 | ret = exynos_drm_ipp_task_cleanup(task); |
936 | } |
937 | return ret; |
938 | free: |
939 | exynos_drm_ipp_task_free(ipp, task); |
940 | |
941 | return ret; |
942 | } |
943 | |