1 /*
2  * GStreamer
3  * Copyright (C) 2018 Carlos Rafael Giani <dv@pseudoterminal.org>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24 
25 #include "gstgldisplay_gbm.h"
26 #include "gstgl_gbm_utils.h"
27 
28 #include <unistd.h>
29 #include <fcntl.h>
30 #include <errno.h>
31 
32 GST_DEBUG_CATEGORY (gst_gl_gbm_debug);
33 
34 GST_DEBUG_CATEGORY_STATIC (gst_gl_display_debug);
35 #define GST_CAT_DEFAULT gst_gl_display_debug
36 
37 
38 #define INVALID_CRTC ((guint32)0)
39 
40 
41 G_DEFINE_TYPE (GstGLDisplayGBM, gst_gl_display_gbm, GST_TYPE_GL_DISPLAY);
42 
43 
44 static void gst_gl_display_gbm_finalize (GObject * object);
45 static guintptr gst_gl_display_gbm_get_handle (GstGLDisplay * display);
46 
47 static guint32 gst_gl_gbm_find_crtc_id_for_encoder (GstGLDisplayGBM *
48     display_gbm, drmModeEncoder const *encoder);
49 static guint32 gst_gl_gbm_find_crtc_id_for_connector (GstGLDisplayGBM *
50     display_gbm);
51 
52 static gboolean gst_gl_display_gbm_setup_drm (GstGLDisplayGBM * display_gbm,
53     const gchar * drm_connector_name);
54 static void gst_gl_display_gbm_shutdown_drm (GstGLDisplayGBM * display_gbm);
55 
56 static gboolean gst_gl_display_gbm_setup_gbm (GstGLDisplayGBM * display_gbm);
57 static void gst_gl_display_gbm_shutdown_gbm (GstGLDisplayGBM * display_gbm);
58 
59 
60 static void
gst_gl_display_gbm_class_init(GstGLDisplayGBMClass * klass)61 gst_gl_display_gbm_class_init (GstGLDisplayGBMClass * klass)
62 {
63   GST_GL_DISPLAY_CLASS (klass)->get_handle =
64       GST_DEBUG_FUNCPTR (gst_gl_display_gbm_get_handle);
65 
66   G_OBJECT_CLASS (klass)->finalize = gst_gl_display_gbm_finalize;
67 }
68 
69 static void
gst_gl_display_gbm_init(GstGLDisplayGBM * display_gbm)70 gst_gl_display_gbm_init (GstGLDisplayGBM * display_gbm)
71 {
72   GstGLDisplay *display = (GstGLDisplay *) display_gbm;
73   display->type = GST_GL_DISPLAY_TYPE_GBM;
74 
75   display_gbm->drm_fd = -1;
76 }
77 
78 static void
gst_gl_display_gbm_finalize(GObject * object)79 gst_gl_display_gbm_finalize (GObject * object)
80 {
81   GstGLDisplayGBM *display_gbm = GST_GL_DISPLAY_GBM (object);
82 
83   gst_gl_display_gbm_shutdown_gbm (display_gbm);
84   gst_gl_display_gbm_shutdown_drm (display_gbm);
85 
86   if (display_gbm->drm_fd >= 0)
87     close (display_gbm->drm_fd);
88 
89   G_OBJECT_CLASS (gst_gl_display_gbm_parent_class)->finalize (object);
90 }
91 
92 static guintptr
gst_gl_display_gbm_get_handle(GstGLDisplay * display)93 gst_gl_display_gbm_get_handle (GstGLDisplay * display)
94 {
95   return (guintptr) GST_GL_DISPLAY_GBM (display)->gbm_dev;
96 }
97 
98 
99 static guint32
gst_gl_gbm_find_crtc_id_for_encoder(GstGLDisplayGBM * display_gbm,drmModeEncoder const * encoder)100 gst_gl_gbm_find_crtc_id_for_encoder (GstGLDisplayGBM * display_gbm,
101     drmModeEncoder const *encoder)
102 {
103   int i;
104   for (i = 0; i < display_gbm->drm_mode_resources->count_crtcs; ++i) {
105     /* possible_crtcs is a bitmask as described here:
106      * https://dvdhrm.wordpress.com/2012/09/13/linux-drm-mode-setting-api */
107     guint32 const crtc_mask = 1 << i;
108     guint32 const crtc_id = display_gbm->drm_mode_resources->crtcs[i];
109 
110     if (encoder->possible_crtcs & crtc_mask)
111       return crtc_id;
112   }
113 
114   /* No match found */
115   return INVALID_CRTC;
116 }
117 
118 
119 static guint32
gst_gl_gbm_find_crtc_id_for_connector(GstGLDisplayGBM * display_gbm)120 gst_gl_gbm_find_crtc_id_for_connector (GstGLDisplayGBM * display_gbm)
121 {
122   int i;
123   for (i = 0; i < display_gbm->drm_mode_connector->count_encoders; ++i) {
124     guint32 encoder_id = display_gbm->drm_mode_connector->encoders[i];
125     drmModeEncoder *encoder =
126         drmModeGetEncoder (display_gbm->drm_fd, encoder_id);
127 
128     if (encoder != NULL) {
129       guint32 crtc_id =
130           gst_gl_gbm_find_crtc_id_for_encoder (display_gbm, encoder);
131       drmModeFreeEncoder (encoder);
132 
133       if (crtc_id != INVALID_CRTC)
134         return crtc_id;
135     }
136   }
137 
138   /* No match found */
139   return INVALID_CRTC;
140 }
141 
142 
143 static gboolean
gst_gl_display_gbm_setup_drm(GstGLDisplayGBM * display_gbm,const gchar * drm_connector_name)144 gst_gl_display_gbm_setup_drm (GstGLDisplayGBM * display_gbm, const gchar *
145     drm_connector_name)
146 {
147   int i;
148 
149   g_assert (display_gbm != NULL);
150   g_assert (display_gbm->drm_fd >= 0);
151 
152   /* Get the DRM mode resources */
153   display_gbm->drm_mode_resources = drmModeGetResources (display_gbm->drm_fd);
154   if (display_gbm->drm_mode_resources == NULL) {
155     GST_ERROR ("Could not get DRM resources: %s (%d)", g_strerror (errno),
156         errno);
157     goto cleanup;
158   }
159   GST_DEBUG ("Got DRM resources");
160 
161   /* Find a connected connector. The connector is where the pixel data is
162    * finally sent to, and typically connects to some form of display, like an
163    * HDMI TV, an LVDS panel etc. */
164   {
165     drmModeConnector *connected_connector = NULL;
166 
167     GST_DEBUG ("Checking %d DRM connector(s)",
168         display_gbm->drm_mode_resources->count_connectors);
169     for (i = 0; i < display_gbm->drm_mode_resources->count_connectors; ++i) {
170       drmModeConnector *candidate_connector =
171           drmModeGetConnector (display_gbm->drm_fd,
172           display_gbm->drm_mode_resources->connectors[i]);
173       gchar *candidate_name;
174 
175       candidate_name = g_strdup_printf ("%s-%i",
176           gst_gl_gbm_get_name_for_drm_connector (candidate_connector),
177           candidate_connector->connector_type_id);
178 
179       GST_DEBUG ("Found DRM connector #%d \"%s\" with ID %" G_GUINT32_FORMAT, i,
180           candidate_name, candidate_connector->connector_id);
181 
182       /* If we already picked a connector, and connected_connector is therefore
183        * non-NULL, then are just printing information about the other connectors
184        * for logging purposes by now, so don't actually do anything with this
185        * connector. Just loop instead. */
186       if (connected_connector != NULL) {
187         drmModeFreeConnector (candidate_connector);
188         g_free (candidate_name);
189         continue;
190       }
191 
192       if (drm_connector_name != NULL) {
193         if (g_ascii_strcasecmp (drm_connector_name, candidate_name) != 0) {
194           drmModeFreeConnector (candidate_connector);
195           g_free (candidate_name);
196           continue;
197         }
198       }
199 
200       if (candidate_connector->connection == DRM_MODE_CONNECTED) {
201         if (drm_connector_name != NULL)
202           GST_DEBUG ("Picking DRM connector #%d because it is connected and "
203               "has a matching name \"%s\"", i, candidate_name);
204         else
205           GST_DEBUG ("Picking DRM connector #%d because it is connected", i);
206         connected_connector = candidate_connector;
207         g_free (candidate_name);
208         break;
209       } else {
210         if (drm_connector_name != NULL)
211           GST_WARNING ("DRM connector #%d has a matching name \"%s\" but is "
212               "not connected; not picking it", i, candidate_name);
213         drmModeFreeConnector (candidate_connector);
214         g_free (candidate_name);
215       }
216     }
217 
218     if (connected_connector == NULL) {
219       GST_ERROR ("No connected DRM connector found");
220       goto cleanup;
221     }
222 
223     display_gbm->drm_mode_connector = connected_connector;
224   }
225 
226   /* Check out what modes are supported by the chosen connector,
227    * and pick either the "preferred" mode or the one with the largest
228    * pixel area. */
229   {
230     int selected_mode_index = -1;
231     int selected_mode_area = -1;
232     gboolean preferred_mode_found = FALSE;
233 
234     GST_DEBUG ("Checking %d DRM mode(s) from selected connector",
235         display_gbm->drm_mode_connector->count_modes);
236     for (i = 0; i < display_gbm->drm_mode_connector->count_modes; ++i) {
237       drmModeModeInfo *current_mode =
238           &(display_gbm->drm_mode_connector->modes[i]);
239       int current_mode_area = current_mode->hdisplay * current_mode->vdisplay;
240 
241       GST_DEBUG ("Found DRM mode #%d width/height %" G_GUINT16_FORMAT "/%"
242           G_GUINT16_FORMAT " hsync/vsync start %" G_GUINT16_FORMAT "/%"
243           G_GUINT16_FORMAT " hsync/vsync end %" G_GUINT16_FORMAT "/%"
244           G_GUINT16_FORMAT " htotal/vtotal %" G_GUINT16_FORMAT "/%"
245           G_GUINT16_FORMAT " hskew %" G_GUINT16_FORMAT " vscan %"
246           G_GUINT16_FORMAT " vrefresh %" G_GUINT32_FORMAT " preferred %d", i,
247           current_mode->hdisplay, current_mode->vdisplay,
248           current_mode->hsync_start, current_mode->vsync_start,
249           current_mode->hsync_end, current_mode->vsync_end,
250           current_mode->htotal, current_mode->vtotal, current_mode->hskew,
251           current_mode->vscan, current_mode->vrefresh,
252           (current_mode->type & DRM_MODE_TYPE_PREFERRED) ? TRUE : FALSE);
253 
254       if (!preferred_mode_found
255           && ((current_mode->type & DRM_MODE_TYPE_PREFERRED)
256               || (current_mode_area > selected_mode_area))) {
257         display_gbm->drm_mode_info = current_mode;
258         selected_mode_area = current_mode_area;
259         selected_mode_index = i;
260 
261         if (current_mode->type & DRM_MODE_TYPE_PREFERRED)
262           preferred_mode_found = TRUE;
263       }
264     }
265 
266     if (display_gbm->drm_mode_info == NULL) {
267       GST_ERROR ("No usable DRM mode found");
268       goto cleanup;
269     }
270 
271     GST_DEBUG ("Selected DRM mode #%d (is preferred: %d)", selected_mode_index,
272         preferred_mode_found);
273   }
274 
275   /* Find an encoder that is attached to the chosen connector. Also find the
276    * index/id of the CRTC associated with this encoder. The encoder takes pixel
277    * data from the CRTC and transmits it to the connector. The CRTC roughly
278    * represents the scanout framebuffer.
279    *
280    * Ultimately, we only care about the CRTC index & ID, so the encoder
281    * reference is discarded here once these are found. The CRTC index is the
282    * index in the m_drm_mode_resources' CRTC array, while the ID is an identifier
283    * used by the DRM to refer to the CRTC universally. (We need the CRTC
284    * information for page flipping and DRM scanout framebuffer configuration.) */
285   {
286     drmModeEncoder *selected_encoder = NULL;
287 
288     GST_DEBUG ("Checking %d DRM encoder(s)",
289         display_gbm->drm_mode_resources->count_encoders);
290     for (i = 0; i < display_gbm->drm_mode_resources->count_encoders; ++i) {
291       drmModeEncoder *candidate_encoder =
292           drmModeGetEncoder (display_gbm->drm_fd,
293           display_gbm->drm_mode_resources->encoders[i]);
294 
295       GST_DEBUG ("Found DRM encoder #%d \"%s\"", i,
296           gst_gl_gbm_get_name_for_drm_encoder (candidate_encoder));
297 
298       if ((selected_encoder == NULL) &&
299           (candidate_encoder->encoder_id ==
300               display_gbm->drm_mode_connector->encoder_id)) {
301         selected_encoder = candidate_encoder;
302         GST_DEBUG ("DRM encoder #%d corresponds to selected DRM connector "
303             "-> selected", i);
304       } else
305         drmModeFreeEncoder (candidate_encoder);
306     }
307 
308     if (selected_encoder == NULL) {
309       GST_DEBUG ("No encoder found; searching for CRTC ID in the connector");
310       display_gbm->crtc_id =
311           gst_gl_gbm_find_crtc_id_for_connector (display_gbm);
312     } else {
313       GST_DEBUG ("Using CRTC ID from selected encoder");
314       display_gbm->crtc_id = selected_encoder->crtc_id;
315       drmModeFreeEncoder (selected_encoder);
316     }
317 
318     if (display_gbm->crtc_id == INVALID_CRTC) {
319       GST_ERROR ("No CRTC found");
320       goto cleanup;
321     }
322 
323     GST_DEBUG ("CRTC with ID %" G_GUINT32_FORMAT " found; now locating it in "
324         "the DRM mode resources CRTC array", display_gbm->crtc_id);
325 
326     for (i = 0; i < display_gbm->drm_mode_resources->count_crtcs; ++i) {
327       if (display_gbm->drm_mode_resources->crtcs[i] == display_gbm->crtc_id) {
328         display_gbm->crtc_index = i;
329         break;
330       }
331     }
332 
333     if (display_gbm->crtc_index < 0) {
334       GST_ERROR ("No matching CRTC entry in DRM resources found");
335       goto cleanup;
336     }
337 
338     GST_DEBUG ("CRTC with ID %" G_GUINT32_FORMAT " can be found at index #%d "
339         "in the DRM mode resources CRTC array", display_gbm->crtc_id,
340         display_gbm->crtc_index);
341   }
342 
343   GST_DEBUG ("DRM structures initialized");
344   return TRUE;
345 
346 cleanup:
347   gst_gl_display_gbm_shutdown_drm (display_gbm);
348   return FALSE;
349 }
350 
351 
352 static void
gst_gl_display_gbm_shutdown_drm(GstGLDisplayGBM * display_gbm)353 gst_gl_display_gbm_shutdown_drm (GstGLDisplayGBM * display_gbm)
354 {
355   g_assert (display_gbm != NULL);
356 
357   display_gbm->drm_mode_info = NULL;
358 
359   display_gbm->crtc_index = -1;
360   display_gbm->crtc_id = INVALID_CRTC;
361 
362   if (display_gbm->drm_mode_connector != NULL) {
363     drmModeFreeConnector (display_gbm->drm_mode_connector);
364     display_gbm->drm_mode_connector = NULL;
365   }
366 
367   if (display_gbm->drm_mode_resources != NULL) {
368     drmModeFreeResources (display_gbm->drm_mode_resources);
369     display_gbm->drm_mode_resources = NULL;
370   }
371 }
372 
373 
374 static gboolean
gst_gl_display_gbm_setup_gbm(GstGLDisplayGBM * display_gbm)375 gst_gl_display_gbm_setup_gbm (GstGLDisplayGBM * display_gbm)
376 {
377   display_gbm->gbm_dev = gbm_create_device (display_gbm->drm_fd);
378   if (display_gbm->gbm_dev == NULL) {
379     GST_ERROR ("Creating GBM device failed");
380     return FALSE;
381   }
382 
383   GST_DEBUG ("GBM structures initialized");
384   return TRUE;
385 }
386 
387 
388 static void
gst_gl_display_gbm_shutdown_gbm(GstGLDisplayGBM * display_gbm)389 gst_gl_display_gbm_shutdown_gbm (GstGLDisplayGBM * display_gbm)
390 {
391   if (display_gbm->gbm_dev != NULL) {
392     gbm_device_destroy (display_gbm->gbm_dev);
393     display_gbm->gbm_dev = NULL;
394   }
395 }
396 
397 
398 static void
_init_debug(void)399 _init_debug (void)
400 {
401   static volatile gsize _init = 0;
402 
403   if (g_once_init_enter (&_init)) {
404     GST_DEBUG_CATEGORY_GET (gst_gl_display_debug, "gldisplay");
405     GST_DEBUG_CATEGORY_INIT (gst_gl_gbm_debug, "gleglgbm", 0,
406         "Mesa3D EGL GBM debugging");
407     g_once_init_leave (&_init, 1);
408   }
409 }
410 
411 
412 GstGLDisplayGBM *
gst_gl_display_gbm_new(void)413 gst_gl_display_gbm_new (void)
414 {
415   int drm_fd = -1;
416   GstGLDisplayGBM *display;
417   const gchar *drm_node_name;
418   const gchar *drm_connector_name;
419 
420   _init_debug ();
421 
422   drm_node_name = g_getenv ("GST_GL_GBM_DRM_DEVICE");
423   drm_connector_name = g_getenv ("GST_GL_GBM_DRM_CONNECTOR");
424 
425   if (drm_node_name != NULL) {
426     GST_DEBUG ("attempting to open device %s (specified by the "
427         "GST_GL_GBM_DRM_DEVICE environment variable)", drm_node_name);
428     drm_fd = open (drm_node_name, O_RDWR | O_CLOEXEC);
429     if (drm_fd < 0) {
430       GST_ERROR ("could not open DRM device %s: %s (%d)", drm_node_name,
431           g_strerror (errno), errno);
432       return NULL;
433     }
434   } else {
435     GST_DEBUG ("GST_GL_GBM_DRM_DEVICE environment variable is not "
436         "set - trying to autodetect device");
437     drm_fd = gst_gl_gbm_find_and_open_drm_node ();
438     if (drm_fd < 0) {
439       GST_ERROR ("could not find or open DRM device");
440       return NULL;
441     }
442   }
443 
444   display = g_object_new (GST_TYPE_GL_DISPLAY_GBM, NULL);
445   display->drm_fd = drm_fd;
446 
447   if (drm_connector_name != NULL) {
448     GST_DEBUG ("GST_GL_GBM_DRM_CONNECTOR variable set to value \"%s\"; "
449         "will use this name to match connector(s) against", drm_connector_name);
450   }
451 
452   if (!gst_gl_display_gbm_setup_drm (display, drm_connector_name)) {
453     GST_WARNING ("Failed to initialize DRM");
454   }
455 
456   if (!gst_gl_display_gbm_setup_gbm (display)) {
457     GST_ERROR ("Failed to initialize GBM");
458     goto cleanup;
459   }
460 
461   GST_DEBUG ("Created GBM EGL display %p", (gpointer) display);
462 
463   return display;
464 
465 cleanup:
466   gst_gl_display_gbm_shutdown_gbm (display);
467   gst_gl_display_gbm_shutdown_drm (display);
468   gst_object_unref (G_OBJECT (display));
469   if (drm_fd >= 0)
470     close (drm_fd);
471   return NULL;
472 }
473