1 /*
2 * va_wayland_drm.c - Wayland/DRM helpers
3 *
4 * Copyright (c) 2012 Intel Corporation. All Rights Reserved.
5 * Copyright (c) 2023 Emil Velikov
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a
8 * copy of this software and associated documentation files (the
9 * "Software"), to deal in the Software without restriction, including
10 * without limitation the rights to use, copy, modify, merge, publish,
11 * distribute, sub license, and/or sell copies of the Software, and to
12 * permit persons to whom the Software is furnished to do so, subject to
13 * the following conditions:
14 *
15 * The above copyright notice and this permission notice (including the
16 * next paragraph) shall be included in all copies or substantial portions
17 * of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
22 * IN NO EVENT SHALL INTEL AND/OR ITS SUPPLIERS BE LIABLE FOR
23 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 */
27
28 #include "sysdeps.h"
29 #include <unistd.h>
30 #include <errno.h>
31 #include <fcntl.h>
32 #include <dlfcn.h>
33 #include <sys/stat.h>
34 #include <xf86drm.h>
35 #include "va_drmcommon.h"
36 #include "drm/va_drm_utils.h"
37 #include "va_wayland_drm.h"
38 #include "va_wayland_private.h"
39 #include "wayland-drm-client-protocol.h"
40
41 typedef struct va_wayland_drm_context {
42 struct va_wayland_context base;
43 struct wl_event_queue *queue;
44 struct wl_drm *drm;
45 uint32_t drm_name;
46 struct wl_registry *registry;
47 unsigned int is_authenticated : 1;
48 } VADisplayContextWaylandDRM;
49
50 static void
drm_handle_device(void * data,struct wl_drm * drm,const char * device)51 drm_handle_device(void *data, struct wl_drm *drm, const char *device)
52 {
53 VADisplayContextP const pDisplayContext = data;
54 VADriverContextP const ctx = pDisplayContext->pDriverContext;
55 VADisplayContextWaylandDRM * const wl_drm_ctx = pDisplayContext->opaque;
56 struct drm_state * const drm_state = ctx->drm_state;
57 drm_magic_t magic;
58 struct stat st;
59 int fd = -1;
60
61 fd = open(device, O_RDWR);
62 if (fd < 0) {
63 va_wayland_error("failed to open %s: %s (errno %d)",
64 device, strerror(errno), errno);
65 return;
66 }
67
68 if (fstat(fd, &st) < 0) {
69 va_wayland_error("failed to identify %s: %s (errno %d)",
70 device, strerror(errno), errno);
71 close(fd);
72 return;
73 }
74
75 if (!S_ISCHR(st.st_mode)) {
76 va_wayland_error("%s is not a device", device);
77 close(fd);
78 return;
79 }
80
81 drm_state->fd = fd;
82
83 int type = drmGetNodeTypeFromFd(fd);
84 if (type != DRM_NODE_RENDER) {
85 drmGetMagic(drm_state->fd, &magic);
86 wl_drm_authenticate(wl_drm_ctx->drm, magic);
87 } else {
88 wl_drm_ctx->is_authenticated = 1;
89 drm_state->auth_type = VA_DRM_AUTH_CUSTOM;
90 }
91 }
92
93 static void
drm_handle_format(void * data,struct wl_drm * drm,uint32_t format)94 drm_handle_format(void *data, struct wl_drm *drm, uint32_t format)
95 {
96 }
97
98 static void
drm_handle_authenticated(void * data,struct wl_drm * drm)99 drm_handle_authenticated(void *data, struct wl_drm *drm)
100 {
101 VADisplayContextP const pDisplayContext = data;
102 VADriverContextP const ctx = pDisplayContext->pDriverContext;
103 VADisplayContextWaylandDRM * const wl_drm_ctx = pDisplayContext->opaque;
104 struct drm_state * const drm_state = ctx->drm_state;
105
106 wl_drm_ctx->is_authenticated = 1;
107 drm_state->auth_type = VA_DRM_AUTH_CUSTOM;
108 }
109
110 static void
drm_handle_capabilities(void * data,struct wl_drm * wl_drm,uint32_t value)111 drm_handle_capabilities(void *data, struct wl_drm *wl_drm, uint32_t value)
112 {
113 VADisplayContextP const pDisplayContext = data;
114 VADriverContextP const ctx = pDisplayContext->pDriverContext;
115 struct VADriverVTableWayland *vtable = ctx->vtable_wayland;
116
117 vtable->has_prime_sharing = !!(value & WL_DRM_CAPABILITY_PRIME);
118 }
119
120 static const struct wl_drm_listener drm_listener = {
121 drm_handle_device,
122 drm_handle_format,
123 drm_handle_authenticated,
124 drm_handle_capabilities,
125 };
126
127 static VAStatus
va_DisplayContextGetDriverNames(VADisplayContextP pDisplayContext,char ** drivers,unsigned * num_drivers)128 va_DisplayContextGetDriverNames(
129 VADisplayContextP pDisplayContext,
130 char **drivers,
131 unsigned *num_drivers
132 )
133 {
134 VADriverContextP const ctx = pDisplayContext->pDriverContext;
135
136 return VA_DRM_GetDriverNames(ctx, drivers, num_drivers);
137 }
138
139 void
va_wayland_drm_destroy(VADisplayContextP pDisplayContext)140 va_wayland_drm_destroy(VADisplayContextP pDisplayContext)
141 {
142 VADriverContextP const ctx = pDisplayContext->pDriverContext;
143 struct va_wayland_drm_context * const wl_drm_ctx = pDisplayContext->opaque;
144 struct drm_state * const drm_state = ctx->drm_state;
145 struct VADriverVTableWayland *vtable = ctx->vtable_wayland;
146
147 vtable->has_prime_sharing = 0;
148 vtable->wl_interface = NULL;
149
150 wl_drm_ctx->is_authenticated = 0;
151
152 if (drm_state) {
153 if (drm_state->fd >= 0) {
154 close(drm_state->fd);
155 drm_state->fd = -1;
156 }
157 free(ctx->drm_state);
158 ctx->drm_state = NULL;
159 }
160 }
161
162 static void
registry_handle_global(void * data,struct wl_registry * registry,uint32_t name,const char * interface,uint32_t version)163 registry_handle_global(
164 void *data,
165 struct wl_registry *registry,
166 uint32_t name,
167 const char *interface,
168 uint32_t version
169 )
170 {
171 VADisplayContextP const pDisplayContext = data;
172 struct va_wayland_drm_context * const wl_drm_ctx = pDisplayContext->opaque;
173
174 if (strcmp(interface, "wl_drm") == 0) {
175 /* bind to at most version 2, but also support version 1 if
176 * compositor does not have v2
177 */
178 wl_drm_ctx->drm_name = name;
179 wl_drm_ctx->drm =
180 wl_registry_bind(wl_drm_ctx->registry, name, &wl_drm_interface,
181 (version < 2) ? version : 2);
182
183 if (wl_drm_ctx->drm
184 && wl_drm_add_listener(wl_drm_ctx->drm, &drm_listener, pDisplayContext) != 0) {
185 va_wayland_error("could not add listener to wl_drm");
186 wl_drm_destroy(wl_drm_ctx->drm);
187 wl_drm_ctx->drm = NULL;
188 }
189 }
190 }
191
192 static void
registry_handle_global_remove(void * data,struct wl_registry * registry,uint32_t name)193 registry_handle_global_remove(
194 void *data,
195 struct wl_registry *registry,
196 uint32_t name
197 )
198 {
199 struct va_wayland_drm_context *wl_drm_ctx = data;
200
201 if (wl_drm_ctx->drm && name == wl_drm_ctx->drm_name) {
202 wl_drm_destroy(wl_drm_ctx->drm);
203 wl_drm_ctx->drm = NULL;
204 }
205 }
206
207 static const struct wl_registry_listener registry_listener = {
208 registry_handle_global,
209 registry_handle_global_remove
210 };
211
212 static bool
wayland_roundtrip_queue(struct wl_display * display,struct wl_event_queue * queue)213 wayland_roundtrip_queue(struct wl_display *display,
214 struct wl_event_queue *queue)
215 {
216 if (wl_display_roundtrip_queue(display, queue) < 0) {
217 int err = wl_display_get_error(display);
218 va_wayland_error("Wayland roundtrip error: %s (errno %d)", strerror(err), err);
219 return false;
220 } else {
221 return true;
222 }
223 }
224
225 bool
va_wayland_drm_create(VADisplayContextP pDisplayContext)226 va_wayland_drm_create(VADisplayContextP pDisplayContext)
227 {
228 bool result = false;
229 VADriverContextP const ctx = pDisplayContext->pDriverContext;
230 struct va_wayland_drm_context *wl_drm_ctx;
231 struct drm_state *drm_state;
232 struct VADriverVTableWayland *vtable = ctx->vtable_wayland;
233 struct wl_display *wrapped_display = NULL;
234
235 vtable->wl_interface = NULL;
236
237 wl_drm_ctx = malloc(sizeof(*wl_drm_ctx));
238 if (!wl_drm_ctx) {
239 va_wayland_error("could not allocate wl_drm_ctx");
240 goto end;
241 }
242 wl_drm_ctx->base.destroy = va_wayland_drm_destroy;
243 wl_drm_ctx->queue = NULL;
244 wl_drm_ctx->drm = NULL;
245 wl_drm_ctx->registry = NULL;
246 wl_drm_ctx->is_authenticated = 0;
247 pDisplayContext->opaque = wl_drm_ctx;
248 pDisplayContext->vaGetDriverNames = va_DisplayContextGetDriverNames;
249
250 drm_state = calloc(1, sizeof(struct drm_state));
251 if (!drm_state) {
252 va_wayland_error("could not allocate drm_state");
253 goto end;
254 }
255 drm_state->fd = -1;
256 drm_state->auth_type = 0;
257 ctx->drm_state = drm_state;
258 vtable->has_prime_sharing = 0;
259
260 /* Use wrapped wl_display with private event queue to prevent
261 * thread safety issues with applications that e.g. run an event pump
262 * parallel to libva initialization.
263 * Using the default queue, events might get lost and crashes occur
264 * because wl_display_roundtrip is not thread-safe with respect to the
265 * same queue.
266 */
267 wl_drm_ctx->queue = wl_display_create_queue(ctx->native_dpy);
268 if (!wl_drm_ctx->queue) {
269 va_wayland_error("could not create Wayland event queue");
270 goto end;
271 }
272
273 wrapped_display = wl_proxy_create_wrapper(ctx->native_dpy);
274 if (!wrapped_display) {
275 va_wayland_error("could not create Wayland proxy wrapper");
276 goto end;
277 }
278
279 /* All created objects will inherit this queue */
280 wl_proxy_set_queue((struct wl_proxy *) wrapped_display, wl_drm_ctx->queue);
281 wl_drm_ctx->registry = wl_display_get_registry(wrapped_display);
282 if (!wl_drm_ctx->registry) {
283 va_wayland_error("could not create wl_registry");
284 goto end;
285 }
286 if (wl_registry_add_listener(wl_drm_ctx->registry, ®istry_listener, pDisplayContext) != 0) {
287 va_wayland_error("could not add listener to wl_registry");
288 goto end;
289 }
290 if (!wayland_roundtrip_queue(ctx->native_dpy, wl_drm_ctx->queue))
291 goto end;
292
293 /* registry_handle_global should have been called by the
294 * wl_display_roundtrip_queue above
295 */
296
297 /* Do not print an error, the compositor might just not support wl_drm */
298 if (!wl_drm_ctx->drm)
299 goto end;
300 if (!wayland_roundtrip_queue(ctx->native_dpy, wl_drm_ctx->queue))
301 goto end;
302 if (drm_state->fd < 0) {
303 va_wayland_error("did not get DRM device");
304 goto end;
305 }
306
307 if (!wayland_roundtrip_queue(ctx->native_dpy, wl_drm_ctx->queue))
308 goto end;
309
310 if (!wl_drm_ctx->is_authenticated) {
311 va_wayland_error("Wayland compositor did not respond to DRM authentication");
312 goto end;
313 }
314
315 vtable->wl_interface = &wl_drm_interface;
316 result = true;
317
318 end:
319 if (wrapped_display) {
320 wl_proxy_wrapper_destroy(wrapped_display);
321 wrapped_display = NULL;
322 }
323
324 if (wl_drm_ctx) {
325 if (wl_drm_ctx->drm) {
326 wl_drm_destroy(wl_drm_ctx->drm);
327 wl_drm_ctx->drm = NULL;
328 }
329
330 if (wl_drm_ctx->registry) {
331 wl_registry_destroy(wl_drm_ctx->registry);
332 wl_drm_ctx->registry = NULL;
333 }
334
335 if (wl_drm_ctx->queue) {
336 wl_event_queue_destroy(wl_drm_ctx->queue);
337 wl_drm_ctx->queue = NULL;
338 }
339 }
340
341 return result;
342 }
343