uterm: share drm-video objects
[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_bind(struct uterm_video *video,
285                            struct uterm_display *disp, drmModeRes *res,
286                            drmModeConnector *conn, int fd)
287 {
288         struct uterm_mode *mode;
289         int ret, i;
290         struct uterm_drm_display *ddrm = disp->data;
291
292         for (i = 0; i < conn->count_modes; ++i) {
293                 ret = mode_new(&mode, &uterm_drm_mode_ops);
294                 if (ret)
295                         continue;
296                 uterm_drm_mode_set(mode, &conn->modes[i]);
297                 mode->next = disp->modes;
298                 disp->modes = mode;
299
300                 /* TODO: more sophisticated default-mode selection */
301                 if (!disp->default_mode)
302                         disp->default_mode = mode;
303         }
304
305         if (!disp->modes) {
306                 log_warn("no valid mode for display found");
307                 return -EFAULT;
308         }
309
310         ddrm->conn_id = conn->connector_id;
311         disp->flags |= DISPLAY_AVAILABLE;
312         disp->next = video->displays;
313         video->displays = disp;
314         disp->dpms = uterm_drm_get_dpms(fd, conn);
315
316         log_info("display %p DPMS is %s", disp,
317                  uterm_dpms_to_name(disp->dpms));
318
319         VIDEO_CB(video, disp, UTERM_NEW);
320         return 0;
321 }
322
323 void uterm_drm_display_unbind(struct uterm_display *disp)
324 {
325         VIDEO_CB(disp->video, disp, UTERM_GONE);
326         uterm_display_deactivate(disp);
327         disp->video = NULL;
328         disp->flags &= ~DISPLAY_AVAILABLE;
329 }
330
331 static void event(struct ev_fd *fd, int mask, void *data)
332 {
333         struct uterm_video *video = data;
334         struct uterm_drm_video *vdrm = video->data;
335         drmEventContext ev;
336
337         /* TODO: run this in a loop with O_NONBLOCK */
338         if (mask & EV_READABLE) {
339                 memset(&ev, 0, sizeof(ev));
340                 ev.version = DRM_EVENT_CONTEXT_VERSION;
341                 ev.page_flip_handler = vdrm->page_flip;
342                 drmHandleEvent(vdrm->fd, &ev);
343         }
344
345         /* TODO: forward HUP to caller */
346         if (mask & (EV_HUP | EV_ERR)) {
347                 log_err("error or hangup on DRM fd");
348                 ev_eloop_rm_fd(vdrm->efd);
349                 vdrm->efd = NULL;
350                 return;
351         }
352 }
353
354 int uterm_drm_video_init(struct uterm_video *video, const char *node,
355                          uterm_drm_page_flip_t pflip, void *data)
356 {
357         struct uterm_drm_video *vdrm;
358         int ret;
359
360         log_info("new drm device via %s", node);
361
362         vdrm = malloc(sizeof(*vdrm));
363         if (!vdrm)
364                 return -ENOMEM;
365         memset(vdrm, 0, sizeof(*vdrm));
366         video->data = vdrm;
367         vdrm->data = data;
368         vdrm->page_flip = pflip;
369
370         vdrm->fd = open(node, O_RDWR | O_CLOEXEC);
371         if (vdrm->fd < 0) {
372                 log_err("cannot open drm device %s (%d): %m", node, errno);
373                 ret = -EFAULT;
374                 goto err_free;
375         }
376         /* TODO: fix the race-condition with DRM-Master-on-open */
377         drmDropMaster(vdrm->fd);
378
379         ret = ev_eloop_new_fd(video->eloop, &vdrm->efd, vdrm->fd, EV_READABLE,
380                               event, video);
381         if (ret)
382                 goto err_close;
383
384         video->flags |= VIDEO_HOTPLUG;
385         return 0;
386
387 err_close:
388         close(vdrm->fd);
389 err_free:
390         free(vdrm);
391         return ret;
392 }
393
394 void uterm_drm_video_destroy(struct uterm_video *video)
395 {
396         struct uterm_drm_video *vdrm = video->data;
397
398         ev_eloop_rm_fd(vdrm->efd);
399         close(vdrm->fd);
400         free(video->data);
401 }
402
403 int uterm_drm_video_find_crtc(struct uterm_video *video, drmModeRes *res,
404                               drmModeEncoder *enc)
405 {
406         int i, crtc;
407         struct uterm_display *iter;
408         struct uterm_drm_display *ddrm;
409
410         for (i = 0; i < res->count_crtcs; ++i) {
411                 if (enc->possible_crtcs & (1 << i)) {
412                         crtc = res->crtcs[i];
413                         for (iter = video->displays; iter; iter = iter->next) {
414                                 ddrm = iter->data;
415                                 if (ddrm->crtc_id == crtc)
416                                         break;
417                         }
418                         if (!iter)
419                                 return crtc;
420                 }
421         }
422
423         return -1;
424 }
425
426 static void bind_display(struct uterm_video *video, drmModeRes *res,
427                          drmModeConnector *conn,
428                          const struct display_ops *ops)
429 {
430         struct uterm_drm_video *vdrm = video->data;
431         struct uterm_display *disp;
432         int ret;
433
434         ret = display_new(&disp, ops, video);
435         if (ret)
436                 return;
437
438         ret = uterm_drm_display_bind(video, disp, res, conn, vdrm->fd);
439         if (ret) {
440                 uterm_display_unref(disp);
441                 return;
442         }
443 }
444
445 static void unbind_display(struct uterm_display *disp)
446 {
447         if (!display_is_conn(disp))
448                 return;
449
450         uterm_drm_display_unbind(disp);
451         uterm_display_unref(disp);
452 }
453
454 int uterm_drm_video_hotplug(struct uterm_video *video,
455                             const struct display_ops *ops)
456 {
457         struct uterm_drm_video *vdrm = video->data;
458         drmModeRes *res;
459         drmModeConnector *conn;
460         struct uterm_display *disp, *tmp;
461         struct uterm_drm_display *ddrm;
462         int i;
463
464         if (!video_is_awake(video) || !video_need_hotplug(video))
465                 return 0;
466
467         res = drmModeGetResources(vdrm->fd);
468         if (!res) {
469                 log_err("cannot retrieve drm resources");
470                 return -EACCES;
471         }
472
473         for (disp = video->displays; disp; disp = disp->next)
474                 disp->flags &= ~DISPLAY_AVAILABLE;
475
476         for (i = 0; i < res->count_connectors; ++i) {
477                 conn = drmModeGetConnector(vdrm->fd, res->connectors[i]);
478                 if (!conn)
479                         continue;
480                 if (conn->connection == DRM_MODE_CONNECTED) {
481                         for (disp = video->displays; disp; disp = disp->next) {
482                                 ddrm = disp->data;
483                                 if (ddrm->conn_id == res->connectors[i]) {
484                                         disp->flags |= DISPLAY_AVAILABLE;
485                                         break;
486                                 }
487                         }
488                         if (!disp)
489                                 bind_display(video, res, conn, ops);
490                 }
491                 drmModeFreeConnector(conn);
492         }
493
494         drmModeFreeResources(res);
495
496         while (video->displays) {
497                 tmp = video->displays;
498                 if (tmp->flags & DISPLAY_AVAILABLE)
499                         break;
500
501                 video->displays = tmp->next;
502                 tmp->next = NULL;
503                 unbind_display(tmp);
504         }
505         for (disp = video->displays; disp && disp->next; ) {
506                 tmp = disp->next;
507                 if (tmp->flags & DISPLAY_AVAILABLE) {
508                         disp = tmp;
509                 } else {
510                         disp->next = tmp->next;
511                         tmp->next = NULL;
512                         unbind_display(tmp);
513                 }
514         }
515
516         video->flags &= ~VIDEO_HOTPLUG;
517         return 0;
518 }