uterm: video: share set_dpms code
[platform/upstream/kmscon.git] / src / uterm_drm_shared.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 shared functions
28  */
29
30 #include <errno.h>
31 #include <fcntl.h>
32 #include <stdbool.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <unistd.h>
36 #include <xf86drm.h>
37 #include <xf86drmMode.h>
38 #include "log.h"
39 #include "uterm_drm_shared_internal.h"
40 #include "uterm_video.h"
41 #include "uterm_video_internal.h"
42
43 #define LOG_SUBSYSTEM "drm_shared"
44
45 int uterm_drm_mode_init(struct uterm_mode *mode)
46 {
47         struct uterm_drm_mode *m;
48
49         m = malloc(sizeof(*m));
50         if (!m)
51                 return -ENOMEM;
52         memset(m, 0, sizeof(*m));
53         mode->data = m;
54
55         return 0;
56 }
57
58 void uterm_drm_mode_destroy(struct uterm_mode *mode)
59 {
60         free(mode->data);
61 }
62
63 const char *uterm_drm_mode_get_name(const struct uterm_mode *mode)
64 {
65         struct uterm_drm_mode *m = mode->data;
66
67         return m->info.name;
68 }
69
70 unsigned int uterm_drm_mode_get_width(const struct uterm_mode *mode)
71 {
72         struct uterm_drm_mode *m = mode->data;
73
74         return m->info.hdisplay;
75 }
76
77 unsigned int uterm_drm_mode_get_height(const struct uterm_mode *mode)
78 {
79         struct uterm_drm_mode *m = mode->data;
80
81         return m->info.vdisplay;
82 }
83
84 void uterm_drm_mode_set(struct uterm_mode *mode, drmModeModeInfo *info)
85 {
86         struct uterm_drm_mode *m = mode->data;
87
88         m->info = *info;
89 }
90
91 const struct mode_ops uterm_drm_mode_ops = {
92         .init = uterm_drm_mode_init,
93         .destroy = uterm_drm_mode_destroy,
94         .get_name = uterm_drm_mode_get_name,
95         .get_width = uterm_drm_mode_get_width,
96         .get_height = uterm_drm_mode_get_height,
97 };
98
99 int uterm_drm_set_dpms(int fd, uint32_t conn_id, int state)
100 {
101         int i, ret, set;
102         drmModeConnector *conn;
103         drmModePropertyRes *prop;
104
105         switch (state) {
106         case UTERM_DPMS_ON:
107                 set = DRM_MODE_DPMS_ON;
108                 break;
109         case UTERM_DPMS_STANDBY:
110                 set = DRM_MODE_DPMS_STANDBY;
111                 break;
112         case UTERM_DPMS_SUSPEND:
113                 set = DRM_MODE_DPMS_SUSPEND;
114                 break;
115         case UTERM_DPMS_OFF:
116                 set = DRM_MODE_DPMS_OFF;
117                 break;
118         default:
119                 return -EINVAL;
120         }
121
122         conn = drmModeGetConnector(fd, conn_id);
123         if (!conn) {
124                 log_err("cannot get display connector");
125                 return -EFAULT;
126         }
127
128         ret = state;
129         for (i = 0; i < conn->count_props; ++i) {
130                 prop = drmModeGetProperty(fd, conn->props[i]);
131                 if (!prop) {
132                         log_error("cannot get DRM property (%d): %m", errno);
133                         continue;
134                 }
135
136                 if (!strcmp(prop->name, "DPMS")) {
137                         ret = drmModeConnectorSetProperty(fd, conn_id,
138                                                           prop->prop_id, set);
139                         if (ret) {
140                                 log_info("cannot set DPMS");
141                                 ret = -EFAULT;
142                         }
143                         drmModeFreeProperty(prop);
144                         break;
145                 }
146                 drmModeFreeProperty(prop);
147         }
148
149         if (i == conn->count_props) {
150                 log_warn("display does not support DPMS");
151                 ret = UTERM_DPMS_UNKNOWN;
152         }
153
154         drmModeFreeConnector(conn);
155         return ret;
156 }
157
158 int uterm_drm_get_dpms(int fd, drmModeConnector *conn)
159 {
160         int i, ret;
161         drmModePropertyRes *prop;
162
163         for (i = 0; i < conn->count_props; ++i) {
164                 prop = drmModeGetProperty(fd, conn->props[i]);
165                 if (!prop) {
166                         log_error("cannot get DRM property (%d): %m", errno);
167                         continue;
168                 }
169
170                 if (!strcmp(prop->name, "DPMS")) {
171                         switch (conn->prop_values[i]) {
172                         case DRM_MODE_DPMS_ON:
173                                 ret = UTERM_DPMS_ON;
174                                 break;
175                         case DRM_MODE_DPMS_STANDBY:
176                                 ret = UTERM_DPMS_STANDBY;
177                                 break;
178                         case DRM_MODE_DPMS_SUSPEND:
179                                 ret = UTERM_DPMS_SUSPEND;
180                                 break;
181                         case DRM_MODE_DPMS_OFF:
182                         default:
183                                 ret = UTERM_DPMS_OFF;
184                         }
185
186                         drmModeFreeProperty(prop);
187                         return ret;
188                 }
189                 drmModeFreeProperty(prop);
190         }
191
192         if (i == conn->count_props)
193                 log_warn("display does not support DPMS");
194         return UTERM_DPMS_UNKNOWN;
195 }
196
197 int uterm_drm_display_init(struct uterm_display *disp, void *data)
198 {
199         struct uterm_drm_display *d;
200
201         d = malloc(sizeof(*d));
202         if (!d)
203                 return -ENOMEM;
204         memset(d, 0, sizeof(*d));
205         disp->data = d;
206         d->data = data;
207
208         return 0;
209 }
210
211 void uterm_drm_display_destroy(struct uterm_display *disp)
212 {
213         free(disp->data);
214 }
215
216 int uterm_drm_display_activate(struct uterm_display *disp, int fd)
217 {
218         struct uterm_video *video = disp->video;
219         struct uterm_drm_display *ddrm = disp->data;
220         drmModeRes *res;
221         drmModeConnector *conn;
222         drmModeEncoder *enc;
223         int crtc, i;
224
225         res = drmModeGetResources(fd);
226         if (!res) {
227                 log_err("cannot get resources for display %p", disp);
228                 return -EFAULT;
229         }
230         conn = drmModeGetConnector(fd, ddrm->conn_id);
231         if (!conn) {
232                 log_err("cannot get connector for display %p", disp);
233                 drmModeFreeResources(res);
234                 return -EFAULT;
235         }
236
237         crtc = -1;
238         for (i = 0; i < conn->count_encoders; ++i) {
239                 enc = drmModeGetEncoder(fd, conn->encoders[i]);
240                 if (!enc)
241                         continue;
242                 crtc = uterm_drm_video_find_crtc(video, res, enc);
243                 drmModeFreeEncoder(enc);
244                 if (crtc >= 0)
245                         break;
246         }
247
248         drmModeFreeConnector(conn);
249         drmModeFreeResources(res);
250
251         if (crtc < 0) {
252                 log_warn("cannot find crtc for new display");
253                 return -ENODEV;
254         }
255
256         ddrm->crtc_id = crtc;
257         if (ddrm->saved_crtc)
258                 drmModeFreeCrtc(ddrm->saved_crtc);
259         ddrm->saved_crtc = drmModeGetCrtc(fd, ddrm->crtc_id);
260
261         return 0;
262 }
263
264 void uterm_drm_display_deactivate(struct uterm_display *disp, int fd)
265 {
266         struct uterm_drm_display *ddrm = disp->data;
267
268         if (ddrm->saved_crtc) {
269                 if (disp->video->flags & VIDEO_AWAKE) {
270                         drmModeSetCrtc(fd, ddrm->saved_crtc->crtc_id,
271                                        ddrm->saved_crtc->buffer_id,
272                                        ddrm->saved_crtc->x,
273                                        ddrm->saved_crtc->y,
274                                        &ddrm->conn_id, 1,
275                                        &ddrm->saved_crtc->mode);
276                 }
277                 drmModeFreeCrtc(ddrm->saved_crtc);
278                 ddrm->saved_crtc = NULL;
279         }
280
281         ddrm->crtc_id = 0;
282 }
283
284 int uterm_drm_display_set_dpms(struct uterm_display *disp, int state)
285 {
286         int ret;
287         struct uterm_drm_display *ddrm = disp->data;
288         struct uterm_drm_video *vdrm;
289
290         if (!display_is_conn(disp) || !video_is_awake(disp->video))
291                 return -EINVAL;
292
293         vdrm = disp->video->data;
294         log_info("setting DPMS of display %p to %s", disp,
295                  uterm_dpms_to_name(state));
296
297         ret = uterm_drm_set_dpms(vdrm->fd, ddrm->conn_id, state);
298         if (ret < 0)
299                 return ret;
300
301         disp->dpms = ret;
302         return 0;
303 }
304
305 int uterm_drm_display_bind(struct uterm_video *video,
306                            struct uterm_display *disp, drmModeRes *res,
307                            drmModeConnector *conn, int fd)
308 {
309         struct uterm_mode *mode;
310         int ret, i;
311         struct uterm_drm_display *ddrm = disp->data;
312
313         for (i = 0; i < conn->count_modes; ++i) {
314                 ret = mode_new(&mode, &uterm_drm_mode_ops);
315                 if (ret)
316                         continue;
317                 uterm_drm_mode_set(mode, &conn->modes[i]);
318                 mode->next = disp->modes;
319                 disp->modes = mode;
320
321                 /* TODO: more sophisticated default-mode selection */
322                 if (!disp->default_mode)
323                         disp->default_mode = mode;
324         }
325
326         if (!disp->modes) {
327                 log_warn("no valid mode for display found");
328                 return -EFAULT;
329         }
330
331         ddrm->conn_id = conn->connector_id;
332         disp->flags |= DISPLAY_AVAILABLE;
333         disp->next = video->displays;
334         video->displays = disp;
335         disp->dpms = uterm_drm_get_dpms(fd, conn);
336
337         log_info("display %p DPMS is %s", disp,
338                  uterm_dpms_to_name(disp->dpms));
339
340         VIDEO_CB(video, disp, UTERM_NEW);
341         return 0;
342 }
343
344 void uterm_drm_display_unbind(struct uterm_display *disp)
345 {
346         VIDEO_CB(disp->video, disp, UTERM_GONE);
347         uterm_display_deactivate(disp);
348         disp->video = NULL;
349         disp->flags &= ~DISPLAY_AVAILABLE;
350 }
351
352 static void event(struct ev_fd *fd, int mask, void *data)
353 {
354         struct uterm_video *video = data;
355         struct uterm_drm_video *vdrm = video->data;
356         drmEventContext ev;
357
358         /* TODO: run this in a loop with O_NONBLOCK */
359         if (mask & EV_READABLE) {
360                 memset(&ev, 0, sizeof(ev));
361                 ev.version = DRM_EVENT_CONTEXT_VERSION;
362                 ev.page_flip_handler = vdrm->page_flip;
363                 drmHandleEvent(vdrm->fd, &ev);
364         }
365
366         /* TODO: forward HUP to caller */
367         if (mask & (EV_HUP | EV_ERR)) {
368                 log_err("error or hangup on DRM fd");
369                 ev_eloop_rm_fd(vdrm->efd);
370                 vdrm->efd = NULL;
371                 return;
372         }
373 }
374
375 int uterm_drm_video_init(struct uterm_video *video, const char *node,
376                          uterm_drm_page_flip_t pflip, void *data)
377 {
378         struct uterm_drm_video *vdrm;
379         int ret;
380
381         log_info("new drm device via %s", node);
382
383         vdrm = malloc(sizeof(*vdrm));
384         if (!vdrm)
385                 return -ENOMEM;
386         memset(vdrm, 0, sizeof(*vdrm));
387         video->data = vdrm;
388         vdrm->data = data;
389         vdrm->page_flip = pflip;
390
391         vdrm->fd = open(node, O_RDWR | O_CLOEXEC);
392         if (vdrm->fd < 0) {
393                 log_err("cannot open drm device %s (%d): %m", node, errno);
394                 ret = -EFAULT;
395                 goto err_free;
396         }
397         /* TODO: fix the race-condition with DRM-Master-on-open */
398         drmDropMaster(vdrm->fd);
399
400         ret = ev_eloop_new_fd(video->eloop, &vdrm->efd, vdrm->fd, EV_READABLE,
401                               event, video);
402         if (ret)
403                 goto err_close;
404
405         video->flags |= VIDEO_HOTPLUG;
406         return 0;
407
408 err_close:
409         close(vdrm->fd);
410 err_free:
411         free(vdrm);
412         return ret;
413 }
414
415 void uterm_drm_video_destroy(struct uterm_video *video)
416 {
417         struct uterm_drm_video *vdrm = video->data;
418
419         ev_eloop_rm_fd(vdrm->efd);
420         close(vdrm->fd);
421         free(video->data);
422 }
423
424 int uterm_drm_video_find_crtc(struct uterm_video *video, drmModeRes *res,
425                               drmModeEncoder *enc)
426 {
427         int i, crtc;
428         struct uterm_display *iter;
429         struct uterm_drm_display *ddrm;
430
431         for (i = 0; i < res->count_crtcs; ++i) {
432                 if (enc->possible_crtcs & (1 << i)) {
433                         crtc = res->crtcs[i];
434                         for (iter = video->displays; iter; iter = iter->next) {
435                                 ddrm = iter->data;
436                                 if (ddrm->crtc_id == crtc)
437                                         break;
438                         }
439                         if (!iter)
440                                 return crtc;
441                 }
442         }
443
444         return -1;
445 }
446
447 static void bind_display(struct uterm_video *video, drmModeRes *res,
448                          drmModeConnector *conn,
449                          const struct display_ops *ops)
450 {
451         struct uterm_drm_video *vdrm = video->data;
452         struct uterm_display *disp;
453         int ret;
454
455         ret = display_new(&disp, ops, video);
456         if (ret)
457                 return;
458
459         ret = uterm_drm_display_bind(video, disp, res, conn, vdrm->fd);
460         if (ret) {
461                 uterm_display_unref(disp);
462                 return;
463         }
464 }
465
466 static void unbind_display(struct uterm_display *disp)
467 {
468         if (!display_is_conn(disp))
469                 return;
470
471         uterm_drm_display_unbind(disp);
472         uterm_display_unref(disp);
473 }
474
475 int uterm_drm_video_hotplug(struct uterm_video *video,
476                             const struct display_ops *ops)
477 {
478         struct uterm_drm_video *vdrm = video->data;
479         drmModeRes *res;
480         drmModeConnector *conn;
481         struct uterm_display *disp, *tmp;
482         struct uterm_drm_display *ddrm;
483         int i;
484
485         if (!video_is_awake(video) || !video_need_hotplug(video))
486                 return 0;
487
488         res = drmModeGetResources(vdrm->fd);
489         if (!res) {
490                 log_err("cannot retrieve drm resources");
491                 return -EACCES;
492         }
493
494         for (disp = video->displays; disp; disp = disp->next)
495                 disp->flags &= ~DISPLAY_AVAILABLE;
496
497         for (i = 0; i < res->count_connectors; ++i) {
498                 conn = drmModeGetConnector(vdrm->fd, res->connectors[i]);
499                 if (!conn)
500                         continue;
501                 if (conn->connection == DRM_MODE_CONNECTED) {
502                         for (disp = video->displays; disp; disp = disp->next) {
503                                 ddrm = disp->data;
504                                 if (ddrm->conn_id == res->connectors[i]) {
505                                         disp->flags |= DISPLAY_AVAILABLE;
506                                         break;
507                                 }
508                         }
509                         if (!disp)
510                                 bind_display(video, res, conn, ops);
511                 }
512                 drmModeFreeConnector(conn);
513         }
514
515         drmModeFreeResources(res);
516
517         while (video->displays) {
518                 tmp = video->displays;
519                 if (tmp->flags & DISPLAY_AVAILABLE)
520                         break;
521
522                 video->displays = tmp->next;
523                 tmp->next = NULL;
524                 unbind_display(tmp);
525         }
526         for (disp = video->displays; disp && disp->next; ) {
527                 tmp = disp->next;
528                 if (tmp->flags & DISPLAY_AVAILABLE) {
529                         disp = tmp;
530                 } else {
531                         disp->next = tmp->next;
532                         tmp->next = NULL;
533                         unbind_display(tmp);
534                 }
535         }
536
537         video->flags &= ~VIDEO_HOTPLUG;
538         return 0;
539 }
540
541 int uterm_drm_video_wake_up(struct uterm_video *video,
542                             const struct display_ops *ops)
543 {
544         int ret;
545         struct uterm_drm_video *vdrm = video->data;
546
547         ret = drmSetMaster(vdrm->fd);
548         if (ret) {
549                 log_err("cannot set DRM-master");
550                 return -EACCES;
551         }
552
553         video->flags |= VIDEO_AWAKE;
554         ret = uterm_drm_video_hotplug(video, ops);
555         if (ret) {
556                 video->flags &= ~VIDEO_AWAKE;
557                 drmDropMaster(vdrm->fd);
558                 return ret;
559         }
560
561         return 0;
562 }
563
564 void uterm_drm_video_sleep(struct uterm_video *video)
565 {
566         struct uterm_drm_video *vdrm = video->data;
567
568         drmDropMaster(vdrm->fd);
569         video->flags &= ~VIDEO_AWAKE;
570 }
571
572 int uterm_drm_video_poll(struct uterm_video *video,
573                          const struct display_ops *ops)
574 {
575         video->flags |= VIDEO_HOTPLUG;
576         return uterm_drm_video_hotplug(video, ops);
577 }