1 /*
2 * va_wayland_drm.c - Wayland/linux-dmabuf helpers
3 *
4 * Copyright (c) 2024 Simon Ser
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sub license, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
13 *
14 * The above copyright notice and this permission notice (including the
15 * next paragraph) shall be included in all copies or substantial portions
16 * of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
21 * IN NO EVENT SHALL INTEL AND/OR ITS SUPPLIERS BE LIABLE FOR
22 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 */
26
27 #include "sysdeps.h"
28 #include <unistd.h>
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <dlfcn.h>
32 #include <sys/stat.h>
33 #include <xf86drm.h>
34 #include "va_drmcommon.h"
35 #include "drm/va_drm_utils.h"
36 #include "va_wayland_linux_dmabuf.h"
37 #include "va_wayland_private.h"
38 #include "linux-dmabuf-v1-client-protocol.h"
39
40 typedef struct va_wayland_linux_dmabuf_context {
41 struct va_wayland_context base;
42 bool has_linux_dmabuf;
43 bool default_feedback_done;
44 } VADisplayContextWaylandLinuxDmabuf;
45
46 static void
feedback_handle_done(void * data,struct zwp_linux_dmabuf_feedback_v1 * feedback)47 feedback_handle_done(
48 void *data,
49 struct zwp_linux_dmabuf_feedback_v1 *feedback
50 )
51 {
52 VADisplayContextP const pDisplayContext = data;
53 struct va_wayland_linux_dmabuf_context *wl_linux_dmabuf_ctx = pDisplayContext->opaque;
54
55 wl_linux_dmabuf_ctx->default_feedback_done = true;
56
57 zwp_linux_dmabuf_feedback_v1_destroy(feedback);
58 }
59
60 static void
feedback_handle_format_table(void * data,struct zwp_linux_dmabuf_feedback_v1 * feedback,int fd,uint32_t size)61 feedback_handle_format_table(
62 void *data,
63 struct zwp_linux_dmabuf_feedback_v1 *feedback,
64 int fd,
65 uint32_t size
66 )
67 {
68 close(fd);
69 }
70
71 /* XXX: replace with drmGetDeviceFromDevId() */
72 static drmDevice *
get_drm_device_from_dev_id(dev_t dev_id)73 get_drm_device_from_dev_id(dev_t dev_id)
74 {
75 uint32_t flags = 0;
76 int devices_len, i, node_type;
77 drmDevice *match = NULL, *dev;
78 struct stat statbuf;
79
80 devices_len = drmGetDevices2(flags, NULL, 0);
81 if (devices_len < 0) {
82 return NULL;
83 }
84 drmDevice **devices = calloc(devices_len, sizeof(*devices));
85 if (devices == NULL) {
86 return NULL;
87 }
88 devices_len = drmGetDevices2(flags, devices, devices_len);
89 if (devices_len < 0) {
90 free(devices);
91 return NULL;
92 }
93
94 for (i = 0; i < devices_len; i++) {
95 dev = devices[i];
96 for (node_type = 0; node_type < DRM_NODE_MAX; node_type++) {
97 if (!(dev->available_nodes & (1 << node_type)))
98 continue;
99
100 if (stat(dev->nodes[node_type], &statbuf) != 0) {
101 va_wayland_error("stat() failed for %s", dev->nodes[node_type]);
102 continue;
103 }
104
105 if (statbuf.st_rdev == dev_id) {
106 match = dev;
107 break;
108 }
109 }
110 }
111
112 for (i = 0; i < devices_len; i++) {
113 dev = devices[i];
114 if (dev != match)
115 drmFreeDevice(&dev);
116 }
117 free(devices);
118
119 return match;
120 }
121
122 static void
feedback_handle_main_device(void * data,struct zwp_linux_dmabuf_feedback_v1 * feedback,struct wl_array * device_array)123 feedback_handle_main_device(
124 void *data,
125 struct zwp_linux_dmabuf_feedback_v1 *feedback,
126 struct wl_array *device_array
127 )
128 {
129 dev_t dev_id;
130 drmDevice *dev;
131 const char *dev_path;
132 VADisplayContextP const pDisplayContext = data;
133 VADriverContextP const ctx = pDisplayContext->pDriverContext;
134 struct drm_state * const drm_state = ctx->drm_state;
135
136 assert(device_array->size == sizeof(dev_id));
137 memcpy(&dev_id, device_array->data, sizeof(dev_id));
138
139 dev = get_drm_device_from_dev_id(dev_id);
140 if (!dev) {
141 va_wayland_error("failed to get DRM device from device ID");
142 return;
143 }
144
145 if (!(dev->available_nodes & (1 << DRM_NODE_RENDER)))
146 goto end;
147
148 dev_path = dev->nodes[DRM_NODE_RENDER];
149 drm_state->fd = open(dev_path, O_RDWR | O_CLOEXEC);
150 if (drm_state->fd < 0) {
151 va_wayland_error("failed to open %s", dev_path);
152 goto end;
153 }
154
155 drm_state->auth_type = VA_DRM_AUTH_CUSTOM;
156
157 end:
158 drmFreeDevice(&dev);
159 }
160
161 static void
feedback_handle_tranche_done(void * data,struct zwp_linux_dmabuf_feedback_v1 * feedback)162 feedback_handle_tranche_done(
163 void *data,
164 struct zwp_linux_dmabuf_feedback_v1 *feedback
165 )
166 {
167 }
168
169 static void
feedback_handle_tranche_target_device(void * data,struct zwp_linux_dmabuf_feedback_v1 * feedback,struct wl_array * device_array)170 feedback_handle_tranche_target_device(
171 void *data,
172 struct zwp_linux_dmabuf_feedback_v1 *feedback,
173 struct wl_array *device_array
174 )
175 {
176 }
177
178 static void
feedback_handle_tranche_formats(void * data,struct zwp_linux_dmabuf_feedback_v1 * feedback,struct wl_array * indices_array)179 feedback_handle_tranche_formats(
180 void *data,
181 struct zwp_linux_dmabuf_feedback_v1 *feedback,
182 struct wl_array *indices_array
183 )
184 {
185 }
186
187 static void
feedback_handle_tranche_flags(void * data,struct zwp_linux_dmabuf_feedback_v1 * feedback,uint32_t flags)188 feedback_handle_tranche_flags(
189 void *data,
190 struct zwp_linux_dmabuf_feedback_v1 *feedback,
191 uint32_t flags
192 )
193 {
194 }
195
196 static const struct zwp_linux_dmabuf_feedback_v1_listener feedback_listener = {
197 .done = feedback_handle_done,
198 .format_table = feedback_handle_format_table,
199 .main_device = feedback_handle_main_device,
200 .tranche_done = feedback_handle_tranche_done,
201 .tranche_target_device = feedback_handle_tranche_target_device,
202 .tranche_formats = feedback_handle_tranche_formats,
203 .tranche_flags = feedback_handle_tranche_flags,
204 };
205
206 static void
registry_handle_global(void * data,struct wl_registry * registry,uint32_t name,const char * interface,uint32_t version)207 registry_handle_global(
208 void *data,
209 struct wl_registry *registry,
210 uint32_t name,
211 const char *interface,
212 uint32_t version
213 )
214 {
215 VADisplayContextP const pDisplayContext = data;
216 struct va_wayland_linux_dmabuf_context *wl_linux_dmabuf_ctx = pDisplayContext->opaque;
217 struct zwp_linux_dmabuf_v1 *linux_dmabuf;
218 struct zwp_linux_dmabuf_feedback_v1 *feedback;
219
220 if (strcmp(interface, zwp_linux_dmabuf_v1_interface.name) == 0 &&
221 version >= 4) {
222 wl_linux_dmabuf_ctx->has_linux_dmabuf = true;
223 linux_dmabuf =
224 wl_registry_bind(registry, name, &zwp_linux_dmabuf_v1_interface, 4);
225 feedback = zwp_linux_dmabuf_v1_get_default_feedback(linux_dmabuf);
226 zwp_linux_dmabuf_feedback_v1_add_listener(feedback, &feedback_listener, data);
227 zwp_linux_dmabuf_v1_destroy(linux_dmabuf);
228 }
229 }
230
231 static void
registry_handle_global_remove(void * data,struct wl_registry * registry,uint32_t name)232 registry_handle_global_remove(
233 void *data,
234 struct wl_registry *registry,
235 uint32_t name
236 )
237 {
238 }
239
240 static const struct wl_registry_listener registry_listener = {
241 .global = registry_handle_global,
242 .global_remove = registry_handle_global_remove,
243 };
244
245 static VAStatus
va_DisplayContextGetDriverNames(VADisplayContextP pDisplayContext,char ** drivers,unsigned * num_drivers)246 va_DisplayContextGetDriverNames(
247 VADisplayContextP pDisplayContext,
248 char **drivers,
249 unsigned *num_drivers
250 )
251 {
252 VADriverContextP const ctx = pDisplayContext->pDriverContext;
253
254 return VA_DRM_GetDriverNames(ctx, drivers, num_drivers);
255 }
256
257 bool
va_wayland_linux_dmabuf_create(VADisplayContextP pDisplayContext)258 va_wayland_linux_dmabuf_create(VADisplayContextP pDisplayContext)
259 {
260 bool result = false;
261 VADriverContextP const ctx = pDisplayContext->pDriverContext;
262 struct VADriverVTableWayland *vtable = ctx->vtable_wayland;
263 struct va_wayland_linux_dmabuf_context *wl_linux_dmabuf_ctx;
264 struct drm_state *drm_state;
265 struct wl_event_queue *queue = NULL;
266 struct wl_display *display = NULL;
267 struct wl_registry *registry = NULL;
268
269 wl_linux_dmabuf_ctx = calloc(1, sizeof(*wl_linux_dmabuf_ctx));
270 if (!wl_linux_dmabuf_ctx) {
271 va_wayland_error("could not allocate wl_linux_dmabuf_ctx");
272 goto end;
273 }
274 wl_linux_dmabuf_ctx->base.destroy = va_wayland_linux_dmabuf_destroy;
275 pDisplayContext->opaque = wl_linux_dmabuf_ctx;
276 pDisplayContext->vaGetDriverNames = va_DisplayContextGetDriverNames;
277
278 drm_state = calloc(1, sizeof(*drm_state));
279 if (!drm_state) {
280 va_wayland_error("could not allocate drm_state");
281 goto end;
282 }
283 drm_state->fd = -1;
284 drm_state->auth_type = 0;
285 ctx->drm_state = drm_state;
286
287 vtable->has_prime_sharing = 0;
288
289 /* Use wrapped wl_display with private event queue to prevent
290 * thread safety issues with applications that e.g. run an event pump
291 * parallel to libva initialization.
292 * Using the default queue, events might get lost and crashes occur
293 * because wl_display_roundtrip is not thread-safe with respect to the
294 * same queue.
295 */
296 queue = wl_display_create_queue(ctx->native_dpy);
297 if (!queue) {
298 va_wayland_error("could not create Wayland event queue");
299 goto end;
300 }
301
302 display = wl_proxy_create_wrapper(ctx->native_dpy);
303 if (!display) {
304 va_wayland_error("could not create Wayland proxy wrapper");
305 goto end;
306 }
307 wl_proxy_set_queue((struct wl_proxy *) display, queue);
308
309 registry = wl_display_get_registry(display);
310 if (!registry) {
311 va_wayland_error("could not create wl_registry");
312 goto end;
313 }
314 wl_registry_add_listener(registry, ®istry_listener, pDisplayContext);
315
316 if (wl_display_roundtrip_queue(ctx->native_dpy, queue) < 0) {
317 va_wayland_error("failed to roundtrip Wayland queue");
318 goto end;
319 }
320
321 if (!wl_linux_dmabuf_ctx->has_linux_dmabuf)
322 goto end;
323
324 while (!wl_linux_dmabuf_ctx->default_feedback_done) {
325 if (wl_display_dispatch_queue(ctx->native_dpy, queue) < 0) {
326 va_wayland_error("failed to dispatch Wayland queue");
327 goto end;
328 }
329 }
330
331 if (drm_state->fd < 0)
332 goto end;
333
334 result = true;
335 vtable->has_prime_sharing = true;
336
337 end:
338 if (registry)
339 wl_registry_destroy(registry);
340 if (display)
341 wl_proxy_wrapper_destroy(display);
342 if (queue)
343 wl_event_queue_destroy(queue);
344 return result;
345 }
346
347 void
va_wayland_linux_dmabuf_destroy(VADisplayContextP pDisplayContext)348 va_wayland_linux_dmabuf_destroy(VADisplayContextP pDisplayContext)
349 {
350 VADriverContextP const ctx = pDisplayContext->pDriverContext;
351 struct drm_state * const drm_state = ctx->drm_state;
352 struct VADriverVTableWayland *vtable = ctx->vtable_wayland;
353
354 vtable->has_prime_sharing = 0;
355
356 if (drm_state) {
357 if (drm_state->fd >= 0) {
358 close(drm_state->fd);
359 drm_state->fd = -1;
360 }
361 free(ctx->drm_state);
362 ctx->drm_state = NULL;
363 }
364 }
365