From 95e434eddb8686c3d7a4875ef541ec310cac6500 Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Fri, 23 Mar 2012 11:45:49 +0100 Subject: [PATCH] uterm_video: add DRM backend uterm_video can work with any backend so we need a DRM backend to get the same functionality as our previous compositor/output API. Signed-off-by: David Herrmann --- Makefile.am | 3 +- configure.ac | 2 + src/uterm_internal.h | 5 + src/uterm_video_drm.c | 893 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 902 insertions(+), 1 deletion(-) create mode 100644 src/uterm_video_drm.c diff --git a/Makefile.am b/Makefile.am index d5387e1..31e1840 100644 --- a/Makefile.am +++ b/Makefile.am @@ -53,7 +53,8 @@ libkmscon_core_la_SOURCES = \ src/terminal.c src/terminal.h \ src/pty.c src/pty.h \ src/uterm.h src/uterm_internal.h \ - src/uterm_video.c + src/uterm_video.c \ + src/uterm_video_drm.c if USE_XKBCOMMON libkmscon_core_la_SOURCES += \ diff --git a/configure.ac b/configure.ac index d760160..21adf8e 100644 --- a/configure.ac +++ b/configure.ac @@ -118,5 +118,7 @@ if test ! x$debug = xyes ; then AC_DEFINE([NDEBUG], [1], [No Debug]) fi +AC_DEFINE([UTERM_HAVE_DRM], [1], [Use DRM uterm backend]) + AC_CONFIG_FILES([Makefile]) AC_OUTPUT([src/genshader.c]) diff --git a/src/uterm_internal.h b/src/uterm_internal.h index 53387d2..3534ccb 100644 --- a/src/uterm_internal.h +++ b/src/uterm_internal.h @@ -275,4 +275,9 @@ static inline bool video_is_awake(const struct uterm_video *video) return video->flags & VIDEO_AWAKE; } +static inline bool video_need_hotplug(const struct uterm_video *video) +{ + return video->flags & VIDEO_HOTPLUG; +} + #endif /* UTERM_INTERNAL_H */ diff --git a/src/uterm_video_drm.c b/src/uterm_video_drm.c new file mode 100644 index 0000000..ba4c3ff --- /dev/null +++ b/src/uterm_video_drm.c @@ -0,0 +1,893 @@ +/* + * uterm - Linux User-Space Terminal + * + * Copyright (c) 2011-2012 David Herrmann + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * DRM Video backend + */ + +#define EGL_EGLEXT_PROTOTYPES +#define GL_GLEXT_PROTOTYPES + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* #include TODO: missing header protection */ +#include "eloop.h" +#include "log.h" +#include "uterm.h" +#include "uterm_internal.h" + +#define LOG_SUBSYSTEM "video_drm" + +static const char *mode_get_name(const struct uterm_mode *mode) +{ + return mode->drm.info.name; +} + +static unsigned int mode_get_width(const struct uterm_mode *mode) +{ + return mode->drm.info.hdisplay; +} + +static unsigned int mode_get_height(const struct uterm_mode *mode) +{ + return mode->drm.info.vdisplay; +} + +static int init_rb(struct uterm_display *disp, struct drm_rb *rb) +{ + unsigned int stride, handle; + int ret; + struct uterm_video *video = disp->video; + + rb->bo = gbm_bo_create(video->drm.gbm, + disp->current_mode->drm.info.hdisplay, + disp->current_mode->drm.info.vdisplay, + GBM_BO_FORMAT_XRGB8888, + GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); + if (!rb->bo) { + log_err("cannot create gbm-bo"); + return -EFAULT; + } + + stride = gbm_bo_get_pitch(rb->bo); + handle = gbm_bo_get_handle(rb->bo).u32; + + /* TODO: how can we choose 24/32 dynamically? */ + ret = drmModeAddFB(video->drm.fd, + disp->current_mode->drm.info.hdisplay, + disp->current_mode->drm.info.vdisplay, + 24, 32, stride, handle, &rb->fb); + if (ret) { + log_err("cannot add drm-fb"); + ret = -EFAULT; + goto err_gbm; + } + + rb->image = eglCreateImageKHR(video->drm.disp, NULL, + EGL_NATIVE_PIXMAP_KHR, rb->bo, NULL); + if (!rb->image) { + log_err("cannot create egl image"); + ret = -EFAULT; + goto err_fb; + } + + glGenRenderbuffers(1, &rb->rb); + glBindRenderbuffer(GL_RENDERBUFFER, rb->rb); + glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, rb->image); + + return 0; + +err_fb: + drmModeRmFB(video->drm.fd, rb->fb); +err_gbm: + gbm_bo_destroy(rb->bo); + return ret; +} + +static void destroy_rb(struct uterm_display *disp, struct drm_rb *rb) +{ + glBindRenderbuffer(GL_RENDERBUFFER, 0); + glDeleteRenderbuffers(1, &rb->rb); + eglDestroyImageKHR(disp->video->drm.disp, rb->image); + drmModeRmFB(disp->video->drm.fd, rb->fb); + gbm_bo_destroy(rb->bo); +} + +static int find_crtc(struct uterm_video *video, drmModeRes *res, + drmModeEncoder *enc) +{ + int i, crtc; + struct uterm_display *iter; + + for (i = 0; i < res->count_crtcs; ++i) { + if (enc->possible_crtcs & (1 << i)) { + crtc = res->crtcs[i]; + for (iter = video->displays; iter; iter = iter->next) { + if (iter->drm.crtc_id == crtc) + break; + } + if (!iter) + return crtc; + } + } + + return -1; +} + +static int display_activate(struct uterm_display *disp, struct uterm_mode *mode) +{ + struct uterm_video *video = disp->video; + int ret, crtc, i; + drmModeRes *res; + drmModeConnector *conn; + drmModeEncoder *enc; + + if (!video || !video_is_awake(video) || !mode) + return -EINVAL; + if (display_is_online(disp)) + return 0; + + log_info("activating display %p to %ux%u", disp, + mode->drm.info.hdisplay, mode->drm.info.vdisplay); + + res = drmModeGetResources(video->drm.fd); + if (!res) { + log_err("cannot get resources for display %p", disp); + return -EFAULT; + } + conn = drmModeGetConnector(video->drm.fd, disp->drm.conn_id); + if (!conn) { + log_err("cannot get connector for display %p", disp); + drmModeFreeResources(res); + return -EFAULT; + } + + crtc = -1; + for (i = 0; i < conn->count_encoders; ++i) { + enc = drmModeGetEncoder(video->drm.fd, conn->encoders[i]); + if (!enc) + continue; + crtc = find_crtc(video, res, enc); + drmModeFreeEncoder(enc); + if (crtc >= 0) + break; + } + + drmModeFreeConnector(conn); + drmModeFreeResources(res); + + if (crtc < 0) { + log_warn("cannot find crtc for new display"); + return -ENODEV; + } + + disp->drm.crtc_id = crtc; + disp->drm.current_rb = 0; + disp->current_mode = mode; + disp->drm.saved_crtc = drmModeGetCrtc(video->drm.fd, + disp->drm.crtc_id); + + ret = init_rb(disp, &disp->drm.rb[0]); + if (ret) + goto err_saved; + + ret = init_rb(disp, &disp->drm.rb[1]); + if (ret) + goto err_rb; + + glGenFramebuffers(1, &disp->drm.fb); + glBindFramebuffer(GL_FRAMEBUFFER, disp->drm.fb); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_RENDERBUFFER, disp->drm.rb[1].rb); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != + GL_FRAMEBUFFER_COMPLETE) { + log_err("cannot create gl-framebuffer"); + ret = -EFAULT; + goto err_fb; + } + + ret = drmModeSetCrtc(video->drm.fd, disp->drm.crtc_id, + disp->drm.rb[0].fb, 0, 0, &disp->drm.conn_id, 1, + &disp->current_mode->drm.info); + if (ret) { + log_err("cannot set drm-crtc"); + ret = -EFAULT; + goto err_fb; + } + + disp->flags |= DISPLAY_ONLINE; + return 0; + +err_fb: + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &disp->drm.fb); + destroy_rb(disp, &disp->drm.rb[1]); +err_rb: + destroy_rb(disp, &disp->drm.rb[0]); +err_saved: + disp->current_mode = NULL; + if (disp->drm.saved_crtc) { + drmModeFreeCrtc(disp->drm.saved_crtc); + disp->drm.saved_crtc = NULL; + } + return ret; +} + +static void display_deactivate(struct uterm_display *disp) +{ + if (!display_is_online(disp)) + return; + + if (disp->drm.saved_crtc) { + if (disp->video->flags & VIDEO_AWAKE) { + drmModeSetCrtc(disp->video->drm.fd, + disp->drm.saved_crtc->crtc_id, + disp->drm.saved_crtc->buffer_id, + disp->drm.saved_crtc->x, + disp->drm.saved_crtc->y, + &disp->drm.conn_id, + 1, + &disp->drm.saved_crtc->mode); + } + drmModeFreeCrtc(disp->drm.saved_crtc); + disp->drm.saved_crtc = NULL; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &disp->drm.fb); + destroy_rb(disp, &disp->drm.rb[1]); + destroy_rb(disp, &disp->drm.rb[0]); + disp->current_mode = NULL; + disp->flags &= ~(DISPLAY_ONLINE | DISPLAY_VSYNC); + log_info("deactivating display %p", disp); +} + +static int display_set_dpms(struct uterm_display *disp, int state) +{ + int i, ret, set; + drmModeConnector *conn; + drmModePropertyRes *prop; + + if (!display_is_conn(disp) || !video_is_awake(disp->video)) + return -EINVAL; + + switch (state) { + case UTERM_DPMS_ON: + set = DRM_MODE_DPMS_ON; + break; + case UTERM_DPMS_STANDBY: + set = DRM_MODE_DPMS_STANDBY; + break; + case UTERM_DPMS_SUSPEND: + set = DRM_MODE_DPMS_SUSPEND; + break; + case UTERM_DPMS_OFF: + set = DRM_MODE_DPMS_OFF; + break; + default: + return -EINVAL; + } + + log_info("setting DPMS of display %p to %s", disp, + uterm_dpms_to_name(state)); + + conn = drmModeGetConnector(disp->video->drm.fd, disp->drm.conn_id); + if (!conn) { + log_err("cannot get display connector"); + return -EFAULT; + } + + for (i = 0; i < conn->count_props; ++i) { + prop = drmModeGetProperty(disp->video->drm.fd, conn->props[i]); + if (!prop) + continue; + + if (!strcmp(prop->name, "DPMS")) { + ret = drmModeConnectorSetProperty(disp->video->drm.fd, + disp->drm.conn_id, prop->prop_id, set); + if (ret) { + log_info("cannot set DPMS"); + ret = -EFAULT; + } + drmModeFreeProperty(prop); + break; + } + drmModeFreeProperty(prop); + } + + if (i == conn->count_props) { + ret = 0; + log_warn("display does not support DPMS"); + state = UTERM_DPMS_UNKNOWN; + } + + drmModeFreeConnector(conn); + disp->dpms = state; + return ret; +} + +static int display_use(struct uterm_display *disp) +{ + if (!display_is_online(disp)) + return -EINVAL; + + /* TODO: we need triple buffering as a VSYNC may still be pending */ + glBindFramebuffer(GL_FRAMEBUFFER, disp->drm.fb); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_RENDERBUFFER, disp->drm.rb[disp->drm.current_rb ^ 1].rb); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != + GL_FRAMEBUFFER_COMPLETE) { + log_warn("cannot set gl-renderbuffer"); + return -EFAULT; + } + + return 0; +} + +static int display_swap(struct uterm_display *disp) +{ + int ret; + + if (!display_is_conn(disp) || !video_is_awake(disp->video)) + return -EINVAL; + if (disp->dpms != UTERM_DPMS_ON) + return -EINVAL; + + /* TODO: is glFlush sufficient here? */ + glFinish(); + + errno = 0; + disp->drm.current_rb ^= 1; + ret = drmModePageFlip(disp->video->drm.fd, disp->drm.crtc_id, + disp->drm.rb[disp->drm.current_rb].fb, + DRM_MODE_PAGE_FLIP_EVENT, disp); + if (ret) { + log_warn("page-flip failed %d %d", ret, errno); + return -EFAULT; + } + uterm_display_ref(disp); + disp->flags |= DISPLAY_VSYNC; + + return 0; +} + +static int get_dpms(struct uterm_display *disp, drmModeConnector *conn) +{ + int i, ret; + drmModePropertyRes *prop; + + for (i = 0; i < conn->count_props; ++i) { + prop = drmModeGetProperty(disp->video->drm.fd, conn->props[i]); + if (!prop) + continue; + + if (!strcmp(prop->name, "DPMS")) { + switch (conn->prop_values[i]) { + case DRM_MODE_DPMS_ON: + ret = UTERM_DPMS_ON; + break; + case DRM_MODE_DPMS_STANDBY: + ret = UTERM_DPMS_STANDBY; + break; + case DRM_MODE_DPMS_SUSPEND: + ret = UTERM_DPMS_SUSPEND; + break; + case DRM_MODE_DPMS_OFF: + default: + ret = UTERM_DPMS_OFF; + } + + drmModeFreeProperty(prop); + return ret; + } + drmModeFreeProperty(prop); + } + + if (i == conn->count_props) + log_warn("display does not support DPMS"); + return UTERM_DPMS_UNKNOWN; +} + +static void bind_display(struct uterm_video *video, drmModeRes *res, + drmModeConnector *conn) +{ + struct uterm_display *disp; + struct uterm_mode *mode; + int ret, i; + + ret = display_new(&disp, &drm_display_ops); + if (ret) + return; + + for (i = 0; i < conn->count_modes; ++i) { + ret = mode_new(&mode, &drm_mode_ops); + if (ret) + continue; + mode->drm.info = conn->modes[i]; + mode->next = disp->modes; + disp->modes = mode; + + /* TODO: more sophisticated default-mode selection */ + if (!disp->default_mode) + disp->default_mode = mode; + } + + if (!disp->modes) { + log_warn("no valid mode for display found"); + uterm_display_unref(disp); + return; + } + + disp->video = video; + disp->drm.conn_id = conn->connector_id; + disp->flags |= DISPLAY_AVAILABLE; + disp->next = video->displays; + video->displays = disp; + disp->dpms = get_dpms(disp, conn); + log_info("display %p DPMS is %s", disp, + uterm_dpms_to_name(disp->dpms)); +} + +static void unbind_display(struct uterm_display *disp) +{ + if (!display_is_conn(disp)) + return; + + display_deactivate(disp); + disp->video = NULL; + disp->flags &= ~DISPLAY_AVAILABLE; + uterm_display_unref(disp); +} + +static int get_id(struct udev_device *dev) +{ + const char *name; + char *end; + int devnum; + + name = udev_device_get_sysname(dev); + if (!name) + return -ENODEV; + if (strncmp(name, "card", 4) || !name[4]) + return -ENODEV; + + devnum = strtol(&name[4], &end, 10); + if (devnum < 0 || *end) + return -ENODEV; + + return devnum; +} + +static void page_flip_handler(int fd, unsigned int frame, unsigned int sec, + unsigned int usec, void *data) +{ + struct uterm_display *disp = data; + + disp->flags &= ~DISPLAY_VSYNC; + uterm_display_unref(disp); +} + +static void event(struct ev_fd *fd, int mask, void *data) +{ + struct uterm_video *video = data; + drmEventContext ev; + + if (mask & (EV_HUP | EV_ERR)) { + log_err("error or hangup on DRM fd"); + ev_eloop_rm_fd(video->drm.efd); + video->drm.efd = NULL; + return; + } + + if (mask & EV_READABLE) { + memset(&ev, 0, sizeof(ev)); + ev.version = DRM_EVENT_CONTEXT_VERSION; + ev.page_flip_handler = page_flip_handler; + drmHandleEvent(video->drm.fd, &ev); + } +} + +static int init_device(struct uterm_video *video, struct udev_device *dev) +{ + const char *node, *ext; + int ret; + EGLint major, minor; + EGLenum api; + static const EGLint ctx_att[] = + { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; + struct drm_video *drm = &video->drm; + + log_info("probing %s", udev_device_get_sysname(dev)); + + node = udev_device_get_devnode(dev); + if (!node) { + log_err("cannot get device node"); + return -ENODEV; + } + + drm->id = get_id(dev); + if (drm->id < 0) { + log_err("cannot get device id"); + return -ENODEV; + } + + drm->fd = open(node, O_RDWR | O_CLOEXEC); + if (drm->fd < 0) { + log_err("cannot open drm device %s (%d): %m", node, errno); + return -EFAULT; + } + drmDropMaster(drm->fd); + + drm->gbm = gbm_create_device(drm->fd); + if (!drm->gbm) { + log_err("cannot create drm device for %s", node); + ret = -EFAULT; + goto err_close; + } + + drm->disp = eglGetDisplay((EGLNativeDisplayType) drm->gbm); + if (!drm->disp) { + log_err("cannot retrieve egl display for %s", node); + ret = -EFAULT; + goto err_gbm; + } + + ret = eglInitialize(drm->disp, &major, &minor); + if (!ret) { + log_err("cannot init egl display for %s", node); + ret = -EFAULT; + goto err_gbm; + } + + ext = eglQueryString(drm->disp, EGL_EXTENSIONS); + if (!ext || !strstr(ext, "EGL_KHR_surfaceless_opengl")) { + log_err("surfaceless opengl not supported"); + ret = -EFAULT; + goto err_disp; + } + + api = EGL_OPENGL_ES_API; + /* TODO: allow api = EGL_OPENGL_API */ + if (!eglBindAPI(api)) { + log_err("cannot bind opengl-es api"); + ret = -EFAULT; + goto err_disp; + } + + drm->ctx = eglCreateContext(drm->disp, NULL, EGL_NO_CONTEXT, ctx_att); + if (!drm->ctx) { + log_err("cannot create egl context"); + ret = -EFAULT; + goto err_disp; + } + + /* + * We allow only one global video object. See uterm_video_new for the + * reasons. If we every change this we need proper context-management + * and should remove this call here. + */ + if (!eglMakeCurrent(drm->disp, EGL_NO_SURFACE, EGL_NO_SURFACE, + drm->ctx)) { + log_err("cannot activate egl context"); + ret = -EFAULT; + goto err_ctx; + } + + ret = ev_eloop_new_fd(video->eloop, &drm->efd, drm->fd, + EV_READABLE, event, video); + if (ret) + goto err_ctx; + + video->flags |= VIDEO_HOTPLUG; + log_info("new drm device via %s", node); + + return 0; + +err_ctx: + eglDestroyContext(drm->disp, drm->ctx); +err_disp: + eglTerminate(drm->disp); +err_gbm: + gbm_device_destroy(drm->gbm); +err_close: + close(drm->fd); + return ret; +} + +static int video_init(struct uterm_video *video) +{ + struct udev_enumerate *e; + struct udev_list_entry *name; + const char *path; + struct udev_device *dev; + int ret; + + ret = udev_monitor_filter_add_match_subsystem_devtype(video->umon, + "drm", "drm_minor"); + if (ret) { + log_err("cannot add udev filter (%d): %m", ret); + return -EFAULT; + } + + ret = udev_monitor_enable_receiving(video->umon); + if (ret) { + log_err("cannot start udev_monitor (%d): %m", ret); + return -EFAULT; + } + + e = udev_enumerate_new(video->udev); + if (!e) { + log_err("cannot create udev_enumerate object"); + return -EFAULT; + } + + ret = udev_enumerate_add_match_subsystem(e, "drm"); + if (ret) { + log_err("cannot add udev match (%d): %m", ret); + ret = -EFAULT; + goto err_enum; + } + ret = udev_enumerate_add_match_sysname(e, "card[0-9]*"); + if (ret) { + log_err("cannot add udev match (%d): %m", ret); + ret = -EFAULT; + goto err_enum; + } + ret = udev_enumerate_scan_devices(e); + if (ret) { + log_err("cannot scan udev devices (%d): %m", ret); + ret = -EFAULT; + goto err_enum; + } + + udev_list_entry_foreach(name, udev_enumerate_get_list_entry(e)) { + path = udev_list_entry_get_name(name); + if (!path || !*path) + continue; + dev = udev_device_new_from_syspath(video->udev, path); + if (!dev) + continue; + + /* TODO: more sophisticated device selection */ + ret = init_device(video, dev); + udev_device_unref(dev); + if (!ret) + goto done; + } + + ret = -ENODEV; + log_err("no drm device found"); + goto err_enum; + +done: + ret = 0; +err_enum: + udev_enumerate_unref(e); + return ret; +} + +static void video_destroy(struct uterm_video *video) +{ + struct drm_video *drm = &video->drm; + struct uterm_display *disp; + + while ((disp = video->displays)) { + video->displays = disp->next; + disp->next = NULL; + unbind_display(disp); + } + + log_info("free drm device"); + ev_eloop_rm_fd(drm->efd); + eglDestroyContext(drm->disp, drm->ctx); + eglTerminate(drm->disp); + gbm_device_destroy(drm->gbm); + drmDropMaster(drm->fd); + close(drm->fd); +} + +static int hotplug(struct uterm_video *video) +{ + drmModeRes *res; + drmModeConnector *conn; + struct uterm_display *disp, *tmp; + unsigned int i; + + if (!video_is_awake(video) || !video_need_hotplug(video)) + return 0; + + res = drmModeGetResources(video->drm.fd); + if (!res) { + log_err("cannot retrieve drm resources"); + return -EACCES; + } + + for (disp = video->displays; disp; disp = disp->next) + disp->flags &= ~DISPLAY_AVAILABLE; + + for (i = 0; i < res->count_connectors; ++i) { + conn = drmModeGetConnector(video->drm.fd, res->connectors[i]); + if (!conn) + continue; + if (conn->connection == DRM_MODE_CONNECTED) { + for (disp = video->displays; disp; disp = disp->next) { + if (disp->drm.conn_id == res->connectors[i]) { + disp->flags |= DISPLAY_AVAILABLE; + break; + } + } + if (!disp) + bind_display(video, res, conn); + } + drmModeFreeConnector(conn); + } + + drmModeFreeResources(res); + + while (video->displays) { + tmp = video->displays; + if (tmp->flags & DISPLAY_AVAILABLE) + break; + + video->displays = tmp->next; + tmp->next = NULL; + unbind_display(tmp); + } + for (disp = video->displays; disp && disp->next; ) { + tmp = disp->next; + if (tmp->flags & DISPLAY_AVAILABLE) { + disp = tmp; + } else { + disp->next = tmp->next; + tmp->next = NULL; + unbind_display(tmp); + } + } + + video->flags &= ~VIDEO_HOTPLUG; + return 0; +} + +static int monitor(struct uterm_video *video) +{ + struct udev_device *dev; + const char *action, *val; + unsigned int id; + + dev = udev_monitor_receive_device(video->umon); + if (!dev) { + log_warn("cannot receive device from udev_monitor"); + return 0; + } + + action = udev_device_get_action(dev); + if (!action) + goto ignore; + if (strcmp(action, "change")) + goto ignore; + + val = udev_device_get_property_value(dev, "HOTPLUG"); + if (strcmp(val, "1")) + goto ignore; + + id = get_id(dev); + if (id != video->drm.id) + goto ignore; + + video->flags |= VIDEO_HOTPLUG; +ignore: + udev_device_unref(dev); + return 0; +} + +static int video_poll(struct uterm_video *video, int mask) +{ + int ret; + + if (mask & (EV_HUP | EV_ERR)) { + log_err("udev_monitor closed unexpectedly"); + return -EFAULT; + } + + if (mask & EV_READABLE) { + ret = monitor(video); + if (ret) + return ret; + } + + return hotplug(video); +} + +static void video_sleep(struct uterm_video *video) +{ + if (!video_is_awake(video)) + return; + + drmDropMaster(video->drm.fd); + video->flags &= ~VIDEO_AWAKE; +} + +static int video_wake_up(struct uterm_video *video) +{ + int ret; + + if (video_is_awake(video)) + return 0; + + ret = drmSetMaster(video->drm.fd); + if (ret) { + log_err("cannot set DRM-master"); + return -EACCES; + } + + video->flags |= VIDEO_AWAKE; + ret = hotplug(video); + if (ret) { + video->flags &= ~VIDEO_AWAKE; + drmDropMaster(video->drm.fd); + return ret; + } + + return 0; +} + +const struct mode_ops drm_mode_ops = { + .init = NULL, + .destroy = NULL, + .get_name = mode_get_name, + .get_width = mode_get_width, + .get_height = mode_get_height, +}; + +const struct display_ops drm_display_ops = { + .init = NULL, + .destroy = NULL, + .activate = display_activate, + .deactivate = display_deactivate, + .set_dpms = display_set_dpms, + .use = display_use, + .swap = display_swap, +}; + +const struct video_ops drm_video_ops = { + .init = video_init, + .destroy = video_destroy, + .segfault = NULL, /* TODO: reset all saved CRTCs on segfault */ + .poll = video_poll, + .sleep = video_sleep, + .wake_up = video_wake_up, +};