Tizen 2.0 Release
[framework/multimedia/gstreamer-vaapi.git] / gst-libs / gst / vaapi / gstvaapidisplay_drm.c
1 /*
2  *  gstvaapidisplay_drm.c - VA/DRM display abstraction
3  *
4  *  Copyright (C) 2012 Intel Corporation
5  *
6  *  This library is free software; you can redistribute it and/or
7  *  modify it under the terms of the GNU Lesser General Public License
8  *  as published by the Free Software Foundation; either version 2.1
9  *  of the License, or (at your option) any later version.
10  *
11  *  This library is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  *  Lesser General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Lesser General Public
17  *  License along with this library; if not, write to the Free
18  *  Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  *  Boston, MA 02110-1301 USA
20  */
21
22 /**
23  * SECTION:gstvaapidisplay_drm
24  * @short_description: VA/DRM display abstraction
25  */
26
27 #include "sysdeps.h"
28 #include <string.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <libudev.h>
32 #include <xf86drm.h>
33 #include <va/va_drm.h>
34 #include "gstvaapiutils.h"
35 #include "gstvaapidisplay_priv.h"
36 #include "gstvaapidisplay_drm.h"
37 #include "gstvaapidisplay_drm_priv.h"
38
39 #define DEBUG 1
40 #include "gstvaapidebug.h"
41
42 G_DEFINE_TYPE(GstVaapiDisplayDRM,
43               gst_vaapi_display_drm,
44               GST_VAAPI_TYPE_DISPLAY);
45
46 enum {
47     PROP_0,
48
49     PROP_DEVICE_PATH,
50     PROP_DRM_DEVICE
51 };
52
53 #define NAME_PREFIX "DRM:"
54 #define NAME_PREFIX_LENGTH 4
55
56 static inline gboolean
57 is_device_path(const gchar *device_path)
58 {
59     return strncmp(device_path, NAME_PREFIX, NAME_PREFIX_LENGTH) == 0;
60 }
61
62 static gboolean
63 compare_device_path(gconstpointer a, gconstpointer b, gpointer user_data)
64 {
65     const gchar *cached_name = a;
66     const gchar *tested_name = b;
67
68     if (!cached_name || !is_device_path(cached_name))
69         return FALSE;
70     g_return_val_if_fail(tested_name && is_device_path(tested_name), FALSE);
71
72     cached_name += NAME_PREFIX_LENGTH;
73     tested_name += NAME_PREFIX_LENGTH;
74     return strcmp(cached_name, tested_name) == 0;
75 }
76
77 static void
78 gst_vaapi_display_drm_finalize(GObject *object)
79 {
80     G_OBJECT_CLASS(gst_vaapi_display_drm_parent_class)->finalize(object);
81 }
82
83 /* Get default device path. Actually, the first match in the DRM subsystem */
84 static const gchar *
85 get_default_device_path(gpointer ptr)
86 {
87     GstVaapiDisplayDRM * const display = GST_VAAPI_DISPLAY_DRM(ptr);
88     GstVaapiDisplayDRMPrivate * const priv = display->priv;
89     const gchar *syspath, *devpath;
90     struct udev *udev = NULL;
91     struct udev_device *device, *parent;
92     struct udev_enumerate *e = NULL;
93     struct udev_list_entry *l;
94     int fd;
95
96     if (!priv->device_path_default) {
97         udev = udev_new();
98         if (!udev)
99             goto end;
100
101         e = udev_enumerate_new(udev);
102         if (!e)
103             goto end;
104
105         udev_enumerate_add_match_subsystem(e, "drm");
106         udev_enumerate_scan_devices(e);
107         udev_list_entry_foreach(l, udev_enumerate_get_list_entry(e)) {
108             syspath = udev_list_entry_get_name(l);
109             device  = udev_device_new_from_syspath(udev, syspath);
110             parent  = udev_device_get_parent(device);
111             if (strcmp(udev_device_get_subsystem(parent), "pci") != 0) {
112                 udev_device_unref(device);
113                 continue;
114             }
115
116             devpath = udev_device_get_devnode(device);
117             fd = open(devpath, O_RDWR|O_CLOEXEC);
118             if (fd < 0) {
119                 udev_device_unref(device);
120                 continue;
121             }
122
123             priv->device_path_default = g_strdup(devpath);
124             close(fd);
125             udev_device_unref(device);
126             break;
127         }
128
129     end:
130         if (e)
131             udev_enumerate_unref(e);
132         if (udev)
133             udev_unref(udev);
134     }
135     return priv->device_path_default;
136 }
137
138 /* Reconstruct a device path without our prefix */
139 static const gchar *
140 get_device_path(gpointer ptr)
141 {
142     GstVaapiDisplayDRM * const display = GST_VAAPI_DISPLAY_DRM(ptr);
143     const gchar *device_path = display->priv->device_path;
144
145     if (!device_path)
146         return NULL;
147
148     g_return_val_if_fail(is_device_path(device_path), NULL);
149
150     device_path += NAME_PREFIX_LENGTH;
151     if (*device_path == '\0')
152         return NULL;
153     return device_path;
154 }
155
156 /* Mangle device path with our prefix */
157 static void
158 set_device_path(GstVaapiDisplayDRM *display, const gchar *device_path)
159 {
160     GstVaapiDisplayDRMPrivate * const priv = display->priv;
161
162     g_free(priv->device_path);
163     priv->device_path = NULL;
164
165     if (!device_path) {
166         device_path = get_default_device_path(display);
167         if (!device_path)
168             return;
169     }
170     priv->device_path = g_strdup_printf("%s%s", NAME_PREFIX, device_path);
171 }
172
173 /* Set device path from file descriptor */
174 static void
175 set_device_path_from_fd(GstVaapiDisplayDRM *display, gint drm_device)
176 {
177     GstVaapiDisplayDRMPrivate * const priv = display->priv;
178     const gchar *busid, *path, *str;
179     gsize busid_length, path_length;
180     struct udev *udev = NULL;
181     struct udev_device *device;
182     struct udev_enumerate *e = NULL;
183     struct udev_list_entry *l;
184
185     g_free(priv->device_path);
186     priv->device_path = NULL;
187
188     if (drm_device < 0)
189         return;
190
191     busid = drmGetBusid(drm_device);
192     if (!busid)
193         return;
194     if (strncmp(busid, "pci:", 4) != 0)
195         return;
196     busid += 4;
197     busid_length = strlen(busid);
198
199     udev = udev_new();
200     if (!udev)
201         goto end;
202
203     e = udev_enumerate_new(udev);
204     if (!e)
205         goto end;
206
207     udev_enumerate_add_match_subsystem(e, "drm");
208     udev_enumerate_scan_devices(e);
209     udev_list_entry_foreach(l, udev_enumerate_get_list_entry(e)) {
210         path = udev_list_entry_get_name(l);
211         str  = strstr(path, busid);
212         if (!str || str <= path || str[-1] != '/')
213             continue;
214
215         path_length = strlen(path);
216         if (str + busid_length >= path + path_length)
217             continue;
218         if (strncmp(&str[busid_length], "/drm/card", 9) != 0)
219             continue;
220
221         device = udev_device_new_from_syspath(udev, path);
222         if (!device)
223             continue;
224
225         path = udev_device_get_devnode(device);
226         priv->device_path = g_strdup_printf("%s%s", NAME_PREFIX, path);
227         udev_device_unref(device);
228         break;
229     }
230
231 end:
232     if (e)
233         udev_enumerate_unref(e);
234     if (udev)
235         udev_unref(udev);
236 }
237
238 static void
239 gst_vaapi_display_drm_set_property(
240     GObject      *object,
241     guint         prop_id,
242     const GValue *value,
243     GParamSpec   *pspec
244 )
245 {
246     GstVaapiDisplayDRM * const display = GST_VAAPI_DISPLAY_DRM(object);
247
248     switch (prop_id) {
249     case PROP_DEVICE_PATH:
250         set_device_path(display, g_value_get_string(value));
251         break;
252     case PROP_DRM_DEVICE:
253         display->priv->drm_device = g_value_get_int(value);
254         break;
255     default:
256         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
257         break;
258     }
259 }
260
261 static void
262 gst_vaapi_display_drm_get_property(
263     GObject    *object,
264     guint       prop_id,
265     GValue     *value,
266     GParamSpec *pspec
267 )
268 {
269     GstVaapiDisplayDRM * const display = GST_VAAPI_DISPLAY_DRM(object);
270
271     switch (prop_id) {
272     case PROP_DEVICE_PATH:
273         g_value_set_string(value, get_device_path(display));
274         break;
275     case PROP_DRM_DEVICE:
276         g_value_set_int(value, display->priv->drm_device);
277         break;
278     default:
279         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
280         break;
281     }
282 }
283
284 static void
285 gst_vaapi_display_drm_constructed(GObject *object)
286 {
287     GstVaapiDisplayDRM * const display = GST_VAAPI_DISPLAY_DRM(object);
288     GstVaapiDisplayDRMPrivate * const priv = display->priv;
289     GstVaapiDisplayCache * const cache = gst_vaapi_display_get_cache();
290     const GstVaapiDisplayInfo *info;
291     GObjectClass *parent_class;
292
293     priv->create_display = priv->drm_device < 0;
294
295     /* Don't create DRM display if there is one in the cache already */
296     if (priv->create_display) {
297         info = gst_vaapi_display_cache_lookup_by_name(
298             cache,
299             priv->device_path,
300             compare_device_path, NULL
301         );
302         if (info) {
303             priv->drm_device     = GPOINTER_TO_INT(info->native_display);
304             priv->create_display = FALSE;
305         }
306     }
307
308     /* Reset device-path if the user provided his own DRM display */
309     if (!priv->create_display)
310         set_device_path_from_fd(display, priv->drm_device);
311
312     parent_class = G_OBJECT_CLASS(gst_vaapi_display_drm_parent_class);
313     if (parent_class->constructed)
314         parent_class->constructed(object);
315 }
316
317 static gboolean
318 gst_vaapi_display_drm_open_display(GstVaapiDisplay *display)
319 {
320     GstVaapiDisplayDRMPrivate * const priv =
321         GST_VAAPI_DISPLAY_DRM(display)->priv;
322
323     if (priv->create_display) {
324         const gchar *device_path = get_device_path(display);
325         if (!device_path)
326             return FALSE;
327         priv->drm_device = open(device_path, O_RDWR|O_CLOEXEC);
328         if (priv->drm_device < 0)
329             return FALSE;
330     }
331     if (priv->drm_device < 0)
332         return FALSE;
333     return TRUE;
334 }
335
336 static void
337 gst_vaapi_display_drm_close_display(GstVaapiDisplay *display)
338 {
339     GstVaapiDisplayDRMPrivate * const priv =
340         GST_VAAPI_DISPLAY_DRM(display)->priv;
341
342     if (priv->drm_device >= 0) {
343         if (priv->create_display)
344             close(priv->drm_device);
345         priv->drm_device = -1;
346     }
347
348     if (priv->device_path) {
349         g_free(priv->device_path);
350         priv->device_path = NULL;
351     }
352
353     if (priv->device_path_default) {
354         g_free(priv->device_path_default);
355         priv->device_path_default = NULL;
356     }
357 }
358
359 static gboolean
360 gst_vaapi_display_drm_get_display_info(
361     GstVaapiDisplay     *display,
362     GstVaapiDisplayInfo *info
363 )
364 {
365     GstVaapiDisplayDRMPrivate * const priv =
366         GST_VAAPI_DISPLAY_DRM(display)->priv;
367     GstVaapiDisplayCache *cache;
368     const GstVaapiDisplayInfo *cached_info;
369
370     /* Return any cached info even if child has its own VA display */
371     cache = gst_vaapi_display_get_cache();
372     if (!cache)
373         return FALSE;
374     cached_info = gst_vaapi_display_cache_lookup_by_native_display(
375         cache, GINT_TO_POINTER(priv->drm_device));
376     if (cached_info) {
377         *info = *cached_info;
378         return TRUE;
379     }
380
381     /* Otherwise, create VA display if there is none already */
382     info->native_display = GINT_TO_POINTER(priv->drm_device);
383     info->display_name   = priv->device_path;
384     if (!info->va_display) {
385         info->va_display = vaGetDisplayDRM(priv->drm_device);
386         if (!info->va_display)
387             return FALSE;
388         info->display_type = GST_VAAPI_DISPLAY_TYPE_DRM;
389     }
390     return TRUE;
391 }
392
393 static void
394 gst_vaapi_display_drm_class_init(GstVaapiDisplayDRMClass *klass)
395 {
396     GObjectClass * const object_class = G_OBJECT_CLASS(klass);
397     GstVaapiDisplayClass * const dpy_class = GST_VAAPI_DISPLAY_CLASS(klass);
398
399     g_type_class_add_private(klass, sizeof(GstVaapiDisplayDRMPrivate));
400
401     object_class->finalize      = gst_vaapi_display_drm_finalize;
402     object_class->set_property  = gst_vaapi_display_drm_set_property;
403     object_class->get_property  = gst_vaapi_display_drm_get_property;
404     object_class->constructed   = gst_vaapi_display_drm_constructed;
405
406     dpy_class->open_display     = gst_vaapi_display_drm_open_display;
407     dpy_class->close_display    = gst_vaapi_display_drm_close_display;
408     dpy_class->get_display      = gst_vaapi_display_drm_get_display_info;
409
410     /**
411      * GstVaapiDisplayDRM:drm-device:
412      *
413      * The DRM device (file descriptor).
414      */
415     g_object_class_install_property
416         (object_class,
417          PROP_DRM_DEVICE,
418          g_param_spec_int("drm-device",
419                           "DRM device",
420                           "DRM device",
421                           -1, G_MAXINT32, -1,
422                           G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY));
423
424     /**
425      * GstVaapiDisplayDRM:device-path:
426      *
427      * The DRM device path.
428      */
429     g_object_class_install_property
430         (object_class,
431          PROP_DEVICE_PATH,
432          g_param_spec_string("device-path",
433                              "DRM device path",
434                              "DRM device path",
435                              NULL,
436                              G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY));
437 }
438
439 static void
440 gst_vaapi_display_drm_init(GstVaapiDisplayDRM *display)
441 {
442     GstVaapiDisplayDRMPrivate * const priv =
443         GST_VAAPI_DISPLAY_DRM_GET_PRIVATE(display);
444
445     display->priv               = priv;
446     priv->device_path_default   = NULL;
447     priv->device_path           = NULL;
448     priv->drm_device            = -1;
449     priv->create_display        = TRUE;
450 }
451
452 /**
453  * gst_vaapi_display_drm_new:
454  * @device_path: the DRM device path
455  *
456  * Opens an DRM file descriptor using @device_path and returns a newly
457  * allocated #GstVaapiDisplay object. The DRM display will be cloed
458  * when the reference count of the object reaches zero.
459  *
460  * If @device_path is NULL, the DRM device path will be automatically
461  * determined as the first positive match in the list of available DRM
462  * devices.
463  *
464  * Return value: a newly allocated #GstVaapiDisplay object
465  */
466 GstVaapiDisplay *
467 gst_vaapi_display_drm_new(const gchar *device_path)
468 {
469     return g_object_new(GST_VAAPI_TYPE_DISPLAY_DRM,
470                         "device-path", device_path,
471                         NULL);
472 }
473
474 /**
475  * gst_vaapi_display_drm_new_with_device:
476  * @device: an open DRM device (file descriptor)
477  *
478  * Creates a #GstVaapiDisplay based on the open DRM @device. The
479  * caller still owns the device file descriptor and must call close()
480  * when all #GstVaapiDisplay references are released. Doing so too
481  * early can yield undefined behaviour.
482  *
483  * Return value: a newly allocated #GstVaapiDisplay object
484  */
485 GstVaapiDisplay *
486 gst_vaapi_display_drm_new_with_device(gint device)
487 {
488     g_return_val_if_fail(device >= 0, NULL);
489
490     return g_object_new(GST_VAAPI_TYPE_DISPLAY_DRM,
491                         "drm-device", device,
492                         NULL);
493 }
494
495 /**
496  * gst_vaapi_display_drm_get_device:
497  * @display: a #GstVaapiDisplayDRM
498  *
499  * Returns the underlying DRM device file descriptor that was created
500  * by gst_vaapi_display_drm_new() or that was bound from
501  * gst_vaapi_display_drm_new_with_device().
502  *
503  * Return value: the DRM file descriptor attached to @display
504  */
505 gint
506 gst_vaapi_display_drm_get_device(GstVaapiDisplayDRM *display)
507 {
508     g_return_val_if_fail(GST_VAAPI_IS_DISPLAY_DRM(display), -1);
509
510     return display->priv->drm_device;
511 }
512
513 /**
514  * gst_vaapi_display_drm_get_device_path:
515  * @display: a #GstVaapiDisplayDRM
516  *
517  * Returns the underlying DRM device path name was created by
518  * gst_vaapi_display_drm_new() or that was bound from
519  * gst_vaapi_display_drm_new_with_device().
520  *
521  * Note: the #GstVaapiDisplayDRM object owns the resulting string, so
522  * it shall not be deallocated.
523  *
524  * Return value: the DRM device path name attached to @display
525  */
526 const gchar *
527 gst_vaapi_display_drm_get_device_path(GstVaapiDisplayDRM *display)
528 {
529     g_return_val_if_fail(GST_VAAPI_IS_DISPLAY_DRM(display), NULL);
530
531     return display->priv->device_path;
532 }