#include <errno.h>
#include <fcntl.h>
+#include <poll.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
-#include "log.h"
+#include "shl_log.h"
+#include "shl_timer.h"
#include "uterm_drm_shared_internal.h"
#include "uterm_video.h"
#include "uterm_video_internal.h"
{
struct uterm_drm_display *ddrm = disp->data;
+ uterm_drm_display_wait_pflip(disp);
+
if (ddrm->saved_crtc) {
if (disp->video->flags & VIDEO_AWAKE) {
drmModeSetCrtc(fd, ddrm->saved_crtc->crtc_id,
}
ddrm->crtc_id = 0;
+ disp->flags &= ~(DISPLAY_VSYNC | DISPLAY_ONLINE | DISPLAY_PFLIP);
}
-int uterm_drm_display_bind(struct uterm_video *video,
- struct uterm_display *disp, drmModeRes *res,
- drmModeConnector *conn, int fd)
+int uterm_drm_display_set_dpms(struct uterm_display *disp, int state)
{
- struct uterm_mode *mode;
- int ret, i;
+ int ret;
struct uterm_drm_display *ddrm = disp->data;
+ struct uterm_drm_video *vdrm = disp->video->data;
- for (i = 0; i < conn->count_modes; ++i) {
- ret = mode_new(&mode, &uterm_drm_mode_ops);
- if (ret)
- continue;
- uterm_drm_mode_set(mode, &conn->modes[i]);
- mode->next = disp->modes;
- disp->modes = mode;
+ log_info("setting DPMS of display %p to %s", disp,
+ uterm_dpms_to_name(state));
- /* TODO: more sophisticated default-mode selection */
- if (!disp->default_mode)
- disp->default_mode = mode;
- }
+ ret = uterm_drm_set_dpms(vdrm->fd, ddrm->conn_id, state);
+ if (ret < 0)
+ return ret;
- if (!disp->modes) {
- log_warn("no valid mode for display found");
- return -EFAULT;
+ disp->dpms = ret;
+ return 0;
+}
+
+int uterm_drm_display_wait_pflip(struct uterm_display *disp)
+{
+ struct uterm_video *video = disp->video;
+ int ret;
+ unsigned int timeout = 1000; /* 1s */
+
+ if ((disp->flags & DISPLAY_PFLIP) || !(disp->flags & DISPLAY_VSYNC))
+ return 0;
+
+ do {
+ ret = uterm_drm_video_wait_pflip(video, &timeout);
+ if (ret < 1)
+ break;
+ else if ((disp->flags & DISPLAY_PFLIP))
+ break;
+ } while (timeout > 0);
+
+ if (ret < 0)
+ return ret;
+ if (ret == 0 || !timeout) {
+ log_warning("timeout waiting for page-flip on display %p",
+ disp);
+ return -ETIMEDOUT;
}
- ddrm->conn_id = conn->connector_id;
- disp->flags |= DISPLAY_AVAILABLE;
- disp->next = video->displays;
- video->displays = disp;
- disp->dpms = uterm_drm_get_dpms(fd, conn);
+ return 0;
+}
- log_info("display %p DPMS is %s", disp,
- uterm_dpms_to_name(disp->dpms));
+int uterm_drm_display_swap(struct uterm_display *disp, uint32_t fb,
+ bool immediate)
+{
+ struct uterm_drm_display *ddrm = disp->data;
+ struct uterm_video *video = disp->video;
+ struct uterm_drm_video *vdrm = video->data;
+ int ret;
+ drmModeModeInfo *mode;
+
+ if (disp->dpms != UTERM_DPMS_ON)
+ return -EINVAL;
+
+ if (immediate) {
+ ret = uterm_drm_display_wait_pflip(disp);
+ if (ret)
+ return ret;
+
+ mode = uterm_drm_mode_get_info(disp->current_mode);
+ ret = drmModeSetCrtc(vdrm->fd, ddrm->crtc_id, fb, 0, 0,
+ &ddrm->conn_id, 1, mode);
+ if (ret) {
+ log_error("cannot set DRM-CRTC (%d): %m", errno);
+ return -EFAULT;
+ }
+ } else {
+ if ((disp->flags & DISPLAY_VSYNC))
+ return -EBUSY;
+
+ ret = drmModePageFlip(vdrm->fd, ddrm->crtc_id, fb,
+ DRM_MODE_PAGE_FLIP_EVENT, disp);
+ if (ret) {
+ log_error("cannot page-flip on DRM-CRTC (%d): %m",
+ errno);
+ return -EFAULT;
+ }
+
+ uterm_display_ref(disp);
+ disp->flags |= DISPLAY_VSYNC;
+ }
- VIDEO_CB(video, disp, UTERM_NEW);
return 0;
}
-void uterm_drm_display_unbind(struct uterm_display *disp)
+static void uterm_drm_display_pflip(struct uterm_display *disp)
{
- VIDEO_CB(disp->video, disp, UTERM_GONE);
- uterm_display_deactivate(disp);
- disp->video = NULL;
- disp->flags &= ~DISPLAY_AVAILABLE;
+ struct uterm_drm_video *vdrm = disp->video->data;
+
+ disp->flags &= ~(DISPLAY_PFLIP | DISPLAY_VSYNC);
+ if (vdrm->page_flip)
+ vdrm->page_flip(disp);
+
+ DISPLAY_CB(disp, UTERM_PAGE_FLIP);
}
-static void event(struct ev_fd *fd, int mask, void *data)
+static void display_event(int fd, unsigned int frame, unsigned int sec,
+ unsigned int usec, void *data)
+{
+ struct uterm_display *disp = data;
+
+ if (disp->video && (disp->flags & DISPLAY_VSYNC))
+ disp->flags |= DISPLAY_PFLIP;
+
+ uterm_display_unref(disp);
+}
+
+static int uterm_drm_video_read_events(struct uterm_video *video)
{
- struct uterm_video *video = data;
struct uterm_drm_video *vdrm = video->data;
drmEventContext ev;
+ int ret;
- /* TODO: run this in a loop with O_NONBLOCK */
- if (mask & EV_READABLE) {
- memset(&ev, 0, sizeof(ev));
- ev.version = DRM_EVENT_CONTEXT_VERSION;
- ev.page_flip_handler = vdrm->page_flip;
- drmHandleEvent(vdrm->fd, &ev);
+ /* TODO: DRM subsystem does not support non-blocking reads and it also
+ * doesn't return 0/-1 if the device is dead. This can lead to serious
+ * deadlocks in userspace if we read() after a device was unplugged. Fix
+ * this upstream and then make this code actually loop. */
+ memset(&ev, 0, sizeof(ev));
+ ev.version = DRM_EVENT_CONTEXT_VERSION;
+ ev.page_flip_handler = display_event;
+ errno = 0;
+ ret = drmHandleEvent(vdrm->fd, &ev);
+
+ if (ret < 0 && errno != EAGAIN)
+ return -EFAULT;
+
+ return 0;
+}
+
+static void do_pflips(struct ev_eloop *eloop, void *unused, void *data)
+{
+ struct uterm_video *video = data;
+ struct uterm_display *disp;
+ struct shl_dlist *iter;
+
+ shl_dlist_for_each(iter, &video->displays) {
+ disp = shl_dlist_entry(iter, struct uterm_display, list);
+ if ((disp->flags & DISPLAY_PFLIP))
+ uterm_drm_display_pflip(disp);
}
+}
+
+static void io_event(struct ev_fd *fd, int mask, void *data)
+{
+ struct uterm_video *video = data;
+ struct uterm_drm_video *vdrm = video->data;
+ struct uterm_display *disp;
+ struct shl_dlist *iter;
+ int ret;
/* TODO: forward HUP to caller */
if (mask & (EV_HUP | EV_ERR)) {
vdrm->efd = NULL;
return;
}
+
+ if (!(mask & EV_READABLE))
+ return;
+
+ ret = uterm_drm_video_read_events(video);
+ if (ret)
+ return;
+
+ shl_dlist_for_each(iter, &video->displays) {
+ disp = shl_dlist_entry(iter, struct uterm_display, list);
+ if ((disp->flags & DISPLAY_PFLIP))
+ uterm_drm_display_pflip(disp);
+ }
+}
+
+static void vt_timeout(struct ev_timer *timer, uint64_t exp, void *data)
+{
+ struct uterm_video *video = data;
+ struct uterm_drm_video *vdrm = video->data;
+ struct uterm_display *disp;
+ struct shl_dlist *iter;
+ int r;
+
+ r = uterm_drm_video_wake_up(video);
+ if (!r) {
+ ev_timer_update(vdrm->vt_timer, NULL);
+ shl_dlist_for_each(iter, &video->displays) {
+ disp = shl_dlist_entry(iter, struct uterm_display, list);
+ VIDEO_CB(video, disp, UTERM_REFRESH);
+ }
+ }
+}
+
+void uterm_drm_video_arm_vt_timer(struct uterm_video *video)
+{
+ struct uterm_drm_video *vdrm = video->data;
+ struct itimerspec spec;
+
+ spec.it_value.tv_sec = 0;
+ spec.it_value.tv_nsec = 20L * 1000L * 1000L; /* 20ms */
+ spec.it_interval = spec.it_value;
+
+ ev_timer_update(vdrm->vt_timer, &spec);
}
int uterm_drm_video_init(struct uterm_video *video, const char *node,
+ const struct display_ops *display_ops,
uterm_drm_page_flip_t pflip, void *data)
{
struct uterm_drm_video *vdrm;
video->data = vdrm;
vdrm->data = data;
vdrm->page_flip = pflip;
+ vdrm->display_ops = display_ops;
- vdrm->fd = open(node, O_RDWR | O_CLOEXEC);
+ vdrm->fd = open(node, O_RDWR | O_CLOEXEC | O_NONBLOCK);
if (vdrm->fd < 0) {
log_err("cannot open drm device %s (%d): %m", node, errno);
ret = -EFAULT;
drmDropMaster(vdrm->fd);
ret = ev_eloop_new_fd(video->eloop, &vdrm->efd, vdrm->fd, EV_READABLE,
- event, video);
+ io_event, video);
if (ret)
goto err_close;
+ ret = shl_timer_new(&vdrm->timer);
+ if (ret)
+ goto err_fd;
+
+ ret = ev_eloop_new_timer(video->eloop, &vdrm->vt_timer, NULL,
+ vt_timeout, video);
+ if (ret)
+ goto err_timer;
+
video->flags |= VIDEO_HOTPLUG;
return 0;
+err_timer:
+ shl_timer_free(vdrm->timer);
+err_fd:
+ ev_eloop_rm_fd(vdrm->efd);
err_close:
close(vdrm->fd);
err_free:
{
struct uterm_drm_video *vdrm = video->data;
+ ev_eloop_rm_timer(vdrm->vt_timer);
+ ev_eloop_unregister_idle_cb(video->eloop, do_pflips, video, EV_SINGLE);
+ shl_timer_free(vdrm->timer);
ev_eloop_rm_fd(vdrm->efd);
close(vdrm->fd);
free(video->data);
int i, crtc;
struct uterm_display *iter;
struct uterm_drm_display *ddrm;
+ struct shl_dlist *it;
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) {
+ shl_dlist_for_each(it, &video->displays) {
+ iter = shl_dlist_entry(it,
+ struct uterm_display,
+ list);
ddrm = iter->data;
if (ddrm->crtc_id == crtc)
break;
}
- if (!iter)
+ if (it == &video->displays)
return crtc;
}
}
}
static void bind_display(struct uterm_video *video, drmModeRes *res,
- drmModeConnector *conn,
- const struct display_ops *ops)
+ drmModeConnector *conn)
{
struct uterm_drm_video *vdrm = video->data;
struct uterm_display *disp;
- int ret;
+ struct uterm_drm_display *ddrm;
+ struct uterm_mode *mode;
+ int ret, i;
- ret = display_new(&disp, ops, video);
+ ret = display_new(&disp, vdrm->display_ops);
if (ret)
return;
+ ddrm = disp->data;
- ret = uterm_drm_display_bind(video, disp, res, conn, vdrm->fd);
- if (ret) {
- uterm_display_unref(disp);
- return;
+ for (i = 0; i < conn->count_modes; ++i) {
+ ret = mode_new(&mode, &uterm_drm_mode_ops);
+ if (ret)
+ continue;
+
+ uterm_drm_mode_set(mode, &conn->modes[i]);
+
+ ret = uterm_mode_bind(mode, disp);
+ if (ret) {
+ uterm_mode_unref(mode);
+ continue;
+ }
+
+ /* TODO: more sophisticated default-mode selection */
+ if (!disp->default_mode)
+ disp->default_mode = mode;
+
+ uterm_mode_unref(mode);
}
-}
-static void unbind_display(struct uterm_display *disp)
-{
- if (!display_is_conn(disp))
- return;
+ if (shl_dlist_empty(&disp->modes)) {
+ log_warn("no valid mode for display found");
+ ret = -EFAULT;
+ goto err_unref;
+ }
- uterm_drm_display_unbind(disp);
+ ddrm->conn_id = conn->connector_id;
+ disp->flags |= DISPLAY_AVAILABLE;
+ disp->dpms = uterm_drm_get_dpms(vdrm->fd, conn);
+
+ log_info("display %p DPMS is %s", disp,
+ uterm_dpms_to_name(disp->dpms));
+
+ ret = uterm_display_bind(disp, video);
+ if (ret)
+ goto err_unref;
+
+ uterm_display_unref(disp);
+ return;
+
+err_unref:
uterm_display_unref(disp);
+ return;
}
int uterm_drm_video_hotplug(struct uterm_video *video,
- const struct display_ops *ops)
+ bool read_dpms, bool modeset)
{
struct uterm_drm_video *vdrm = video->data;
drmModeRes *res;
drmModeConnector *conn;
- struct uterm_display *disp, *tmp;
+ struct uterm_display *disp;
struct uterm_drm_display *ddrm;
- int i;
+ int i, dpms;
+ struct shl_dlist *iter, *tmp;
if (!video_is_awake(video) || !video_need_hotplug(video))
return 0;
+ log_debug("testing DRM hotplug status");
+
res = drmModeGetResources(vdrm->fd);
if (!res) {
log_err("cannot retrieve drm resources");
return -EACCES;
}
- for (disp = video->displays; disp; disp = disp->next)
+ shl_dlist_for_each(iter, &video->displays) {
+ disp = shl_dlist_entry(iter, struct uterm_display, list);
disp->flags &= ~DISPLAY_AVAILABLE;
+ }
for (i = 0; i < res->count_connectors; ++i) {
conn = drmModeGetConnector(vdrm->fd, res->connectors[i]);
if (!conn)
continue;
- if (conn->connection == DRM_MODE_CONNECTED) {
- for (disp = video->displays; disp; disp = disp->next) {
- ddrm = disp->data;
- if (ddrm->conn_id == res->connectors[i]) {
- disp->flags |= DISPLAY_AVAILABLE;
- break;
+ if (conn->connection != DRM_MODE_CONNECTED) {
+ drmModeFreeConnector(conn);
+ continue;
+ }
+
+ shl_dlist_for_each(iter, &video->displays) {
+ disp = shl_dlist_entry(iter, struct uterm_display,
+ list);
+ ddrm = disp->data;
+
+ if (ddrm->conn_id != res->connectors[i])
+ continue;
+
+ disp->flags |= DISPLAY_AVAILABLE;
+ if (!display_is_online(disp))
+ break;
+
+ if (read_dpms) {
+ dpms = uterm_drm_get_dpms(vdrm->fd, conn);
+ if (dpms != disp->dpms) {
+ log_debug("DPMS state for display %p changed",
+ disp);
+ uterm_drm_display_set_dpms(disp, disp->dpms);
}
}
- if (!disp)
- bind_display(video, res, conn, ops);
+
+ if (modeset) {
+ log_debug("re-activate display %p", disp);
+ uterm_display_use(disp, NULL);
+ uterm_display_swap(disp, true);
+ }
+
+ break;
}
+
+ if (iter == &video->displays)
+ 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);
- }
+ shl_dlist_for_each_safe(iter, tmp, &video->displays) {
+ disp = shl_dlist_entry(iter, struct uterm_display, list);
+ if (!(disp->flags & DISPLAY_AVAILABLE))
+ uterm_display_unbind(disp);
}
video->flags &= ~VIDEO_HOTPLUG;
return 0;
}
-int uterm_drm_video_wake_up(struct uterm_video *video,
- const struct display_ops *ops)
+int uterm_drm_video_wake_up(struct uterm_video *video)
{
int ret;
struct uterm_drm_video *vdrm = video->data;
return -EACCES;
}
- video->flags |= VIDEO_AWAKE;
- ret = uterm_drm_video_hotplug(video, ops);
+ video->flags |= VIDEO_AWAKE | VIDEO_HOTPLUG;
+ ret = uterm_drm_video_hotplug(video, true, true);
if (ret) {
- video->flags &= ~VIDEO_AWAKE;
drmDropMaster(vdrm->fd);
return ret;
}
struct uterm_drm_video *vdrm = video->data;
drmDropMaster(vdrm->fd);
- video->flags &= ~VIDEO_AWAKE;
+ ev_timer_drain(vdrm->vt_timer, NULL);
+ ev_timer_update(vdrm->vt_timer, NULL);
}
-int uterm_drm_video_poll(struct uterm_video *video,
- const struct display_ops *ops)
+int uterm_drm_video_poll(struct uterm_video *video)
{
video->flags |= VIDEO_HOTPLUG;
- return uterm_drm_video_hotplug(video, ops);
+ return uterm_drm_video_hotplug(video, false, false);
+}
+
+/* Waits for events on DRM fd for \mtimeout milliseconds and returns 0 if the
+ * timeout expired, -ERR on errors and 1 if a page-flip event has been read.
+ * \mtimeout is adjusted to the remaining time. */
+int uterm_drm_video_wait_pflip(struct uterm_video *video,
+ unsigned int *mtimeout)
+{
+ struct uterm_drm_video *vdrm = video->data;
+ struct pollfd pfd;
+ int ret;
+ uint64_t elapsed;
+
+ shl_timer_start(vdrm->timer);
+
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = vdrm->fd;
+ pfd.events = POLLIN;
+
+ log_debug("waiting for pageflip on %p", video);
+ ret = poll(&pfd, 1, *mtimeout);
+
+ elapsed = shl_timer_stop(vdrm->timer);
+ *mtimeout = *mtimeout - (elapsed / 1000 + 1);
+
+ if (ret < 0) {
+ log_error("poll() failed on DRM fd (%d): %m", errno);
+ return -EFAULT;
+ } else if (!ret) {
+ log_warning("timeout waiting for page-flip on %p", video);
+ return 0;
+ } else if ((pfd.revents & POLLIN)) {
+ ret = uterm_drm_video_read_events(video);
+ if (ret)
+ return ret;
+
+ ret = ev_eloop_register_idle_cb(video->eloop, do_pflips,
+ video, EV_ONESHOT | EV_SINGLE);
+ if (ret)
+ return ret;
+
+ return 1;
+ } else {
+ log_debug("poll() HUP/ERR on DRM fd (%d)", pfd.revents);
+ return -EFAULT;
+ }
}