uterm: drm: retry DRM wakeup after short timeout
[platform/upstream/kmscon.git] / src / uterm_drm3d_video.c
1 /*
2  * uterm - Linux User-Space Terminal
3  *
4  * Copyright (c) 2011-2013 David Herrmann <dh.herrmann@googlemail.com>
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining
7  * a copy of this software and associated documentation files
8  * (the "Software"), to deal in the Software without restriction, including
9  * without limitation the rights to use, copy, modify, merge, publish,
10  * distribute, sublicense, 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 shall be included
15  * in all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24  */
25
26 /*
27  * DRM Video backend
28  */
29
30 #define EGL_EGLEXT_PROTOTYPES
31 #define GL_GLEXT_PROTOTYPES
32
33 #include <EGL/egl.h>
34 #include <EGL/eglext.h>
35 #include <errno.h>
36 #include <fcntl.h>
37 #include <gbm.h>
38 #include <GLES2/gl2.h>
39 #include <GLES2/gl2ext.h>
40 #include <inttypes.h>
41 #include <stdbool.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.h>
45 #include <xf86drm.h>
46 #include <xf86drmMode.h>
47 #include "eloop.h"
48 #include "shl_log.h"
49 #include "shl_misc.h"
50 #include "static_gl.h"
51 #include "uterm_drm_shared_internal.h"
52 #include "uterm_drm3d_internal.h"
53 #include "uterm_video.h"
54 #include "uterm_video_internal.h"
55
56 #define LOG_SUBSYSTEM "uterm_drm3d_video"
57
58 static int display_init(struct uterm_display *disp)
59 {
60         struct uterm_drm3d_display *d3d;
61         int ret;
62
63         d3d = malloc(sizeof(*d3d));
64         if (!d3d)
65                 return -ENOMEM;
66         memset(d3d, 0, sizeof(*d3d));
67
68         ret = uterm_drm_display_init(disp, d3d);
69         if (ret) {
70                 free(d3d);
71                 return ret;
72         }
73
74         return 0;
75 }
76
77 static void display_destroy(struct uterm_display *disp)
78 {
79         free(uterm_drm_display_get_data(disp));
80         uterm_drm_display_destroy(disp);
81 }
82
83 static void bo_destroy_event(struct gbm_bo *bo, void *data)
84 {
85         struct uterm_drm3d_rb *rb = data;
86         struct uterm_drm_video *vdrm;
87
88         if (!rb)
89                 return;
90
91         vdrm = rb->disp->video->data;
92         drmModeRmFB(vdrm->fd, rb->fb);
93         free(rb);
94 }
95
96 static struct uterm_drm3d_rb *bo_to_rb(struct uterm_display *disp,
97                                        struct gbm_bo *bo)
98 {
99         struct uterm_drm3d_rb *rb = gbm_bo_get_user_data(bo);
100         struct uterm_video *video = disp->video;
101         struct uterm_drm_video *vdrm = video->data;
102         int ret;
103         unsigned int stride, handle, width, height;;
104
105         if (rb)
106                 return rb;
107
108         rb = malloc(sizeof(*rb));
109         if (!rb) {
110                 log_error("cannot allocate memory for render buffer (%d): %m",
111                           errno);
112                 return NULL;
113         }
114         rb->disp = disp;
115         rb->bo = bo;
116
117 #ifdef BUILD_HAVE_GBM_BO_GET_PITCH
118         stride = gbm_bo_get_pitch(rb->bo);
119 #else
120         stride = gbm_bo_get_stride(rb->bo);
121 #endif
122         handle = gbm_bo_get_handle(rb->bo).u32;
123         width = gbm_bo_get_width(rb->bo);
124         height = gbm_bo_get_height(rb->bo);
125
126         ret = drmModeAddFB(vdrm->fd, width, height, 24, 32, stride,
127                            handle, &rb->fb);
128         if (ret) {
129                 log_err("cannot add drm-fb (%d): %m", errno);
130                 free(rb);
131                 return NULL;
132         }
133
134         gbm_bo_set_user_data(bo, rb, bo_destroy_event);
135         return rb;
136 }
137
138 static int display_activate(struct uterm_display *disp,
139                             struct uterm_mode *mode)
140 {
141         struct uterm_video *video = disp->video;
142         struct uterm_drm_video *vdrm;
143         struct uterm_drm3d_video *v3d;
144         struct uterm_drm_display *ddrm = disp->data;
145         struct uterm_drm3d_display *d3d = uterm_drm_display_get_data(disp);
146         int ret;
147         struct gbm_bo *bo;
148         drmModeModeInfo *minfo;
149
150         if (!mode)
151                 return -EINVAL;
152
153         vdrm = video->data;
154         v3d = uterm_drm_video_get_data(video);
155         minfo = uterm_drm_mode_get_info(mode);
156         log_info("activating display %p to %ux%u", disp,
157                  minfo->hdisplay, minfo->vdisplay);
158
159         ret = uterm_drm_display_activate(disp, vdrm->fd);
160         if (ret)
161                 return ret;
162
163         d3d->current = NULL;
164         d3d->next = NULL;
165         disp->current_mode = mode;
166
167         d3d->gbm = gbm_surface_create(v3d->gbm, minfo->hdisplay,
168                                       minfo->vdisplay, GBM_FORMAT_XRGB8888,
169                                       GBM_BO_USE_SCANOUT |
170                                       GBM_BO_USE_RENDERING);
171         if (!d3d->gbm) {
172                 log_error("cannot create gbm surface (%d): %m", errno);
173                 ret = -EFAULT;
174                 goto err_saved;
175         }
176
177         d3d->surface = eglCreateWindowSurface(v3d->disp, v3d->conf,
178                                               (EGLNativeWindowType)d3d->gbm,
179                                               NULL);
180         if (d3d->surface == EGL_NO_SURFACE) {
181                 log_error("cannot create EGL window surface");
182                 ret = -EFAULT;
183                 goto err_gbm;
184         }
185
186         if (!eglMakeCurrent(v3d->disp, d3d->surface, d3d->surface,
187                             v3d->ctx)) {
188                 log_error("cannot activate EGL context");
189                 ret = -EFAULT;
190                 goto err_surface;
191         }
192
193         glClearColor(0, 0, 0, 0);
194         glClear(GL_COLOR_BUFFER_BIT);
195         if (!eglSwapBuffers(v3d->disp, d3d->surface)) {
196                 log_error("cannot swap buffers");
197                 ret = -EFAULT;
198                 goto err_noctx;
199         }
200
201         bo = gbm_surface_lock_front_buffer(d3d->gbm);
202         if (!bo) {
203                 log_error("cannot lock front buffer during creation");
204                 ret = -EFAULT;
205                 goto err_noctx;
206         }
207
208         d3d->current = bo_to_rb(disp, bo);
209         if (!d3d->current) {
210                 log_error("cannot lock front buffer");
211                 ret = -EFAULT;
212                 goto err_bo;
213         }
214
215         ret = drmModeSetCrtc(vdrm->fd, ddrm->crtc_id, d3d->current->fb,
216                              0, 0, &ddrm->conn_id, 1, minfo);
217         if (ret) {
218                 log_err("cannot set drm-crtc");
219                 ret = -EFAULT;
220                 goto err_bo;
221         }
222
223         disp->flags |= DISPLAY_ONLINE;
224         return 0;
225
226 err_bo:
227         gbm_surface_release_buffer(d3d->gbm, bo);
228 err_noctx:
229         eglMakeCurrent(v3d->disp, EGL_NO_SURFACE, EGL_NO_SURFACE,
230                        v3d->ctx);
231 err_surface:
232         eglDestroySurface(v3d->disp, d3d->surface);
233 err_gbm:
234         gbm_surface_destroy(d3d->gbm);
235 err_saved:
236         disp->current_mode = NULL;
237         uterm_drm_display_deactivate(disp, vdrm->fd);
238         return ret;
239 }
240
241 static void display_deactivate(struct uterm_display *disp)
242 {
243         struct uterm_drm3d_display *d3d = uterm_drm_display_get_data(disp);
244         struct uterm_video *video = disp->video;
245         struct uterm_drm_video *vdrm;
246         struct uterm_drm3d_video *v3d;
247
248         log_info("deactivating display %p", disp);
249
250         vdrm = video->data;
251         v3d = uterm_drm_video_get_data(video);
252         uterm_drm_display_deactivate(disp, vdrm->fd);
253
254         eglMakeCurrent(v3d->disp, EGL_NO_SURFACE, EGL_NO_SURFACE,
255                        v3d->ctx);
256         eglDestroySurface(v3d->disp, d3d->surface);
257
258         if (d3d->current) {
259                 gbm_surface_release_buffer(d3d->gbm,
260                                            d3d->current->bo);
261                 d3d->current = NULL;
262         }
263         if (d3d->next) {
264                 gbm_surface_release_buffer(d3d->gbm,
265                                            d3d->next->bo);
266                 d3d->next = NULL;
267         }
268
269         gbm_surface_destroy(d3d->gbm);
270         disp->current_mode = NULL;
271 }
272
273 int uterm_drm3d_display_use(struct uterm_display *disp, bool *opengl)
274 {
275         struct uterm_drm3d_display *d3d = uterm_drm_display_get_data(disp);
276         struct uterm_drm3d_video *v3d;
277
278         v3d = uterm_drm_video_get_data(disp->video);
279         if (!eglMakeCurrent(v3d->disp, d3d->surface,
280                             d3d->surface, v3d->ctx)) {
281                 log_error("cannot activate EGL context");
282                 return -EFAULT;
283         }
284
285         if (opengl)
286                 *opengl = true;
287
288         /* TODO: lets find a way how to retrieve the current front buffer */
289         return 0;
290 }
291
292 static int display_swap(struct uterm_display *disp, bool immediate)
293 {
294         int ret;
295         struct gbm_bo *bo;
296         struct uterm_drm3d_rb *rb;
297         struct uterm_drm3d_display *d3d = uterm_drm_display_get_data(disp);
298         struct uterm_video *video = disp->video;
299         struct uterm_drm3d_video *v3d = uterm_drm_video_get_data(video);
300
301         if (!gbm_surface_has_free_buffers(d3d->gbm))
302                 return -EBUSY;
303
304         if (!eglSwapBuffers(v3d->disp, d3d->surface)) {
305                 log_error("cannot swap EGL buffers (%d): %m", errno);
306                 return -EFAULT;
307         }
308
309         bo = gbm_surface_lock_front_buffer(d3d->gbm);
310         if (!bo) {
311                 log_error("cannot lock front buffer");
312                 return -EFAULT;
313         }
314
315         rb = bo_to_rb(disp, bo);
316         if (!rb) {
317                 log_error("cannot lock front gbm buffer (%d): %m", errno);
318                 gbm_surface_release_buffer(d3d->gbm, bo);
319                 return -EFAULT;
320         }
321
322         ret = uterm_drm_display_swap(disp, rb->fb, immediate);
323         if (ret) {
324                 gbm_surface_release_buffer(d3d->gbm, bo);
325                 return ret;
326         }
327
328         if (d3d->next) {
329                 gbm_surface_release_buffer(d3d->gbm, d3d->next->bo);
330                 d3d->next = NULL;
331         }
332
333         if (immediate) {
334                 if (d3d->current)
335                         gbm_surface_release_buffer(d3d->gbm, d3d->current->bo);
336                 d3d->current = rb;
337         } else {
338                 d3d->next = rb;
339         }
340
341         return 0;
342 }
343
344 static const struct display_ops drm_display_ops = {
345         .init = display_init,
346         .destroy = display_destroy,
347         .activate = display_activate,
348         .deactivate = display_deactivate,
349         .set_dpms = uterm_drm_display_set_dpms,
350         .use = uterm_drm3d_display_use,
351         .get_buffers = NULL,
352         .swap = display_swap,
353         .blit = uterm_drm3d_display_blit,
354         .fake_blendv = uterm_drm3d_display_fake_blendv,
355         .fill = uterm_drm3d_display_fill,
356 };
357
358 static void show_displays(struct uterm_video *video)
359 {
360         int ret;
361         struct uterm_display *iter;
362         struct shl_dlist *i;
363
364         if (!video_is_awake(video))
365                 return;
366
367         shl_dlist_for_each(i, &video->displays) {
368                 iter = shl_dlist_entry(i, struct uterm_display, list);
369
370                 if (!display_is_online(iter))
371                         continue;
372                 if (iter->dpms != UTERM_DPMS_ON)
373                         continue;
374
375                 ret = uterm_drm3d_display_use(iter, NULL);
376                 if (ret)
377                         continue;
378
379                 glClearColor(0, 0, 0, 1);
380                 glClear(GL_COLOR_BUFFER_BIT);
381                 display_swap(iter, true);
382         }
383 }
384
385 static void page_flip_handler(struct uterm_display *disp)
386 {
387         struct uterm_drm3d_display *d3d = uterm_drm_display_get_data(disp);
388
389         if (d3d->next) {
390                 if (d3d->current)
391                         gbm_surface_release_buffer(d3d->gbm,
392                                                    d3d->current->bo);
393                 d3d->current = d3d->next;
394                 d3d->next = NULL;
395         }
396 }
397
398 static int video_init(struct uterm_video *video, const char *node)
399 {
400         static const EGLint conf_att[] = {
401                 EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
402                 EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
403                 EGL_RED_SIZE, 1,
404                 EGL_GREEN_SIZE, 1,
405                 EGL_BLUE_SIZE, 1,
406                 EGL_ALPHA_SIZE, 0,
407                 EGL_NONE,
408         };
409         static const EGLint ctx_att[] = {
410                 EGL_CONTEXT_CLIENT_VERSION, 2,
411                 EGL_NONE
412         };
413         const char *ext;
414         int ret;
415         EGLint major, minor, n;
416         EGLenum api;
417         EGLBoolean b;
418         struct uterm_drm_video *vdrm;
419         struct uterm_drm3d_video *v3d;
420
421         v3d = malloc(sizeof(*v3d));
422         if (!v3d)
423                 return -ENOMEM;
424         memset(v3d, 0, sizeof(*v3d));
425
426         ret = uterm_drm_video_init(video, node, &drm_display_ops,
427                                    page_flip_handler, v3d);
428         if (ret)
429                 goto err_free;
430         vdrm = video->data;
431
432         log_debug("initialize 3D layer on %p", video);
433
434         v3d->gbm = gbm_create_device(vdrm->fd);
435         if (!v3d->gbm) {
436                 log_err("cannot create gbm device for %s (permission denied)",
437                         node);
438                 ret = -EFAULT;
439                 goto err_video;
440         }
441
442         v3d->disp = eglGetDisplay((EGLNativeDisplayType) v3d->gbm);
443         if (v3d->disp == EGL_NO_DISPLAY) {
444                 log_err("cannot retrieve egl display for %s", node);
445                 ret = -EFAULT;
446                 goto err_gbm;
447         }
448
449         b = eglInitialize(v3d->disp, &major, &minor);
450         if (!b) {
451                 log_err("cannot init egl display for %s", node);
452                 ret = -EFAULT;
453                 goto err_gbm;
454         }
455
456         log_debug("EGL Init %d.%d", major, minor);
457         log_debug("EGL Version %s", eglQueryString(v3d->disp, EGL_VERSION));
458         log_debug("EGL Vendor %s", eglQueryString(v3d->disp, EGL_VENDOR));
459         ext = eglQueryString(v3d->disp, EGL_EXTENSIONS);
460         log_debug("EGL Extensions %s", ext);
461
462         if (!ext || !strstr(ext, "EGL_KHR_surfaceless_context")) {
463                 log_err("surfaceless opengl not supported");
464                 ret = -EFAULT;
465                 goto err_disp;
466         }
467
468         api = EGL_OPENGL_ES_API;
469         if (!eglBindAPI(api)) {
470                 log_err("cannot bind opengl-es api");
471                 ret = -EFAULT;
472                 goto err_disp;
473         }
474
475         b = eglChooseConfig(v3d->disp, conf_att, &v3d->conf, 1, &n);
476         if (!b || n != 1) {
477                 log_error("cannot find a proper EGL framebuffer configuration");
478                 ret = -EFAULT;
479                 goto err_disp;
480         }
481
482         v3d->ctx = eglCreateContext(v3d->disp, v3d->conf, EGL_NO_CONTEXT,
483                                     ctx_att);
484         if (v3d->ctx == EGL_NO_CONTEXT) {
485                 log_error("cannot create egl context");
486                 ret = -EFAULT;
487                 goto err_disp;
488         }
489
490         if (!eglMakeCurrent(v3d->disp, EGL_NO_SURFACE, EGL_NO_SURFACE,
491                             v3d->ctx)) {
492                 log_error("cannot activate surfaceless EGL context");
493                 ret = -EFAULT;
494                 goto err_ctx;
495         }
496
497         ext = (const char*)glGetString(GL_EXTENSIONS);
498         if (ext && strstr((const char*)ext, "GL_EXT_unpack_subimage"))
499                 v3d->supports_rowlen = true;
500         else
501                 log_warning("your GL implementation does not support GL_EXT_unpack_subimage, rendering may be slower than usual");
502
503         return 0;
504
505 err_ctx:
506         eglDestroyContext(v3d->disp, v3d->ctx);
507 err_disp:
508         eglTerminate(v3d->disp);
509 err_gbm:
510         gbm_device_destroy(v3d->gbm);
511 err_video:
512         uterm_drm_video_destroy(video);
513 err_free:
514         free(v3d);
515         return ret;
516 }
517
518 static void video_destroy(struct uterm_video *video)
519 {
520         struct uterm_drm3d_video *v3d = uterm_drm_video_get_data(video);
521
522         log_info("free drm video device %p", video);
523
524         if (!eglMakeCurrent(v3d->disp, EGL_NO_SURFACE, EGL_NO_SURFACE,
525                             v3d->ctx))
526                 log_error("cannot activate GL context during destruction");
527         uterm_drm3d_deinit_shaders(video);
528
529         eglMakeCurrent(v3d->disp, EGL_NO_SURFACE, EGL_NO_SURFACE,
530                        EGL_NO_CONTEXT);
531         eglDestroyContext(v3d->disp, v3d->ctx);
532         eglTerminate(v3d->disp);
533         gbm_device_destroy(v3d->gbm);
534         free(v3d);
535         uterm_drm_video_destroy(video);
536 }
537
538 static int video_poll(struct uterm_video *video)
539 {
540         return uterm_drm_video_poll(video);
541 }
542
543 static void video_sleep(struct uterm_video *video)
544 {
545         show_displays(video);
546         uterm_drm_video_sleep(video);
547 }
548
549 static int video_wake_up(struct uterm_video *video)
550 {
551         int ret;
552
553         ret = uterm_drm_video_wake_up(video);
554         if (ret) {
555                 uterm_drm_video_arm_vt_timer(video);
556                 return ret;
557         }
558
559         show_displays(video);
560         return 0;
561 }
562
563 static const struct video_ops drm_video_ops = {
564         .init = video_init,
565         .destroy = video_destroy,
566         .segfault = NULL, /* TODO: reset all saved CRTCs on segfault */
567         .poll = video_poll,
568         .sleep = video_sleep,
569         .wake_up = video_wake_up,
570 };
571
572 static const struct uterm_video_module drm3d_module = {
573         .ops = &drm_video_ops,
574 };
575
576 SHL_EXPORT
577 const struct uterm_video_module *UTERM_VIDEO_DRM3D = &drm3d_module;