[systemd-devel] [RFC 07/12] gfx: add graphics layer

David Herrmann dh.herrmann at gmail.com
Wed Nov 27 10:48:42 PST 2013


This adds the graphics-layer to sd-gfx. The graphics layer is based on
linux DRM+KMS. No other devices are supported (fbdev is obsolete!). The
API is rather complex but tries to hide as many DRM details as possible.

DRM divides each display-controller into cards. They are accessible via
/dev/dri/card<num>. Each card is independent of the others and you have to
create a separate sd_gfx_card for each of them. This is the based object
for DRM devices.

A single DRM card can drive many displays. The pipeline to drive a single
display can be quite complex (especially if multiple displays are run in
hardware clone-mode). On a single pipe/CRTC there may be many active
encoders, connectors, framebuffers, planes and more. The sd_gfx_card
objects hides most of this.

For each active pipe, sd_gfx_card advertises an sd_gfx_pipe to the
application. This is done on runtime to support monitor hotplugging. A
pipe is the main object applications deal with. It has an associated mode,
planes and framebuffers and an application should treat a pipe as a single
active connected monitor (although there might be multiple connectors in
clone-mode). A Wake-up event is sent when a pipe gets active (normally on
hotplug) and a Sleep event is sent when a pipe gets inactive. Furthermore,
on mode-change, session-change etc., the same is done. So applications can
treat WAKE_UP as "start rendering" and SLEEP as "stop rendering".

To render, applications need framebuffers. They are represented as
sd_gfx_fb. Framebuffers can be allocated by an application on request
(although, some cards may only provide a single framebuffer, applications
need to correctly deal with such cases). Direct memory-access to
framebuffers is hidden by sd_gfx_fb, but helpers are provided to
blit/blend data onto the framebuffer. Drivers may not allow direct
memory-access to framebuffers, so this needs to be hidden. More helpers
may be added once needed.

A framebuffer itself is useless as it is an off-screen buffer. To display
it, you need to attach it to a pipe. Each pipe can usually have only a
single backing framebuffer. However, hardware-blending/blitting may be
available, so another layer has to be introduced: planes

Planes are hardware entities which take a framebuffer and attach it to a
pipe. Each pipe has always one primary plane, which is the bottom-most
plane and the main entity used by applications. Simply attach your
framebuffer to the primary-plane and it will automatically get displayed
on the pipe.
However, some hardware provides additional planes. You can attach other
framebuffers to these planes and then attach the plane to a pipe. You can
specify offsets/sizes so the plane is scaled/moved before it is
blitted/blended over the primary plane. This allows hardware 2D
compositing.
Note that non-primary planes are not yet supported by the sd-gfx layer as
atomic-modesetting is not yet merged upstream.

Last but not least, we need to take vertical-blanks (VBLANK/VSYNC) into
account. Each active pipe always has an attached primary plane with an
attached front framebuffer. If you render into the front frameuffer, you
may see artifacts as not the whole screen is updated at once. Thus, you
should always allocate a second framebuffer to render into. Once your
frame is finished you swap both buffers so the whole screen is updated at
once. To avoid tearing, this swap must occur during a VBLANK. The
sd_gfx_plane object provides a swap/page-flip helper which schedules a
swap for the next VBLANK and provides an event to you once that happened.
---
 Makefile.am                  |    1 +
 configure.ac                 |    2 +-
 src/libsystemd-gfx/gfx-drm.c | 2551 ++++++++++++++++++++++++++++++++++++++++++
 src/systemd/sd-gfx.h         |  195 ++++
 4 files changed, 2748 insertions(+), 1 deletion(-)
 create mode 100644 src/libsystemd-gfx/gfx-drm.c

diff --git a/Makefile.am b/Makefile.am
index 6ecbd50..70148c5 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3859,6 +3859,7 @@ noinst_LTLIBRARIES += \
 
 libsystemd_gfx_la_SOURCES = \
 	src/libsystemd-gfx/sd-gfx.h \
+	src/libsystemd-gfx/gfx-drm.c \
 	src/libsystemd-gfx/gfx-kbd.c \
 	src/libsystemd-gfx/gfx-unifont.c
 
diff --git a/configure.ac b/configure.ac
index 74c37ae..b76a86d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -294,7 +294,7 @@ AM_CONDITIONAL(HAVE_KMOD, [test "$have_kmod" = "yes"])
 have_gfx=no
 AC_ARG_ENABLE(gfx, AS_HELP_STRING([--disable-gfx], [disable sd-gfx graphics and input support]))
 if test "x$enable_gfx" != "xno"; then
-        PKG_CHECK_MODULES(GFX, [ libevdev >= 0.4 xkbcommon >= 0.3 ],
+        PKG_CHECK_MODULES(GFX, [ libdrm >= 2.4.45 libevdev >= 0.4 xkbcommon >= 0.3 ],
                 [AC_DEFINE(HAVE_GFX, 1, [Define if sd-gfx is built]) have_gfx=yes], have_gfx=no)
         if test "x$have_gfx" = xno -a "x$enable_gfx" = xyes; then
                 AC_MSG_ERROR([*** sd-gfx support requested, but libraries not found])
diff --git a/src/libsystemd-gfx/gfx-drm.c b/src/libsystemd-gfx/gfx-drm.c
new file mode 100644
index 0000000..b0ce87f
--- /dev/null
+++ b/src/libsystemd-gfx/gfx-drm.c
@@ -0,0 +1,2551 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 David Herrmann <dh.herrmann at gmail.com>
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+#include <drm.h>
+#include <drm_fourcc.h>
+
+#include "def.h"
+#include "log.h"
+#include "macro.h"
+#include "missing.h"
+#include "sd-event.h"
+#include "sd-gfx.h"
+#include "util.h"
+
+typedef struct gfx_config_pipe gfx_config_pipe;
+typedef struct gfx_connector gfx_connector;
+typedef struct gfx_encoder gfx_encoder;
+
+/* clock-wise framebuffer rotation */
+enum {
+        GFX_ROTATE_0,
+        GFX_ROTATE_90,
+        GFX_ROTATE_180,
+        GFX_ROTATE_270,
+};
+
+struct gfx_config_pipe {
+        unsigned int config_id;
+        char **connectors;
+        char *mode;
+        unsigned int rotate;
+
+        unsigned int disable : 1;
+};
+
+struct sd_gfx_fb {
+        unsigned long ref;
+        sd_gfx_plane *plane;
+        sd_gfx_fb_unlink_fn unlink_fn;
+        sd_gfx_fb_unpin_fn unpin_fn;
+        void *fn_data;
+        void *data;
+
+        uint32_t fb;
+        uint32_t format;
+        uint32_t handles[4];
+        uint32_t width;
+        uint32_t height;
+        uint32_t strides[4];
+        uint64_t size;
+        uint8_t *map;
+};
+
+struct sd_gfx_plane {
+        unsigned long ref;
+        sd_gfx_card *card;
+        sd_gfx_pipe *pipe;
+        void *data;
+        void *fn_data;
+        sd_gfx_plane_prepare_fn prepare_fn;
+        sd_gfx_plane_cancel_fn cancel_fn;
+        sd_gfx_plane_finish_fn finish_fn;
+        uint32_t plane_id;
+
+        size_t fb_count;
+        sd_gfx_fb **fbs;
+
+        sd_gfx_fb *current;
+        sd_gfx_fb *swap;
+        sd_gfx_fb *swap_to;
+
+        unsigned int primary : 1;
+        unsigned int enabled : 1;
+        unsigned int change : 1;
+};
+
+struct sd_gfx_mode {
+        unsigned long ref;
+        drmModeModeInfo info;
+};
+
+struct gfx_connector {
+        sd_gfx_card *card;
+        unsigned int id;
+        uint32_t connector_id;
+        const char *type;
+        uint32_t type_id;
+
+        /* kernel setup */
+        gfx_encoder *encoder;
+        uint32_t possible_encoders;
+        uint32_t possible_pipes;
+        unsigned int mode_count;
+        sd_gfx_mode **modes;
+        sd_gfx_mode *preferred_mode;
+        sd_gfx_mode *default_mode;
+        unsigned int dpms;
+        uint32_t dpms_prop;
+
+        /* connector setup */
+        sd_gfx_pipe *pipe;
+
+        /* wanted setup */
+        gfx_config_pipe *want_config;
+
+        /* state */
+        unsigned int assigned : 1;
+        unsigned int connected : 1;
+};
+
+struct gfx_encoder {
+        sd_gfx_card *card;
+        unsigned int id;
+        uint32_t encoder_id;
+
+        /* kernel setup */
+        sd_gfx_pipe *pipe;
+        uint32_t possible_pipes;
+        uint32_t possible_clones;
+};
+
+struct sd_gfx_pipe {
+        unsigned long ref;
+        sd_gfx_card *card;
+        unsigned int id;
+        sd_gfx_plane *primary;
+        void *data;
+        void *fn_data;
+
+        /* kernel setup */
+        drmModeCrtc *kern_crtc;
+        gfx_connector *kern_connector;
+        uint32_t crtc_id;
+        sd_gfx_mode *kern_mode;
+
+        /* pipe setup */
+        unsigned int rotation;
+        unsigned int connector_count;
+        gfx_connector **connectors;
+        uint32_t *connector_ids;
+        sd_gfx_mode *mode;
+
+        /* wanted setup */
+        unsigned int want_rotation;
+        unsigned int want_connector_count;
+        gfx_connector **want_connectors;
+        uint32_t *want_connector_ids;
+        sd_gfx_mode *want_mode;
+
+        /* page-flip counters */
+        uint64_t page_flip;
+        uint64_t page_flip_cnt;
+
+        /* state */
+        unsigned int assigned : 1;
+};
+
+struct sd_gfx_card {
+        char *name;
+        sd_gfx_card_event_fn event_fn;
+        void *data;
+        void *fn_data;
+        int fd;
+        sd_event *event;
+        sd_event_source *fd_source;
+
+        unsigned int pipe_ids;
+        sd_gfx_pipe **pipes;
+        unsigned int encoder_ids;
+        gfx_encoder **encoders;
+        unsigned int connector_ids;
+        gfx_connector **connectors;
+
+        gfx_connector **tcons;
+        gfx_config_pipe *configs;
+
+        unsigned int public : 1;
+        unsigned int awake : 1;
+        unsigned int real_awake : 1;
+        unsigned int tory : 1;
+        unsigned int clone_tory : 1;
+};
+
+static void gfx_card_async_sleep(sd_gfx_card *card);
+
+/*
+ * Page-flip data
+ * The DRM API allows us to store 64bit of data with each scheduled page-flip.
+ * The data is returned alongside the page-flip event. However, drivers tend to
+ * spuriously drop page-flip events if we reset CRTCs in between. Furthermore,
+ * on fast session-switches, page-flip events may be reported *after* we got
+ * reactivated, thus, we might have already scheduled a new page-flip.
+ * To reliable track page-flips, we use the 64bit arg to store the pipe-ID
+ * (5 bit) together with a 59bit counter. The counter allows to see whether a
+ * retrieved page-flip event is really the event we're waiting for or whether
+ * it was already superceeded by a following page-flip call.
+ */
+
+static uint64_t gfx_encode_arg(uint64_t arg, unsigned int id) {
+        return (arg << 5ULL) | (id & 0x1fULL);
+}
+
+static void gfx_decode_arg(uint64_t *arg, unsigned int *id) {
+        *id = *arg & 0x1fULL;
+        *arg >>= 5ULL;
+}
+
+/*
+ * Framebuffers
+ * Each FB represent a rectangular buffer with image data. A framebuffer may be
+ * a back buffer (currently not shown on screen) or a front-buffer (currently
+ * used as scanout-buffer). You should normally allocate at least two buffers
+ * so you never have to draw into a front-buffer (otherwise, tearing might
+ * occur).
+ *
+ * Framebuffers are always attached to a plane. Once a plane changes its mode,
+ * framebuffers might get orphaned and unlinked. So you should always react to
+ * plane-reconfigurations to retrieve the current framebuffers and screen
+ * sizes.
+ */
+
+static int gfx_find_slot(sd_gfx_plane *plane, sd_gfx_fb ***slot) {
+        sd_gfx_fb **t;
+        size_t num, i;
+
+        for (i = 0; i < plane->fb_count; ++i) {
+                if (!plane->fbs[i]) {
+                        *slot = &plane->fbs[i];
+                        return 0;
+                }
+        }
+
+        num = plane->fb_count ? plane->fb_count * 2 : 2;
+        if (num <= plane->fb_count)
+                return -ENOMEM;
+
+        t = realloc(plane->fbs, sizeof(*plane->fbs) * num);
+        if (!t)
+                return log_oom();
+
+        memset(&t[plane->fb_count], 0, sizeof(*plane->fbs) * (num - plane->fb_count));
+        *slot = &t[plane->fb_count];
+        plane->fb_count = num;
+        plane->fbs = t;
+
+        return 0;
+}
+
+int sd_gfx_fb_new(sd_gfx_fb **out,
+                  sd_gfx_plane *plane,
+                  uint32_t format,
+                  uint32_t handles[4],
+                  uint32_t width,
+                  uint32_t height,
+                  uint32_t strides[4],
+                  uint32_t offsets[4],
+                  uint64_t size,
+                  uint64_t map_offset) {
+        sd_gfx_fb *fb, **slot = NULL;
+        int r;
+
+        assert(out);
+        assert(plane);
+        assert(handles);
+        assert(strides);
+        assert(offsets);
+
+        if (!plane->card)
+                return -EINVAL;
+
+        r = gfx_find_slot(plane, &slot);
+        if (r < 0)
+                return r;
+
+        fb = calloc(1, sizeof(*fb));
+        if (!fb)
+                return log_oom();
+
+        fb->ref = 1;
+        fb->plane = plane;
+
+        fb->format = format;
+        memcpy(fb->handles, handles, sizeof(fb->handles));
+        fb->width = width;
+        fb->height = height;
+        memcpy(fb->strides, strides, sizeof(fb->strides));
+        fb->size = size;
+
+        r = drmModeAddFB2(plane->card->fd,
+                          fb->width,
+                          fb->height,
+                          fb->format,
+                          fb->handles,
+                          fb->strides,
+                          offsets,
+                          &fb->fb, 0);
+        if (r < 0) {
+                log_error("card %s: AddFB2(%dx%d@%d): %m",
+                          plane->card->name, (int)fb->width, (int)fb->height, (int)fb->format);
+                r = -EINVAL;
+                goto err_free;
+        }
+
+        if (map_offset) {
+                fb->map = mmap(0, fb->size, PROT_WRITE, MAP_SHARED, plane->card->fd, map_offset);
+                if (fb->map == MAP_FAILED) {
+                        log_error("card %s: mmap(%llu): %m",
+                                  plane->card->name, (unsigned long long)map_offset);
+                        r = -EINVAL;
+                        goto err_fb;
+                }
+                memset(fb->map, 0, fb->size);
+        }
+
+        if (!plane->change) {
+                sd_gfx_fb_ref(fb);
+                *slot = fb;
+        }
+
+        *out = fb;
+        return 0;
+
+err_fb:
+        drmModeRmFB(plane->card->fd, fb->fb);
+err_free:
+        free(fb);
+        return r;
+}
+
+int sd_gfx_fb_new_rgb(sd_gfx_fb **out,
+                      sd_gfx_plane *plane,
+                      uint32_t format,
+                      uint32_t handle,
+                      uint32_t width,
+                      uint32_t height,
+                      uint32_t stride,
+                      uint32_t offset,
+                      uint64_t size,
+                      uint64_t map_offset) {
+        uint32_t handles[4], strides[4], offsets[4];
+
+        memset(handles, 0, sizeof(handles));
+        memset(strides, 0, sizeof(strides));
+        memset(offsets, 0, sizeof(offsets));
+        handles[0] = handle;
+        strides[0] = stride;
+        offsets[0] = offset;
+
+        return sd_gfx_fb_new(out, plane, format, handles, width, height, strides, offsets, size, map_offset);
+}
+
+static void gfx_fb_unlink_dumb_fn(sd_gfx_fb *fb, void *fn_data) {
+        struct drm_mode_destroy_dumb dreq;
+
+        memset(&dreq, 0, sizeof(dreq));
+        dreq.handle = fb->handles[0];
+        drmIoctl(fb->plane->card->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
+}
+
+int sd_gfx_fb_new_dumb(sd_gfx_fb **out,
+                       sd_gfx_plane *plane,
+                       uint32_t format,
+                       uint32_t bpp,
+                       uint32_t width,
+                       uint32_t height) {
+        sd_gfx_fb *fb;
+        struct drm_mode_create_dumb creq;
+        struct drm_mode_destroy_dumb dreq;
+        struct drm_mode_map_dumb mreq;
+        int r;
+
+        assert(out);
+        assert(plane);
+
+        if (!plane->card)
+                return -EINVAL;
+
+        memset(&creq, 0, sizeof(creq));
+        creq.width = width;
+        creq.height = height;
+        creq.bpp = bpp;
+
+        r = drmIoctl(plane->card->fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
+        if (r < 0) {
+                log_error("card %s: CreateDumb(%dx%d at 32): %m",
+                          plane->card->name, (int)creq.width, (int)creq.height);
+                return -EINVAL;
+        }
+
+        memset(&mreq, 0, sizeof(mreq));
+        mreq.handle = creq.handle;
+
+        r = drmIoctl(plane->card->fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
+        if (r < 0) {
+                log_error("card %s: MapDumb(%d): %m",
+                          plane->card->name, (int)creq.handle);
+                r = -EINVAL;
+                goto err_dumb;
+        }
+
+        r = sd_gfx_fb_new_rgb(&fb,
+                              plane,
+                              format,
+                              creq.handle,
+                              creq.width,
+                              creq.height,
+                              creq.pitch,
+                              0,
+                              creq.size,
+                              mreq.offset);
+        if (r < 0)
+                goto err_dumb;
+
+        fb->unlink_fn = gfx_fb_unlink_dumb_fn;
+        *out = fb;
+        return 0;
+
+err_dumb:
+        memset(&dreq, 0, sizeof(dreq));
+        dreq.handle = creq.handle;
+        drmIoctl(plane->card->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
+        return r;
+}
+
+void sd_gfx_fb_ref(sd_gfx_fb *fb) {
+        if (!fb || !fb->ref)
+                return;
+
+        ++fb->ref;
+}
+
+static void gfx_fb_unlink(sd_gfx_fb *fb) {
+        if (!fb->plane)
+                return;
+
+        if (fb->map)
+                munmap(fb->map, fb->size);
+        fb->map = NULL;
+
+        drmModeRmFB(fb->plane->card->fd, fb->fb);
+
+        if (fb->unlink_fn)
+                fb->unlink_fn(fb, fb->fn_data);
+
+        fb->plane = NULL;
+}
+
+static void gfx_fb_unpin(sd_gfx_fb *fb) {
+        if (fb->unpin_fn)
+                fb->unpin_fn(fb, fb->fn_data);
+}
+
+void sd_gfx_fb_unref(sd_gfx_fb *fb) {
+        if (!fb || !fb->ref || --fb->ref)
+                return;
+
+        gfx_fb_unlink(fb);
+        free(fb);
+}
+
+void sd_gfx_fb_set_data(sd_gfx_fb *fb, void *data) {
+        fb->data = data;
+}
+
+void *sd_gfx_fb_get_data(sd_gfx_fb *fb) {
+        return fb->data;
+}
+
+void sd_gfx_fb_set_fn_data(sd_gfx_fb *fb, void *fn_data) {
+        fb->fn_data = fn_data;
+}
+
+void *sd_gfx_fb_get_fn_data(sd_gfx_fb *fb) {
+        return fb->fn_data;
+}
+
+void sd_gfx_fb_set_fns(sd_gfx_fb *fb, sd_gfx_fb_unlink_fn unlink_fn, sd_gfx_fb_unpin_fn unpin_fn) {
+        fb->unlink_fn = unlink_fn;
+        fb->unpin_fn = unpin_fn;
+}
+
+sd_gfx_plane *sd_gfx_fb_get_plane(sd_gfx_fb *fb) {
+        return fb->plane;
+}
+
+uint32_t sd_gfx_fb_get_format(sd_gfx_fb *fb) {
+        return fb->format;
+}
+
+uint32_t sd_gfx_fb_get_width(sd_gfx_fb *fb) {
+        return fb->width;
+}
+
+uint32_t sd_gfx_fb_get_height(sd_gfx_fb *fb) {
+        return fb->height;
+}
+
+void sd_gfx_fb_fill(sd_gfx_fb *fb,
+                    uint32_t color,
+                    unsigned int pos_x,
+                    unsigned int pos_y,
+                    unsigned int width,
+                    unsigned int height) {
+        uint8_t *dst;
+        unsigned int i;
+        uint32_t *d;
+
+        if (!fb->plane || !fb->map)
+                return;
+        if (pos_x >= fb->width || pos_y >= fb->height)
+                return;
+        if (!width || !height)
+                return;
+
+        width = MIN(width, fb->width - pos_x);
+        height = MIN(height, fb->height - pos_y);
+
+        dst = fb->map + pos_y * fb->strides[0] + pos_x * 4;
+
+        while (height--) {
+                for (i = 0; i < width; ++i) {
+                        d = (void*)&dst[i * 4];
+                        *d = color;
+                }
+
+                dst += fb->strides[0];
+        }
+}
+
+void sd_gfx_fb_blend_bichrome(sd_gfx_fb *fb,
+                              uint32_t fg_color,
+                              uint32_t bg_color,
+                              unsigned int pos_x,
+                              unsigned int pos_y,
+                              const sd_gfx_buffer *source) {
+        unsigned int width, height;
+        const uint8_t *src, *s;
+        uint8_t *dst;
+        unsigned int i;
+        uint32_t *d;
+
+        if (!fb->plane || !fb->map)
+                return;
+        if (pos_x >= fb->width || pos_y >= fb->height)
+                return;
+        if (!source->width || !source->height)
+                return;
+
+        /* TODO: add rotation support */
+        /* TODO: add support for more formats */
+        if (source->format != SD_GFX_BUFFER_FORMAT_A1)
+                return;
+
+        width = MIN(source->width, fb->width - pos_x);
+        height = MIN(source->height, fb->height - pos_y);
+
+        src = source->data;
+        dst = fb->map + pos_y * fb->strides[0] + pos_x * 4;
+
+        while (height--) {
+                for (i = 0; i < width; ++i) {
+                        s = (void*)&src[i / 8];
+                        d = (void*)&dst[i * 4];
+                        if (*s & (1 << (7 - i % 8)))
+                                *d = fg_color;
+                        else
+                                *d = bg_color;
+                }
+
+                src += source->stride;
+                dst += fb->strides[0];
+        }
+}
+
+/*
+ * Planes
+ * Each plane allows to map framebuffers into the virtual screen of a pipe.
+ * Normally, each pipe has one primary plane which is the underlying
+ * framebuffer. But many new hardware provides additional planes so we can do
+ * blitting or even blending of multiple buffers in hardware.
+ */
+
+static int gfx_plane_new(sd_gfx_plane **out, sd_gfx_card *card) {
+        sd_gfx_plane *plane;
+
+        plane = calloc(1, sizeof(*plane));
+        if (!plane)
+                return log_oom();
+
+        plane->ref = 1;
+        plane->card = card;
+
+        *out = plane;
+        return 0;
+}
+
+static int gfx_plane_new_primary(sd_gfx_plane **out, sd_gfx_pipe *pipe) {
+        sd_gfx_plane *plane = NULL;
+        int r;
+
+        r = gfx_plane_new(&plane, pipe->card);
+        if (r < 0)
+                return r;
+
+        plane->primary = 1;
+        plane->pipe = pipe;
+
+        *out = plane;
+        return 0;
+}
+
+void sd_gfx_plane_ref(sd_gfx_plane *plane) {
+        if (!plane || !plane->ref)
+                return;
+
+        ++plane->ref;
+}
+
+static void gfx_plane_unlink(sd_gfx_plane *plane) {
+        unsigned int i;
+
+        if (!plane->card)
+                return;
+
+        if (plane->current)
+                gfx_fb_unpin(plane->current);
+        if (plane->swap_to)
+                gfx_fb_unpin(plane->swap_to);
+        if (plane->swap)
+                gfx_fb_unpin(plane->swap);
+
+        for (i = 0; i < plane->fb_count; ++i) {
+                if (plane->fbs[i]) {
+                        gfx_fb_unlink(plane->fbs[i]);
+                        sd_gfx_fb_unref(plane->fbs[i]);
+                        plane->fbs[i] = NULL;
+                }
+        }
+
+        plane->current = NULL;
+        plane->swap = NULL;
+        plane->swap_to = NULL;
+        plane->pipe = NULL;
+        plane->card = NULL;
+}
+
+void sd_gfx_plane_unref(sd_gfx_plane *plane) {
+        if (!plane || !plane->ref || --plane->ref)
+                return;
+
+        gfx_plane_unlink(plane);
+        free(plane->fbs);
+        free(plane);
+}
+
+static int gfx_plane_prepare_change(sd_gfx_plane *plane, unsigned int width, unsigned int height, sd_gfx_fb **fb) {
+        int r;
+
+        if (plane->current) {
+                if (plane->current->width == width && plane->current->height == height) {
+                        *fb = plane->current;
+                        return 0;
+                }
+        }
+
+        plane->change = 1;
+        if (plane->prepare_fn)
+                r = plane->prepare_fn(plane, plane->fn_data, width, height, fb);
+        else
+                r = sd_gfx_fb_new_dumb(fb, plane, DRM_FORMAT_XRGB8888, 32, width, height);
+        plane->change = 0;
+
+        return r;
+}
+
+static void gfx_plane_cancel_change(sd_gfx_plane *plane, sd_gfx_fb *fb) {
+        if (fb != plane->current) {
+                if (plane->cancel_fn)
+                        plane->cancel_fn(plane, plane->fn_data, fb);
+                gfx_fb_unlink(fb);
+                sd_gfx_fb_unref(fb);
+        }
+}
+
+static void gfx_plane_finish_change(sd_gfx_plane *plane, sd_gfx_fb *fb) {
+        unsigned int i;
+
+        if (fb == plane->current)
+                return;
+
+        if (plane->current)
+                gfx_fb_unpin(plane->current);
+        if (plane->swap_to)
+                gfx_fb_unpin(plane->swap_to);
+        if (plane->swap)
+                gfx_fb_unpin(plane->swap);
+        plane->current = NULL;
+        plane->swap = NULL;
+        plane->swap_to = NULL;
+
+        for (i = 0; i < plane->fb_count; ++i) {
+                if (plane->fbs[i]) {
+                        gfx_fb_unlink(plane->fbs[i]);
+                        sd_gfx_fb_unref(plane->fbs[i]);
+                        plane->fbs[i] = NULL;
+                }
+        }
+
+        plane->fbs[0] = fb;
+        plane->current = fb;
+        if (plane->finish_fn)
+                plane->finish_fn(plane, plane->fn_data, fb);
+}
+
+void sd_gfx_plane_set_data(sd_gfx_plane *plane, void *data) {
+        plane->data = data;
+}
+
+void *sd_gfx_plane_get_data(sd_gfx_plane *plane) {
+        return plane->data;
+}
+
+void sd_gfx_plane_set_fn_data(sd_gfx_plane *plane, void *fn_data) {
+        plane->fn_data = fn_data;
+}
+
+void *sd_gfx_plane_get_fn_data(sd_gfx_plane *plane) {
+        return plane->fn_data;
+}
+
+void sd_gfx_plane_set_fns(sd_gfx_plane *plane,
+                          sd_gfx_plane_prepare_fn prepare_fn,
+                          sd_gfx_plane_cancel_fn cancel_fn,
+                          sd_gfx_plane_finish_fn finish_fn) {
+        plane->prepare_fn = prepare_fn;
+        plane->cancel_fn = cancel_fn;
+        plane->finish_fn = finish_fn;
+}
+
+unsigned int sd_gfx_plane_get_id(sd_gfx_plane *plane) {
+        return plane->plane_id;
+}
+
+sd_gfx_card *sd_gfx_plane_get_card(sd_gfx_plane *plane) {
+        return plane->card;
+}
+
+sd_gfx_pipe *sd_gfx_plane_get_pipe(sd_gfx_plane *plane) {
+        return plane->pipe;
+}
+
+bool sd_gfx_plane_is_enabled(sd_gfx_plane *plane) {
+        return plane->primary ? 1 : plane->enabled;
+}
+
+void sd_gfx_plane_enable(sd_gfx_plane *plane) {
+        plane->enabled = 1;
+}
+
+void sd_gfx_plane_disable(sd_gfx_plane *plane) {
+        if (!plane->primary)
+                plane->enabled = 0;
+}
+
+bool sd_gfx_plane_supports_format(sd_gfx_plane *plane, uint32_t format) {
+        if (plane->primary) {
+                if (format == DRM_FORMAT_XRGB8888)
+                        return true;
+                else
+                        return false;
+        }
+
+        return false;
+}
+
+bool sd_gfx_plane_supports_pipe(sd_gfx_plane *plane, sd_gfx_pipe *pipe) {
+        if (plane->pipe == pipe)
+                return true;
+        if (plane->primary)
+                return false;
+
+        return false;
+}
+
+sd_gfx_fb *sd_gfx_plane_get_front(sd_gfx_plane *plane) {
+        return plane->current;
+}
+
+sd_gfx_fb *sd_gfx_plane_get_back(sd_gfx_plane *plane) {
+        sd_gfx_fb *fb;
+        unsigned int i;
+
+        for (i = 0; i < plane->fb_count; ++i) {
+                fb = plane->fbs[i];
+                if (fb && fb != plane->current && fb != plane->swap_to && fb != plane->swap)
+                        return fb;
+        }
+
+        return NULL;
+}
+
+void sd_gfx_plane_swap_to(sd_gfx_plane *plane, sd_gfx_fb *fb) {
+        if (!plane->card)
+                return;
+        if (fb && fb->plane != plane)
+                return;
+
+        if (plane->swap_to)
+                gfx_fb_unpin(plane->swap_to);
+
+        plane->swap_to = fb;
+}
+
+static void gfx_plane_swap(sd_gfx_plane *plane) {
+        gfx_fb_unpin(plane->current);
+        plane->current = plane->swap;
+        plane->swap = NULL;
+}
+
+/*
+ * Modes
+ * DRM modes describe the resolution, refresh-rate and other flags of a
+ * configured pipe. They are immutable and dead. They only serve to forward
+ * mode-information to the caller.
+ */
+
+static int gfx_mode_new(sd_gfx_mode **out, drmModeModeInfo *info) {
+        sd_gfx_mode *mode;
+
+        mode = calloc(1, sizeof(*mode));
+        if (!mode)
+                return log_oom();
+
+        mode->ref = 1;
+        mode->info = *info;
+
+        *out = mode;
+        return 0;
+}
+
+void sd_gfx_mode_ref(sd_gfx_mode *mode) {
+        if (!mode || !mode->ref)
+                return;
+
+        ++mode->ref;
+}
+
+void sd_gfx_mode_unref(sd_gfx_mode *mode) {
+        if (!mode || !mode->ref || --mode->ref)
+                return;
+
+        free(mode);
+}
+
+unsigned int sd_gfx_mode_get_width(sd_gfx_mode *mode) {
+        return mode->info.hdisplay;
+}
+
+unsigned int sd_gfx_mode_get_height(sd_gfx_mode *mode) {
+        return mode->info.vdisplay;
+}
+
+const char *sd_gfx_mode_get_name(sd_gfx_mode *mode) {
+        return mode->info.name;
+}
+
+/*
+ * Connectors
+ * Each physical connector on your card is represented as a gfx_connector. A
+ * card may not be able to program all connectors at the same time (eg., if
+ * not enough pipes are available). Thus, we need to find a suitable
+ * configuration for all requested connectors so we can program them the way
+ * the user wants.
+ * Connectors are normally the only entity that is known to users, and it's the
+ * only entity that should be configurable by users. The whole underlying
+ * pipeline should be programmed internally without ever telling users about
+ * it.
+ */
+
+static const char *gfx_connector_types[] = {
+        [DRM_MODE_CONNECTOR_Unknown]            = "Unknown",
+        [DRM_MODE_CONNECTOR_VGA]                = "VGA",
+        [DRM_MODE_CONNECTOR_DVII]               = "DVI-I",
+        [DRM_MODE_CONNECTOR_DVID]               = "DVI-D",
+        [DRM_MODE_CONNECTOR_DVIA]               = "DVI-A",
+        [DRM_MODE_CONNECTOR_Composite]          = "Composite",
+        [DRM_MODE_CONNECTOR_SVIDEO]             = "SVIDEO",
+        [DRM_MODE_CONNECTOR_LVDS]               = "LVDS",
+        [DRM_MODE_CONNECTOR_Component]          = "Component",
+        [DRM_MODE_CONNECTOR_9PinDIN]            = "DIN",
+        [DRM_MODE_CONNECTOR_DisplayPort]        = "DP",
+        [DRM_MODE_CONNECTOR_HDMIA]              = "HDMI-A",
+        [DRM_MODE_CONNECTOR_HDMIB]              = "HDMI-B",
+        [DRM_MODE_CONNECTOR_TV]                 = "TV",
+        [DRM_MODE_CONNECTOR_eDP]                = "eDP",
+};
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(drmModeConnector*, drmModeFreeConnector);
+
+static void gfx_connector_refresh(gfx_connector *connector, drmModeConnector *con) {
+        _cleanup_(drmModeFreeConnectorp) drmModeConnector *con2 = NULL;
+        drmModePropertyRes *prop;
+        unsigned int i;
+        int j;
+        gfx_encoder *encoder;
+
+        connector->encoder = NULL;
+
+        if (!con) {
+                con2 = drmModeGetConnector(connector->card->fd, connector->connector_id);
+                if (!con2) {
+                        log_error("card %s: GetConnector(%d): %m",
+                                  connector->card->name, (int)connector->connector_id);
+                        return;
+                }
+                con = con2;
+        }
+
+        if (con->encoder_id > 0) {
+                for (i = 0; i < connector->card->encoder_ids; ++i) {
+                        encoder = connector->card->encoders[i];
+                        if (encoder && encoder->encoder_id == con->encoder_id) {
+                                connector->encoder = encoder;
+                                break;
+                        }
+                }
+        }
+
+        for (j = 0; j < con->count_props; ++j) {
+                prop = drmModeGetProperty(connector->card->fd, con->props[j]);
+                if (!prop)
+                        continue;
+
+                if (!strcmp(prop->name, "DPMS")) {
+                        connector->dpms_prop = prop->prop_id;
+                        connector->dpms = con->prop_values[j];
+                }
+
+                drmModeFreeProperty(prop);
+        }
+
+        if (connector->card->real_awake && connector->encoder && connector->encoder->pipe)
+                if (!connector->encoder->pipe->kern_connector)
+                        connector->encoder->pipe->kern_connector = connector;
+}
+
+static int gfx_connector_new(gfx_connector **out, sd_gfx_card *card, drmModeConnector *con) {
+        gfx_connector *connector;
+        gfx_encoder *encoder;
+        sd_gfx_mode *mode;
+        unsigned int j;
+        int i, r;
+
+        connector = calloc(1, sizeof(*connector));
+        if (!connector)
+                return log_oom();
+
+        connector->card = card;
+        connector->id = card->connector_ids;
+        connector->connector_id = con->connector_id;
+        connector->connected = con->connection == DRM_MODE_CONNECTED;
+        connector->dpms = SD_GFX_DPMS_UNKNOWN;
+
+        if (con->connector_type < ELEMENTSOF(gfx_connector_types))
+                connector->type = gfx_connector_types[con->connector_type];
+        if (!connector->type)
+                connector->type = gfx_connector_types[DRM_MODE_CONNECTOR_Unknown];
+
+        connector->type_id = con->connector_type_id;
+
+        connector->modes = calloc(con->count_modes, sizeof(*connector->modes));
+        if (!connector->modes) {
+                free(connector);
+                return log_oom();
+        }
+
+        for (i = 0; i < con->count_modes; ++i) {
+                r = gfx_mode_new(&mode, &con->modes[i]);
+                if (r >= 0) {
+                        connector->modes[connector->mode_count++] = mode;
+                        if (!connector->preferred_mode && (mode->info.type & DRM_MODE_TYPE_PREFERRED))
+                                connector->preferred_mode = mode;
+                        if (!connector->default_mode && (mode->info.type & DRM_MODE_TYPE_DEFAULT))
+                                connector->default_mode = mode;
+                }
+        }
+
+        if (connector->mode_count > 0) {
+                if (!connector->preferred_mode)
+                        connector->preferred_mode = connector->modes[0];
+                if (!connector->default_mode)
+                        connector->default_mode = connector->preferred_mode;
+        }
+
+        connector->possible_pipes = 0xffffffff;
+        for (j = 0; j < card->encoder_ids; ++j) {
+                encoder = card->encoders[j];
+                if (!encoder)
+                        continue;
+
+                for (i = 0; i < con->count_encoders; ++i) {
+                        if (encoder->encoder_id == con->encoders[i]) {
+                                connector->possible_encoders |= 1 << j;
+                                connector->possible_pipes &= encoder->possible_pipes;
+                        }
+                }
+        }
+
+        gfx_connector_refresh(connector, con);
+
+        *out = connector;
+        return 0;
+}
+
+static void gfx_connector_free(gfx_connector *connector) {
+        unsigned int i;
+
+        if (!connector)
+                return;
+
+        for (i = 0; i < connector->mode_count; ++i)
+                sd_gfx_mode_unref(connector->modes[i]);
+
+        free(connector->modes);
+        free(connector);
+}
+
+static bool gfx_connector_match(gfx_connector *connector, const char *name) {
+        size_t len;
+        unsigned int id;
+
+        len = strlen(connector->type);
+        if (strncasecmp(name, connector->type, len))
+                return false;
+        if (!name[len])
+                return connector->type_id == 1;
+        if (name[len] != '-')
+                return false;
+        if (safe_atou(&name[len + 1], &id) < 0)
+                return false;
+
+        return connector->type_id == id;
+}
+
+static void gfx_connector_set_dpms(gfx_connector *connector, unsigned int dpms) {
+        int r;
+
+        if (!connector->dpms_prop)
+                return;
+
+        r = drmModeConnectorSetProperty(connector->card->fd, connector->connector_id, connector->dpms_prop, dpms);
+        if (r >= 0)
+                connector->dpms = dpms;
+}
+
+/*
+ * Encoders
+ * DRM encoders describe the pipeline from a crtc to a connector. They are only
+ * used to tell user-space about pipeline-constraints. We cannot explicitly
+ * configure any encoder.
+ */
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(drmModeEncoder*, drmModeFreeEncoder);
+
+static void gfx_encoder_refresh(gfx_encoder *encoder, drmModeEncoder *enc) {
+        _cleanup_(drmModeFreeEncoderp) drmModeEncoder *enc2 = NULL;
+        unsigned int i;
+        sd_gfx_pipe *pipe;
+
+        encoder->pipe = NULL;
+
+        if (!enc) {
+                enc2 = drmModeGetEncoder(encoder->card->fd, encoder->encoder_id);
+                if (!enc2) {
+                        log_error("card %s: GetEncoder(%d): %m",
+                                  encoder->card->name, (int)encoder->encoder_id);
+                        return;
+                }
+                enc = enc2;
+        }
+
+        if (enc->crtc_id > 0) {
+                for (i = 0; i < encoder->card->pipe_ids; ++i) {
+                        pipe = encoder->card->pipes[i];
+                        if (!pipe)
+                                continue;
+
+                        if (pipe->crtc_id == enc->crtc_id) {
+                                encoder->pipe = pipe;
+                                break;
+                        }
+                }
+        }
+}
+
+static int gfx_encoder_new(gfx_encoder **out, sd_gfx_card *card, drmModeEncoder *enc) {
+        gfx_encoder *encoder;
+
+        encoder = calloc(1, sizeof(*encoder));
+        if (!encoder)
+                return log_oom();
+
+        encoder->card = card;
+        encoder->id = card->encoder_ids;
+        encoder->encoder_id = enc->encoder_id;
+        encoder->possible_pipes = enc->possible_crtcs;
+        encoder->possible_clones = enc->possible_clones;
+
+        gfx_encoder_refresh(encoder, enc);
+
+        *out = encoder;
+        return 0;
+}
+
+static void gfx_encoder_free(gfx_encoder *encoder) {
+        if (!encoder)
+                return;
+
+        free(encoder);
+}
+
+/*
+ * Pipes
+ * A pipe (or CRTC) is the central mode-setting object. It controls which
+ * planes, which framebuffers, which encoders and connectors to use. A pipe is
+ * usually a piece of hardware and has several constraints we need to respect
+ * during modesetting.
+ *
+ * On each pipe we can only set a single framebuffer. Thus, if multiple
+ * connectors are bound to a single pipe, they will mirror each other. The
+ * kernel automatically figures out which encoders to use to drive a given set
+ * of connectors on a single pipe.
+ *
+ * Each pipe has a primary plane. Newer hardware allows to assign additional
+ * planes to a pipe, thus allowing 2D hardware compositing. The kernel usually
+ * reserves one plane for hardware-cursors.
+ *
+ * Modesetting should be atomic. That means, all state should be applied at the
+ * same time and preferably during a vertical blank (to avoid tearing).
+ * Depending on the kernel API and driver support, this might not be supported.
+ * However, our API emulates atomic modesetting for all other devices and simply
+ * applies all state sequencially.
+ * This means, if you change any state on the connectors or planes, it will not
+ * get applied until you commit it. The pipe decides whether a full modeset is
+ * performed or whether we can schedule a cheap page-flip instead.
+ */
+
+static void gfx_pipe_call(sd_gfx_pipe *pipe, unsigned int type) {
+        struct sd_gfx_card_event event;
+
+        memset(&event, 0, sizeof(event));
+        event.type = type;
+        event.pipe = pipe;
+
+        if (pipe->card->event_fn)
+                pipe->card->event_fn(pipe->card, pipe->card->fn_data, &event);
+}
+
+static void gfx_pipe_call_wake_up(sd_gfx_pipe *pipe, sd_gfx_mode *old_mode) {
+        struct sd_gfx_card_event event;
+
+        memset(&event, 0, sizeof(event));
+        event.type = SD_GFX_CARD_PIPE_WAKE_UP;
+        event.pipe = pipe;
+        event.old_mode = old_mode;
+
+        if (pipe->card->event_fn)
+                pipe->card->event_fn(pipe->card, pipe->card->fn_data, &event);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(drmModeCrtc*, drmModeFreeCrtc);
+
+static void gfx_pipe_refresh(sd_gfx_pipe *pipe, drmModeCrtc *crtc) {
+        _cleanup_(drmModeFreeCrtcp) drmModeCrtc *crtc2 = NULL;
+
+        sd_gfx_mode_unref(pipe->kern_mode);
+        pipe->kern_mode = NULL;
+
+        if (!crtc) {
+                crtc2 = drmModeGetCrtc(pipe->card->fd, pipe->crtc_id);
+                if (!crtc2) {
+                        log_error("card %s: GetCrtc(%d): %m",
+                                  pipe->card->name, (int)pipe->crtc_id);
+                        return;
+                }
+                crtc = crtc2;
+        }
+
+        if (crtc->mode_valid) {
+                /* keeps NULL on failure */
+                gfx_mode_new(&pipe->kern_mode, &crtc->mode);
+        }
+
+        if (pipe->card->real_awake && !pipe->kern_crtc && crtc->buffer_id) {
+                if (crtc2) {
+                        pipe->kern_crtc = crtc2;
+                        crtc2 = NULL;
+                } else {
+                        pipe->kern_crtc = drmModeGetCrtc(pipe->card->fd, pipe->crtc_id);
+                        if (pipe->kern_crtc && !pipe->kern_crtc->buffer_id) {
+                                drmModeFreeCrtc(pipe->kern_crtc);
+                                pipe->kern_crtc = NULL;
+                        }
+                }
+        }
+}
+
+static int gfx_pipe_new(sd_gfx_pipe **out, sd_gfx_card *card, drmModeCrtc *crtc, unsigned int connector_count) {
+        sd_gfx_pipe *pipe;
+        int r;
+
+        pipe = calloc(1, sizeof(*pipe));
+        if (!pipe)
+                return log_oom();
+
+        pipe->ref = 1;
+        pipe->card = card;
+        pipe->id = card->pipe_ids;
+        pipe->crtc_id = crtc->crtc_id;
+        pipe->page_flip_cnt = 1;
+
+        pipe->connectors = calloc(connector_count, sizeof(*pipe->connectors));
+        if (!pipe->connectors) {
+                r = log_oom();
+                goto err_pipe;
+        }
+
+        pipe->want_connectors = calloc(connector_count, sizeof(*pipe->want_connectors));
+        if (!pipe->want_connectors) {
+                r = log_oom();
+                goto err_connectors;
+        }
+
+        pipe->connector_ids = calloc(connector_count, sizeof(*pipe->connector_ids));
+        if (!pipe->connector_ids) {
+                r = log_oom();
+                goto err_want_connectors;
+        }
+
+        pipe->want_connector_ids = calloc(connector_count, sizeof(*pipe->want_connector_ids));
+        if (!pipe->want_connector_ids) {
+                r = log_oom();
+                goto err_connector_ids;
+        }
+
+        r = gfx_plane_new_primary(&pipe->primary, pipe);
+        if (r < 0)
+                goto err_want_connector_ids;
+
+        gfx_pipe_refresh(pipe, crtc);
+
+        *out = pipe;
+        return 0;
+
+err_want_connector_ids:
+        free(pipe->want_connector_ids);
+err_connector_ids:
+        free(pipe->connector_ids);
+err_want_connectors:
+        free(pipe->want_connectors);
+err_connectors:
+        free(pipe->connectors);
+err_pipe:
+        free(pipe);
+        return r;
+}
+
+void sd_gfx_pipe_ref(sd_gfx_pipe *pipe) {
+        if (!pipe || !pipe->ref)
+                return;
+
+        ++pipe->ref;
+}
+
+static void gfx_pipe_unlink(sd_gfx_pipe *pipe) {
+        if (!pipe->card)
+                return;
+
+        if (pipe->card->public) {
+                if (pipe->assigned && pipe->mode)
+                        gfx_pipe_call(pipe, SD_GFX_CARD_PIPE_SLEEP);
+
+                gfx_pipe_call(pipe, SD_GFX_CARD_PIPE_DESTROY);
+        }
+
+        pipe->assigned = 0;
+
+        drmModeFreeCrtc(pipe->kern_crtc);
+        pipe->kern_crtc = NULL;
+
+        pipe->connector_count = 0;
+
+        sd_gfx_mode_unref(pipe->mode);
+        pipe->mode = NULL;
+        sd_gfx_mode_unref(pipe->want_mode);
+        pipe->want_mode = NULL;
+        sd_gfx_mode_unref(pipe->kern_mode);
+        pipe->kern_mode = NULL;
+
+        gfx_plane_unlink(pipe->primary);
+        sd_gfx_plane_unref(pipe->primary);
+        pipe->primary = NULL;
+
+        pipe->card = NULL;
+}
+
+void sd_gfx_pipe_unref(sd_gfx_pipe *pipe) {
+        if (!pipe || !pipe->ref || --pipe->ref)
+                return;
+
+        gfx_pipe_unlink(pipe);
+        free(pipe->want_connector_ids);
+        free(pipe->connector_ids);
+        free(pipe->want_connectors);
+        free(pipe->connectors);
+        free(pipe);
+}
+
+void sd_gfx_pipe_set_data(sd_gfx_pipe *pipe, void *data) {
+        pipe->data = data;
+}
+
+void *sd_gfx_pipe_get_data(sd_gfx_pipe *pipe) {
+        return pipe->data;
+}
+
+void sd_gfx_pipe_set_fn_data(sd_gfx_pipe *pipe, void *fn_data) {
+        pipe->fn_data = fn_data;
+}
+
+void *sd_gfx_pipe_get_fn_data(sd_gfx_pipe *pipe) {
+        return pipe->fn_data;
+}
+
+unsigned int sd_gfx_pipe_get_id(sd_gfx_pipe *pipe) {
+        return pipe->id;
+}
+
+unsigned int sd_gfx_pipe_get_dpms(sd_gfx_pipe *pipe) {
+        unsigned int i, res;
+
+        if (!pipe->card || !pipe->card->real_awake)
+                return SD_GFX_DPMS_UNKNOWN;
+
+        res = SD_GFX_DPMS_ON;
+        for (i = 0; i < pipe->connector_count; ++i)
+                if (pipe->connectors[i]->dpms > res)
+                        res = pipe->connectors[i]->dpms;
+
+        return res;
+}
+
+void sd_gfx_pipe_set_dpms(sd_gfx_pipe *pipe, unsigned int dpms) {
+        unsigned int i;
+
+        if (!pipe->card || dpms >= SD_GFX_DPMS_UNKNOWN)
+                return;
+
+        for (i = 0; i < pipe->connector_count; ++i)
+                gfx_connector_set_dpms(pipe->connectors[i], dpms);
+}
+
+sd_gfx_mode *sd_gfx_pipe_get_mode(sd_gfx_pipe *pipe) {
+        return pipe->mode;
+}
+
+sd_gfx_plane *sd_gfx_pipe_get_cursor(sd_gfx_pipe *pipe) {
+        return NULL;
+}
+
+sd_gfx_plane *sd_gfx_pipe_get_primary_plane(sd_gfx_pipe *pipe) {
+        return pipe->primary;
+}
+
+int sd_gfx_pipe_attach_plane(sd_gfx_pipe *pipe, sd_gfx_plane *plane) {
+        if (!pipe->card)
+                return -ENODEV;
+
+        return -EINVAL;
+}
+
+void sd_gfx_pipe_detach_plane(sd_gfx_pipe *pipe, sd_gfx_plane *plane) {
+}
+
+static void gfx_pipe_commit_kern(sd_gfx_pipe *pipe) {
+        int r;
+
+        if (!pipe->kern_crtc || !pipe->kern_connector || pipe->assigned)
+                return;
+
+        log_debug("card %s: restore pipe %u to %dx%d with connector %u",
+                  pipe->card->name, pipe->id,
+                  pipe->kern_crtc->mode.hdisplay, pipe->kern_crtc->mode.vdisplay,
+                  pipe->kern_connector->id);
+
+        r = drmModeSetCrtc(pipe->card->fd,
+                           pipe->kern_crtc->crtc_id,
+                           pipe->kern_crtc->buffer_id,
+                           pipe->kern_crtc->x,
+                           pipe->kern_crtc->y,
+                           &pipe->kern_connector->connector_id, 1,
+                           &pipe->kern_crtc->mode);
+        if (r < 0)
+                log_error("card %s: RestoreCrtc(%d): %m",
+                          pipe->card->name, (int)pipe->crtc_id);
+}
+
+static void gfx_pipe_print_wanted(sd_gfx_pipe *pipe, sd_gfx_mode *mode) {
+        unsigned int i;
+
+        if (!mode) {
+                log_debug("card %s: set pipe %u to <none>",
+                          pipe->card->name, pipe->id);
+                return;
+        }
+
+        log_debug("card %s: set pipe %u to %dx%d with:",
+                  pipe->card->name, pipe->id, (int)mode->info.hdisplay, (int)mode->info.vdisplay);
+
+        for (i = 0; i < pipe->want_connector_count; ++i)
+                log_debug("  connector: %u", pipe->want_connectors[i]->id);
+}
+
+static int gfx_pipe_commit_wanted(sd_gfx_pipe *pipe, sd_gfx_mode *mode) {
+        gfx_connector *connector, **tcons;
+        sd_gfx_mode *old_mode;
+        sd_gfx_fb *fb;
+        unsigned int i;
+        uint32_t *tids;
+        int r;
+
+        gfx_pipe_print_wanted(pipe, mode);
+
+        if (pipe->assigned)
+                return -EINVAL;
+
+        if (!mode) {
+                sd_gfx_mode_unref(pipe->mode);
+                pipe->mode = NULL;
+                sd_gfx_mode_unref(pipe->want_mode);
+                pipe->want_mode = NULL;
+                pipe->connector_count = 0;
+
+                r = drmModeSetCrtc(pipe->card->fd, pipe->crtc_id, 0, 0, 0, NULL, 0, NULL);
+                if (r < 0) {
+                        if (errno == EACCES) {
+                                gfx_card_async_sleep(pipe->card);
+                                return -EACCES;
+                        }
+
+                        log_error("card %s: SetCrtc(%d): %m",
+                                  pipe->card->name, (int)pipe->crtc_id);
+                        return -EINVAL;
+                }
+
+                pipe->assigned = 1;
+                pipe->page_flip = ++pipe->page_flip_cnt;
+
+                return 0;
+        }
+
+        if (!pipe->want_connector_count)
+                return -EINVAL;
+
+        for (i = 0; i < pipe->want_connector_count; ++i) {
+                connector = pipe->want_connectors[i];
+                if (connector->assigned)
+                        return -EINVAL;
+
+                pipe->want_connector_ids[i] = connector->connector_id;
+        }
+
+        sd_gfx_mode_ref(mode);
+        sd_gfx_mode_unref(pipe->want_mode);
+        pipe->want_mode = mode;
+
+        r = gfx_plane_prepare_change(pipe->primary,
+                                     pipe->want_mode->info.hdisplay,
+                                     pipe->want_mode->info.vdisplay,
+                                     &fb);
+        if (r < 0)
+                return r;
+
+        r = drmModeSetCrtc(pipe->card->fd,
+                           pipe->crtc_id,
+                           fb->fb,
+                           0, 0,
+                           pipe->want_connector_ids,
+                           pipe->want_connector_count,
+                           &pipe->want_mode->info);
+        if (r < 0) {
+                if (errno == EACCES) {
+                        gfx_card_async_sleep(pipe->card);
+                        r = -EACCES;
+                } else {
+                        log_error("card %s: SetCrtc(%d): %m",
+                                  pipe->card->name, (int)pipe->crtc_id);
+                        r = -EINVAL;
+                }
+
+                gfx_plane_cancel_change(pipe->primary, fb);
+                return r;
+        }
+
+        gfx_plane_finish_change(pipe->primary, fb);
+
+        for (i = 0; i < pipe->want_connector_count; ++i) {
+                connector = pipe->want_connectors[i];
+                connector->pipe = pipe;
+                connector->assigned = 1;
+        }
+
+        tcons = pipe->want_connectors;
+        pipe->want_connectors = pipe->connectors;
+        pipe->connectors = tcons;
+
+        tids = pipe->want_connector_ids;
+        pipe->want_connector_ids = pipe->connector_ids;
+        pipe->connector_ids = tids;
+
+        pipe->connector_count = pipe->want_connector_count;
+        pipe->want_connector_count = 0;
+
+        old_mode = pipe->mode;
+        pipe->mode = pipe->want_mode;
+        pipe->want_mode = NULL;
+
+        pipe->assigned = 1;
+        pipe->page_flip = ++pipe->page_flip_cnt;
+
+        gfx_pipe_call_wake_up(pipe, old_mode);
+        sd_gfx_mode_unref(old_mode);
+
+        return 0;
+}
+
+int sd_gfx_pipe_commit(sd_gfx_pipe *pipe) {
+        struct drm_mode_crtc_page_flip flip;
+        sd_gfx_fb *fb;
+        int r;
+
+        if (!pipe->card || !pipe->mode || !pipe->assigned)
+                return -ENODEV;
+        if (!pipe->card->real_awake)
+                return -EACCES;
+        if (pipe->page_flip != pipe->page_flip_cnt)
+                return -EALREADY;
+
+        if (pipe->primary->swap)
+                return -EALREADY;
+
+        fb = pipe->primary->swap_to;
+        if (!fb)
+                return 0;
+
+        memset(&flip, 0, sizeof(flip));
+        flip.fb_id = fb->fb;
+        flip.crtc_id = pipe->crtc_id;
+        flip.user_data = gfx_encode_arg(pipe->page_flip_cnt + 1, pipe->id);
+        flip.flags = DRM_MODE_PAGE_FLIP_EVENT;
+
+        r = drmIoctl(pipe->card->fd, DRM_IOCTL_MODE_PAGE_FLIP, &flip);
+        if (r < 0) {
+                if (errno == EACCES) {
+                        gfx_card_async_sleep(pipe->card);
+                        r = -EACCES;
+                } else {
+                        log_error("card %s: PageFlip(): %m", pipe->card->name);
+                        r = -EINVAL;
+                }
+
+                return r;
+        }
+
+        ++pipe->page_flip_cnt;
+        pipe->primary->swap = fb;
+        pipe->primary->swap_to = NULL;
+
+        return 0;
+}
+
+static void gfx_pipe_page_flip(sd_gfx_pipe *pipe, unsigned int frame, unsigned int sec, unsigned int usec, uint64_t page_flip) {
+        if (page_flip < pipe->page_flip_cnt)
+                return;
+
+        pipe->page_flip = pipe->page_flip_cnt;
+
+        gfx_plane_swap(pipe->primary);
+
+        if (!pipe->card || !pipe->mode || !pipe->assigned)
+                return;
+
+        gfx_pipe_call(pipe, SD_GFX_CARD_PIPE_SWAP);
+}
+
+/*
+ * Modesetting
+ * The gfx_config helpers configure modesetting pipelines. They try to group
+ * connectors if clone-mode is requested, find suitable pipes and modes and
+ * perform modesetting.
+ *
+ * We allow to set some rules to control how modesetting is performed. But
+ * these are optional. The default rules should find suitable configurations
+ * for all systems.
+ */
+
+/* helper to collect connectors of a given pipe-config */
+static unsigned int gfx_config_collect_pipe(sd_gfx_card *card, gfx_config_pipe *conf_pipe) {
+        gfx_connector *connector;
+        unsigned int num, i;
+
+        num = 0;
+        for (i = 0; i < card->connector_ids; ++i) {
+                connector = card->connectors[i];
+                if (connector && connector->want_config == conf_pipe && connector->connected && !connector->assigned)
+                        card->tcons[num++] = connector;
+        }
+
+        return num;
+}
+
+/* helper to find a common pipe of a given pipe-config */
+static sd_gfx_pipe *gfx_config_find_pipe(sd_gfx_card *card, gfx_config_pipe *conf_pipe, unsigned int num) {
+        sd_gfx_pipe *pipe;
+        unsigned int i;
+        uint32_t pipes;
+
+        pipes = 0xffffffff;
+        for (i = 0; i < num; ++i)
+                pipes &= card->tcons[i]->possible_pipes;
+
+        if (!pipes) {
+                log_warning("card %s: no common pipe found for pipe-config %u",
+                            card->name, conf_pipe->config_id);
+                return NULL;
+        }
+
+        pipe = NULL;
+        for (i = 0; i < card->pipe_ids; ++i) {
+                pipe = card->pipes[i];
+                if (pipe && (pipes & (1 << i)) && !pipe->assigned)
+                        break;
+        }
+
+        if (i >= card->pipe_ids) {
+                log_warning("card %s: no unassigned pipe found for pipe-config %u",
+                            card->name, conf_pipe->config_id);
+                return NULL;
+        }
+
+        return pipe;
+}
+
+/* helper to find a common mode of a given pipe-config */
+static sd_gfx_mode *gfx_config_find_mode(sd_gfx_card *card, gfx_config_pipe *conf_pipe, unsigned int num) {
+        gfx_connector *connector;
+        sd_gfx_mode *mode, *m;
+        unsigned int i, j;
+        bool auto_mode = false;
+
+        if (!strcmp(conf_pipe->mode, "auto"))
+                auto_mode = true;
+
+        mode = NULL;
+        for (i = 0; i < num; ++i) {
+                connector = card->tcons[i];
+
+                if (auto_mode) {
+                        m = connector->preferred_mode;
+                        if (!mode)
+                                mode = m;
+                        else if (m->info.hdisplay > mode->info.hdisplay)
+                                mode = m;
+                        else if (m->info.hdisplay == mode->info.hdisplay && m->info.vdisplay > mode->info.vdisplay)
+                                mode = m;
+                } else {
+                        for (j = 0; j < connector->mode_count; ++j) {
+                                m = connector->modes[j];
+                                if (!strcmp(m->info.name, conf_pipe->mode))
+                                        return m;
+                        }
+                }
+        }
+
+        if (mode)
+                return mode;
+
+        if (auto_mode)
+                log_warning("card %s: no common mode found for pipe-config %u",
+                            card->name, conf_pipe->config_id);
+        else
+                log_warning("card %s: mode '%s' not found for pipe-config %u",
+                            card->name, conf_pipe->mode, conf_pipe->config_id);
+
+        return NULL;
+}
+
+static int gfx_config_apply_pipe(sd_gfx_card *card, gfx_config_pipe *conf_pipe) {
+        gfx_connector **tcons;
+        sd_gfx_pipe *pipe;
+        sd_gfx_mode *mode;
+        unsigned int num;
+        int r;
+
+        /* First step during setup. Takes a single pipe-configuration and
+         * tries to apply it. If it fails, we simply leave the entities
+         * unassigned so later steps pick them up.
+         * This step is a no-op if no pipe-configurations were supplied by
+         * the caller/user. */
+
+        if (!conf_pipe->mode || conf_pipe->disable)
+                return 0;
+
+        num = gfx_config_collect_pipe(card, conf_pipe);
+        if (!num)
+                return 0;
+
+        pipe = gfx_config_find_pipe(card, conf_pipe, num);
+        if (!pipe)
+                return 0;
+
+        mode = gfx_config_find_mode(card, conf_pipe, num);
+        if (!mode)
+                return 0;
+
+        pipe->want_rotation = conf_pipe->rotate;
+        pipe->want_connector_count = num;
+
+        tcons = pipe->want_connectors;
+        pipe->want_connectors = card->tcons;
+        card->tcons = tcons;
+
+        r = gfx_pipe_commit_wanted(pipe, mode);
+        if (r == -EACCES)
+                return r;
+
+        return 0;
+}
+
+static int gfx_config_apply_current(sd_gfx_card *card)
+{
+        sd_gfx_pipe *pipe;
+        gfx_connector *connector;
+        unsigned int i, j, num;
+        int r;
+
+        /* Optional second step during setup. Tries to take-over any existing
+         * setup that was left from the previous session but with our own FB.
+         * This is a conservative mode which allows us to avoid any pipe
+         * configurations on our own and rely on the previous session to do
+         * a suitable setup. We just take it over. */
+
+        for (i = 0; i < card->pipe_ids; ++i) {
+                pipe = card->pipes[i];
+                if (!pipe || pipe->assigned || !pipe->kern_mode)
+                        continue;
+
+                num = 0;
+                for (j = 0; j < card->connector_ids; ++j) {
+                        connector = card->connectors[j];
+                        if (!connector)
+                                continue;
+                        if (!connector->connected || connector->assigned)
+                                continue;
+                        if (!connector->encoder || connector->encoder->pipe != pipe)
+                                continue;
+                        if (connector->want_config)
+                                continue;
+
+                        pipe->want_connectors[num++] = connector;
+                }
+
+                if (!num)
+                        continue;
+                if (num > 1 && !card->clone_tory)
+                        continue;
+
+                pipe->want_rotation = 0;
+                pipe->want_connector_count = num;
+                r = gfx_pipe_commit_wanted(pipe, pipe->kern_mode);
+                if (r == -EACCES)
+                        return r;
+        }
+
+        return 0;
+}
+
+static int gfx_config_apply_remaining(sd_gfx_card *card) {
+        sd_gfx_pipe *pipe;
+        gfx_connector *connector;
+        unsigned int i, j;
+        int r;
+
+        /* Last step when applying configurations. Picks up all unassigned
+         * pipes and tries to find an unassigned connector. Performs
+         * mode-setting if combination found, otherwise disables the pipe. */
+
+        for (i = 0; i < card->pipe_ids; ++i) {
+                pipe = card->pipes[i];
+                if (!pipe || pipe->assigned)
+                        continue;
+
+                for (j = 0; j < card->connector_ids; ++j) {
+                        connector = card->connectors[j];
+                        if (!connector || !connector->preferred_mode)
+                                continue;
+                        if (!connector->connected || connector->assigned)
+                                continue;
+                        if (!(connector->possible_pipes & (1 << i)))
+                                continue;
+                        if (connector->want_config && connector->want_config->disable)
+                                continue;
+
+                        pipe->want_rotation = connector->want_config ? connector->want_config->rotate : 0;
+                        pipe->want_connector_count = 1;
+                        pipe->want_connectors[0] = connector;
+
+                        r = gfx_pipe_commit_wanted(pipe, connector->preferred_mode);
+                        if (r >= 0)
+                                break;
+                        else if (r == -EACCES)
+                                return r;
+                }
+
+                if (j >= card->connector_ids) {
+                        pipe->want_connector_count = 0;
+                        pipe->want_connectors[0] = NULL;
+
+                        r = gfx_pipe_commit_wanted(pipe, NULL);
+                        if (r == -EACCES)
+                                return r;
+                }
+        }
+
+        return 0;
+}
+
+static int gfx_config_apply(sd_gfx_card *card) {
+        gfx_config_pipe *conf_pipe;
+        int r;
+
+        /* Called on WAKEUP. Tries to assign all unassigned pipes and
+         * connectors and performs mode-setting. You should call
+         * gfx_config_reset() on SLEEP so this will re-assign *all* entities,
+         * in case the setup changed while asleep. If *_reset() was not called,
+         * this will only touch unassigned entities. */
+
+        log_debug("card %s: apply pipe-configurations", card->name);
+        for (conf_pipe = card->configs; conf_pipe; ++conf_pipe) {
+                r = gfx_config_apply_pipe(card, conf_pipe);
+                if (r < 0)
+                        return r;
+        }
+
+        if (card->tory) {
+                log_debug("card %s: adopt previous pipes", card->name);
+                r = gfx_config_apply_current(card);
+                if (r < 0)
+                        return r;
+        }
+
+        log_debug("card %s: set remaining pipes", card->name);
+        return gfx_config_apply_remaining(card);
+}
+
+static int gfx_config_update(sd_gfx_card *card) {
+        sd_gfx_pipe *pipe;
+        gfx_connector *connector;
+        gfx_config_pipe *conf_pipe;
+        unsigned int i, j, num;
+        bool set;
+        int r;
+
+        /* Called on HOTPLUG events. All pipes and connectors were already
+         * assigned. We first verify that each pipe has all its connectors
+         * still set and re-assign them in case they aren't. We also pick up
+         * any possibly new connector that showed up and matches the config.
+         * Any unused pipe is reset to unassigned state again and we let
+         * gfx_config_apply_remaining() pick up any additional connector. */
+
+        for (i = 0; i < card->connector_ids; ++i) {
+                connector = card->connectors[i];
+                if (connector && connector->connected)
+                        if (!connector->encoder || !connector->encoder->pipe)
+                                connector->assigned = 0;
+        }
+
+        for (i = 0; i < card->pipe_ids; ++i) {
+                pipe = card->pipes[i];
+                if (!pipe)
+                        continue;
+
+                if (!pipe->assigned)
+                        continue;
+
+                if (!pipe->mode) {
+                        pipe->assigned = 0;
+                        continue;
+                }
+
+                conf_pipe = NULL;
+                set = false;
+                num = 0;
+
+                /* pick up all previous connectors */
+                for (j = 0; j < pipe->connector_count; ++j) {
+                        connector = pipe->connectors[j];
+                        conf_pipe = connector->want_config;
+                        if (!connector->connected) {
+                                connector->assigned = 0;
+                                continue;
+                        }
+
+                        pipe->want_connectors[num++] = connector;
+                        if (!connector->encoder || connector->encoder->pipe != pipe)
+                                set = true;
+                }
+
+                /* pick up any new connector */
+                if (conf_pipe) {
+                        for (j = 0; j < card->connector_ids; ++j) {
+                                connector = card->connectors[j];
+                                if (!connector || !connector->connected)
+                                        continue;
+                                if (connector->assigned || connector->want_config != conf_pipe)
+                                        continue;
+
+                                pipe->want_connectors[num++] = connector;
+                                set = true;
+                        }
+                }
+
+                if (set || num != pipe->connector_count) {
+                        pipe->assigned = 0;
+                        for (j = 0; j < num; ++j)
+                                pipe->want_connectors[j]->assigned = 0;
+                        gfx_pipe_call(pipe, SD_GFX_CARD_PIPE_SLEEP);
+
+                        if (num) {
+                                pipe->want_connector_count = num;
+                                r = gfx_pipe_commit_wanted(pipe, pipe->mode);
+                                if (r == -EACCES)
+                                        return r;
+                        }
+
+                        if (!pipe->assigned) {
+                                sd_gfx_mode_unref(pipe->mode);
+                                pipe->mode = NULL;
+                                sd_gfx_mode_unref(pipe->want_mode);
+                                pipe->want_mode = NULL;
+                                pipe->connector_count = 0;
+                                pipe->want_connector_count = 0;
+                        }
+                }
+        }
+
+        return gfx_config_apply_remaining(card);
+}
+
+static void gfx_config_assign(sd_gfx_card *card) {
+        gfx_config_pipe *conf_pipe;
+        gfx_connector *connector;
+        unsigned int i;
+        char **c;
+
+        /* Whenever card->configs changes, call this to re-assign pipe-configs
+         * to the correct connectors. This invalidates the config-caches so
+         * you *must* apply the config afterwards to take effect. */
+
+        for (i = 0; i < card->connector_ids; ++i)
+                if (card->connectors[i])
+                        card->connectors[i]->want_config = NULL;
+
+        for (conf_pipe = card->configs; conf_pipe; ++conf_pipe) {
+                for (c = conf_pipe->connectors; c; ++c) {
+                        for (i = 0; i < card->connector_ids; ++i) {
+                                connector = card->connectors[i];
+                                if (connector && gfx_connector_match(connector, *c))
+                                        break;
+                        }
+
+                        if (i >= card->connector_ids) {
+                               log_warning("card %s: configured connector %s not found in pipe-config %u",
+                                           card->name, *c, conf_pipe->config_id);
+                                continue;
+                        } else if (connector->want_config) {
+                                log_warning("card %s: configured connector %s already assigned (pipe-config %u:%u)",
+                                            card->name, *c, connector->want_config->config_id, conf_pipe->config_id);
+                                continue;
+                        }
+
+                        connector->want_config = conf_pipe;
+                }
+        }
+}
+
+static void gfx_config_reset(sd_gfx_card *card) {
+        unsigned int i;
+        sd_gfx_pipe *pipe;
+
+        for (i = 0; i < card->pipe_ids; ++i) {
+                pipe = card->pipes[i];
+                if (!pipe)
+                        continue;
+
+                if (pipe->assigned && pipe->mode)
+                        gfx_pipe_call(pipe, SD_GFX_CARD_PIPE_SLEEP);
+
+                pipe->assigned = 0;
+
+                sd_gfx_mode_unref(pipe->mode);
+                pipe->mode = NULL;
+                sd_gfx_mode_unref(pipe->want_mode);
+                pipe->want_mode = NULL;
+                pipe->want_rotation = 0;
+                pipe->connector_count = 0;
+                pipe->want_connector_count = 0;
+        }
+
+        for (i = 0; i < card->connector_ids; ++i) {
+                if (card->connectors[i])
+                        card->connectors[i]->assigned = 0;
+        }
+}
+
+/*
+ * Card Manager
+ * Each DRM device is represented by a sd_gfx_card object. It manages all DRM
+ * resources, allocates them during device setup and monitors the devices for
+ * hotplug events.
+ *
+ * A newly allocated card is always asleep. If you want to use it, you need to
+ * wake it up. On each wake-up, it tries to restore the previous modeset state
+ * and notifies you about new or gone pipes.
+ * A device can be put asleep asynchronously. The card manager notices that
+ * and puts the device temporarily asleep. You must never assume the device is
+ * awake and fully functional as access might be revoked at any time.
+ */
+
+static void gfx_card_call(sd_gfx_card *card, unsigned int type) {
+        sd_gfx_card_event ev;
+
+        memset(&ev, 0, sizeof(ev));
+        ev.type = type;
+
+        if (card->event_fn)
+                card->event_fn(card, card->fn_data, &ev);
+}
+
+static void gfx_card_page_flip(sd_gfx_card *card, unsigned int frame, unsigned int sec, unsigned int usec, uint64_t data) {
+        unsigned int id;
+        sd_gfx_pipe *pipe;
+
+        gfx_decode_arg(&data, &id);
+        if (id >= card->pipe_ids)
+                return;
+
+        pipe = card->pipes[id];
+        if (!pipe)
+                return;
+
+        gfx_pipe_page_flip(pipe, frame, sec, usec, data);
+}
+
+static int gfx_card_io_fn(sd_event_source *source, int fd, uint32_t revents, void *data) {
+        sd_gfx_card *card = data;
+        struct drm_event *ev;
+        struct drm_event_vblank *vb;
+        char buf[1024];
+        ssize_t i, l;
+
+        if (revents & (EPOLLHUP | EPOLLERR)) {
+                log_debug("card %s: hangup", card->name);
+                sd_event_source_set_enabled(source, SD_EVENT_OFF);
+                gfx_card_call(card, SD_GFX_CARD_HUP);
+                return 0;
+        }
+
+        if (!(revents & EPOLLIN))
+                return 0;
+
+        l = read(fd, buf, sizeof(buf));
+        if (l < 0) {
+                if (errno == EAGAIN || errno == EINTR)
+                        return 0;
+
+                log_debug("card %s: read(): %m", card->name);
+                return 0;
+        } else if (!l) {
+                log_debug("card %s: read() EOF", card->name);
+                return 0;
+        } else if (l < (ssize_t)sizeof(*ev)) {
+                log_error("card %s: read(): %zu < %zu",
+                          card->name, (size_t)l, sizeof(*ev));
+                return 0;
+        }
+
+        i = 0;
+        while (i < l) {
+                ev = (void*)&buf[i];
+
+                switch (ev->type) {
+                case DRM_EVENT_FLIP_COMPLETE:
+                        vb = (void*)ev;
+                        gfx_card_page_flip(card, vb->sequence, vb->tv_sec, vb->tv_usec, vb->user_data);
+                        break;
+                default:
+                        break;
+                }
+
+                i += ev->length;
+        }
+
+        return 0;
+}
+
+static void gfx_card_print_dev(sd_gfx_card *card) {
+        unsigned int i, j;
+        sd_gfx_pipe *pipe;
+        gfx_encoder *encoder;
+        gfx_connector *connector;
+        sd_gfx_mode *mode;
+
+        log_debug("card %s: current device-state is:", card->name);
+
+        log_debug("  pipes:");
+        for (i = 0; i < card->pipe_ids; ++i) {
+                pipe = card->pipes[i];
+                if (!pipe) {
+                        log_debug("    %u missing", i);
+                        continue;
+                }
+
+                log_debug("    (id: %u crtc: %d)",
+                          i, (int)pipe->crtc_id);
+                if (pipe->kern_mode)
+                        log_debug("      mode: %dx%d",
+                                  (int)pipe->kern_mode->info.hdisplay,
+                                  (int)pipe->kern_mode->info.vdisplay);
+                else
+                        log_debug("      mode: <none>");
+        }
+
+        log_debug("  encoders:");
+        for (i = 0; i < card->encoder_ids; ++i) {
+                encoder = card->encoders[i];
+                if (!encoder) {
+                        log_debug("    %u missing", i);
+                        continue;
+                }
+
+                log_debug("    (id: %u encoder: %d)",
+                          i, (int)encoder->encoder_id);
+                if (encoder->pipe)
+                        log_debug("      pipe: %u", encoder->pipe->id);
+                else
+                        log_debug("      pipe: <none>");
+                log_debug("      can: pipes: %08x clones: %08x",
+                          (unsigned int)encoder->possible_pipes,
+                          (unsigned int)encoder->possible_clones);
+        }
+
+        log_debug("  connectors:");
+        for (i = 0; i < card->connector_ids; ++i) {
+                connector = card->connectors[i];
+                if (!connector) {
+                        log_debug("    %u missing", i);
+                        continue;
+                }
+
+                log_debug("    (id: %u connector: %d state: %d dpms: %u)",
+                          i, (int)connector->connector_id, (int)connector->connected, connector->dpms);
+                log_debug("      type: %s-%d", connector->type, (int)connector->type_id);
+                if (connector->encoder)
+                        log_debug("      encoder: %u", connector->encoder->id);
+                else
+                        log_debug("      encoder: <none>");
+                log_debug("      can: encoders: %08x pipes: %08x",
+                          (unsigned int)connector->possible_encoders,
+                          (unsigned int)connector->possible_pipes);
+
+                for (j = 0; j < connector->mode_count; ++j) {
+                        mode = connector->modes[j];
+                        log_debug("      mode: %dx%d%s%s",
+                                  (int)mode->info.hdisplay, (int)mode->info.vdisplay,
+                                  (mode == connector->preferred_mode) ? " +preferred" : "",
+                                  (mode == connector->default_mode) ? " +default" : "");
+                }
+        }
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(drmModeRes*, drmModeFreeResources);
+
+static int gfx_card_open(sd_gfx_card *card) {
+        _cleanup_(drmModeFreeResourcesp) drmModeRes *res = NULL;
+        drmModeCrtc *crtc;
+        drmModeEncoder *enc;
+        drmModeConnector *con;
+        int i, r;
+        sd_gfx_pipe *pipe;
+        gfx_encoder *encoder;
+        gfx_connector *connector;
+
+        /* All Mode-Objects of a DRM device are known during probe-time.
+         * Hotplugging of single mode-objects is not supported by the DRM
+         * API. Thus, the API relies on the object-order to assign IDs
+         * implicitly. This means, the 3rd encoder in res->encoders gets
+         * the implicit ID "3" (together with the explicit mode-object ID
+         * "encoder_id"). The implicit IDs are per-type, the explicit IDs
+         * are per-device.
+         * Implicit IDs are used for bitmaps ("possible_crtcs",
+         * "possible_clones", ...), explicit IDs are used for object
+         * identification in global scope (ioctls, modesetting, ...).
+         * Note that implicit IDs are not really part of the kernel ABI,
+         * but still there's no way to use the API without them. Kind of
+         * ugly, but everyone relies on these IDs so that makes them part
+         * of the ABI, right? */
+
+        res = drmModeGetResources(card->fd);
+        if (!res) {
+                log_error("card %s: GetResources(): %m", card->name);
+                return -EINVAL;
+        }
+
+        card->pipes = calloc(res->count_crtcs, sizeof(*card->pipes));
+        if (!card->pipes)
+                return log_oom();
+
+        card->encoders = calloc(res->count_encoders, sizeof(*card->encoders));
+        if (!card->encoders)
+                return log_oom();
+
+        card->connectors = calloc(res->count_connectors, sizeof(*card->connectors));
+        if (!card->connectors)
+                return log_oom();
+
+        card->tcons = calloc(res->count_connectors, sizeof(*card->tcons));
+        if (!card->tcons)
+                return log_oom();
+
+        /* We *must* be careful here to assign the correct implicit IDs
+         * to each mode-object. If we cannot create an object, its ID
+         * must stay allocated (but unassigned)! Otherwise, our IDs are
+         * not in sync with the kernel. */
+
+        for (i = 0; i < res->count_crtcs; ++i) {
+                if (i >= 32) {
+                        log_warning("card %s: too many pipes", card->name);
+                        break;
+                }
+
+                crtc = drmModeGetCrtc(card->fd, res->crtcs[i]);
+                if (!crtc) {
+                        log_error("card %s: GetCrtc(%d): %m",
+                                  card->name, (int)res->crtcs[i]);
+                } else {
+                        pipe = NULL;
+                        r = gfx_pipe_new(&pipe, card, crtc, res->count_connectors);
+                        if (r >= 0)
+                                card->pipes[pipe->id] = pipe;
+
+                        drmModeFreeCrtc(crtc);
+                }
+                ++card->pipe_ids;
+        }
+
+        for (i = 0; i < res->count_encoders; ++i) {
+                if (i >= 32) {
+                        log_warning("card %s: too many encoders", card->name);
+                        break;
+                }
+
+                enc = drmModeGetEncoder(card->fd, res->encoders[i]);
+                if (!enc) {
+                        log_error("card %s: GetEncoder(%d): %m",
+                                  card->name, (int)res->encoders[i]);
+                } else {
+                        encoder = NULL;
+                        r = gfx_encoder_new(&encoder, card, enc);
+                        if (r >= 0)
+                                card->encoders[encoder->id] = encoder;
+
+                        drmModeFreeEncoder(enc);
+                }
+                ++card->encoder_ids;
+        }
+
+        for (i = 0; i < res->count_connectors; ++i) {
+                if (i >= 32) {
+                        log_warning("card %s: too many connectors", card->name);
+                        break;
+                }
+
+                con = drmModeGetConnector(card->fd, res->connectors[i]);
+                if (!con) {
+                        log_error("card %s: GetConnector(%d): %m",
+                                  card->name, (int)res->connectors[i]);
+                } else {
+                        connector = NULL;
+                        r = gfx_connector_new(&connector, card, con);
+                        if (r >= 0)
+                                card->connectors[connector->id] = connector;
+
+                        drmModeFreeConnector(con);
+                }
+                ++card->connector_ids;
+        }
+
+        return 0;
+}
+
+int sd_gfx_card_new(sd_gfx_card **out,
+                    const char *name,
+                    int fd,
+                    sd_event *event) {
+        sd_gfx_card *card;
+        uint64_t has_dumb = 0;
+        int r;
+
+        assert(name);
+        assert(fd >= 0);
+        assert(event);
+
+        log_debug("card %s: new", name);
+
+        r = drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_dumb);
+        if (r < 0 || !has_dumb) {
+                log_error("card %s: dumb buffers not supported", name);
+                return -ENODEV;
+        }
+
+        card = calloc(1, sizeof(*card));
+        if (!card)
+                return log_oom();
+
+        card->name = strdup(name);
+        if (!card->name) {
+                r = log_oom();
+                goto err_free;
+        }
+
+        card->fd = fd;
+        card->tory = 1;
+
+        r = sd_event_add_io(event, fd, EPOLLIN, gfx_card_io_fn, card, &card->fd_source);
+        if (r < 0)
+                goto err_free;
+
+        card->event = event;
+        sd_event_ref(card->event);
+
+        r = gfx_card_open(card);
+        if (r < 0) {
+                sd_gfx_card_free(card);
+                return r;
+        }
+
+        gfx_config_assign(card);
+        *out = card;
+        return 0;
+
+err_free:
+        free(card->name);
+        free(card);
+        return r;
+}
+
+void sd_gfx_card_free(sd_gfx_card *card) {
+        unsigned int i;
+        sd_gfx_pipe *pipe;
+
+        if (!card)
+                return;
+
+        log_debug("card %s: free", card->name);
+
+        for (i = 0; i < card->pipe_ids; ++i) {
+                pipe = card->pipes[i];
+                if (!pipe)
+                        continue;
+
+                gfx_pipe_unlink(pipe);
+                sd_gfx_pipe_unref(pipe);
+        }
+
+        for (i = 0; i < card->encoder_ids; ++i)
+                gfx_encoder_free(card->encoders[i]);
+
+        for (i = 0; i < card->connector_ids; ++i)
+                gfx_connector_free(card->connectors[i]);
+
+        free(card->tcons);
+        free(card->connectors);
+        free(card->encoders);
+        free(card->pipes);
+        sd_event_source_set_enabled(card->fd_source, SD_EVENT_OFF);
+        sd_event_source_unref(card->fd_source);
+        sd_event_unref(card->event);
+        close_nointr(card->fd);
+        free(card->name);
+        free(card);
+}
+
+void sd_gfx_card_set_data(sd_gfx_card *card, void *data) {
+        card->data = data;
+}
+
+void *sd_gfx_card_get_data(sd_gfx_card *card) {
+        return card->data;
+}
+
+void sd_gfx_card_set_fn_data(sd_gfx_card *card, void *fn_data) {
+        card->fn_data = fn_data;
+}
+
+void *sd_gfx_card_get_fn_data(sd_gfx_card *card) {
+        return card->fn_data;
+}
+
+void sd_gfx_card_set_event_fn(sd_gfx_card *card, sd_gfx_card_event_fn event_fn) {
+        card->event_fn = event_fn;
+}
+
+static void gfx_card_refresh(sd_gfx_card *card) {
+        unsigned int i;
+        sd_gfx_pipe *pipe;
+        gfx_encoder *encoder;
+        gfx_connector *connector;
+
+        log_debug("card %s: refresh device state", card->name);
+
+        for (i = 0; i < card->pipe_ids; ++i) {
+                pipe = card->pipes[i];
+                if (!pipe)
+                        continue;
+
+                if (!card->public)
+                        gfx_pipe_call(pipe, SD_GFX_CARD_PIPE_CREATE);
+
+                gfx_pipe_refresh(pipe, NULL);
+        }
+
+        card->public = 1;
+
+        for (i = 0; i < card->encoder_ids; ++i) {
+                encoder = card->encoders[i];
+                if (!encoder)
+                        continue;
+
+                gfx_encoder_refresh(encoder, NULL);
+        }
+
+        for (i = 0; i < card->connector_ids; ++i) {
+                connector = card->connectors[i];
+                if (!connector)
+                        continue;
+
+                gfx_connector_refresh(connector, NULL);
+        }
+
+        gfx_card_print_dev(card);
+}
+
+int sd_gfx_card_wake_up(sd_gfx_card *card) {
+        if (card->awake)
+                return 0;
+
+        log_debug("card %s: wake up", card->name);
+
+        card->awake = 1;
+        card->real_awake = 1;
+        gfx_card_refresh(card);
+
+        return gfx_config_apply(card);
+}
+
+void sd_gfx_card_sleep(sd_gfx_card *card) {
+        if (!card->awake)
+                return;
+
+        log_debug("card %s: sync-sleep", card->name);
+
+        card->awake = 0;
+        card->real_awake = 0;
+        gfx_config_reset(card);
+}
+
+bool sd_gfx_card_is_awake(sd_gfx_card *card) {
+        return card->real_awake;
+}
+
+static void gfx_card_async_sleep(sd_gfx_card *card) {
+        if (card->real_awake) {
+                card->real_awake = 0;
+                log_debug("card %s: async-sleep", card->name);
+        }
+}
+
+void sd_gfx_card_hotplug(sd_gfx_card *card) {
+        if (!card->real_awake)
+                return;
+
+        gfx_card_refresh(card);
+        gfx_config_update(card);
+}
+
+void sd_gfx_card_restore(sd_gfx_card *card) {
+        unsigned int i;
+        sd_gfx_pipe *pipe;
+
+        if (card->awake)
+                sd_gfx_card_sleep(card);
+
+        for (i = 0; i < card->pipe_ids; ++i) {
+                pipe = card->pipes[i];
+                if (!pipe)
+                        continue;
+
+                gfx_pipe_commit_kern(pipe);
+        }
+}
diff --git a/src/systemd/sd-gfx.h b/src/systemd/sd-gfx.h
index 96d034a..44237a1 100644
--- a/src/systemd/sd-gfx.h
+++ b/src/systemd/sd-gfx.h
@@ -37,6 +37,13 @@ typedef struct sd_gfx_buffer sd_gfx_buffer;
 
 typedef struct sd_gfx_font sd_gfx_font;
 
+typedef struct sd_gfx_fb sd_gfx_fb;
+typedef struct sd_gfx_plane sd_gfx_plane;
+typedef struct sd_gfx_mode sd_gfx_mode;
+typedef struct sd_gfx_pipe sd_gfx_pipe;
+typedef struct sd_gfx_card_event sd_gfx_card_event;
+typedef struct sd_gfx_card sd_gfx_card;
+
 typedef struct sd_gfx_kbd_event sd_gfx_kbd_event;
 typedef struct sd_gfx_kbd sd_gfx_kbd;
 
@@ -67,6 +74,194 @@ unsigned int sd_gfx_font_get_height(sd_gfx_font *font);
 
 void sd_gfx_font_render(sd_gfx_font *font, uint32_t id, const uint32_t *ucs4, size_t len, sd_gfx_buffer **out);
 
+/* framebuffer */
+
+typedef void (*sd_gfx_fb_unlink_fn) (sd_gfx_fb *fb, void *fn_data);
+typedef void (*sd_gfx_fb_unpin_fn) (sd_gfx_fb *fb, void *fn_data);
+
+int sd_gfx_fb_new(sd_gfx_fb **out,
+                  sd_gfx_plane *plane,
+                  uint32_t format,
+                  uint32_t handles[4],
+                  uint32_t width,
+                  uint32_t height,
+                  uint32_t strides[4],
+                  uint32_t offsets[4],
+                  uint64_t size,
+                  uint64_t map_offset);
+int sd_gfx_fb_new_rgb(sd_gfx_fb **out,
+                      sd_gfx_plane *plane,
+                      uint32_t format,
+                      uint32_t handle,
+                      uint32_t width,
+                      uint32_t height,
+                      uint32_t stride,
+                      uint32_t offset,
+                      uint64_t size,
+                      uint64_t map_offset);
+int sd_gfx_fb_new_dumb(sd_gfx_fb **out,
+                       sd_gfx_plane *plane,
+                       uint32_t format,
+                       uint32_t bpp,
+                       uint32_t width,
+                       uint32_t height);
+
+void sd_gfx_fb_ref(sd_gfx_fb *fb);
+void sd_gfx_fb_unref(sd_gfx_fb *fb);
+
+void sd_gfx_fb_set_data(sd_gfx_fb *fb, void *data);
+void *sd_gfx_fb_get_data(sd_gfx_fb *fb);
+void sd_gfx_fb_set_fn_data(sd_gfx_fb *fb, void *fn_data);
+void *sd_gfx_fb_get_fn_data(sd_gfx_fb *fb);
+
+void sd_gfx_fb_set_fns(sd_gfx_fb *fb, sd_gfx_fb_unlink_fn unlink_fn, sd_gfx_fb_unpin_fn unpin_fn);
+
+sd_gfx_plane *sd_gfx_fb_get_plane(sd_gfx_fb *fb);
+uint32_t sd_gfx_fb_get_format(sd_gfx_fb *fb);
+uint32_t sd_gfx_fb_get_width(sd_gfx_fb *fb);
+uint32_t sd_gfx_fb_get_height(sd_gfx_fb *fb);
+
+void sd_gfx_fb_fill(sd_gfx_fb *fb,
+                    uint32_t color,
+                    unsigned int pos_x,
+                    unsigned int pos_y,
+                    unsigned int width,
+                    unsigned int height);
+void sd_gfx_fb_blend_bichrome(sd_gfx_fb *fb,
+                              uint32_t fg_color,
+                              uint32_t bg_color,
+                              unsigned int pos_x,
+                              unsigned int pos_y,
+                              const sd_gfx_buffer *source);
+
+/* plane */
+
+typedef int (*sd_gfx_plane_prepare_fn) (sd_gfx_plane *plane,
+                                        void *fn_data,
+                                        unsigned int width,
+                                        unsigned int height,
+                                        sd_gfx_fb **fb);
+typedef void (*sd_gfx_plane_cancel_fn) (sd_gfx_plane *plane,
+                                        void *fn_data,
+                                        sd_gfx_fb *fb);
+typedef void (*sd_gfx_plane_finish_fn) (sd_gfx_plane *plane,
+                                        void *fn_data,
+                                        sd_gfx_fb *fb);
+
+void sd_gfx_plane_ref(sd_gfx_plane *plane);
+void sd_gfx_plane_unref(sd_gfx_plane *plane);
+
+void sd_gfx_plane_set_data(sd_gfx_plane *plane, void *data);
+void *sd_gfx_plane_get_data(sd_gfx_plane *plane);
+void sd_gfx_plane_set_fn_data(sd_gfx_plane *plane, void *fn_data);
+void *sd_gfx_plane_get_fn_data(sd_gfx_plane *plane);
+
+void sd_gfx_plane_set_fns(sd_gfx_plane *plane,
+                          sd_gfx_plane_prepare_fn prepare_fn,
+                          sd_gfx_plane_cancel_fn cancel_fn,
+                          sd_gfx_plane_finish_fn finish_fn);
+
+unsigned int sd_gfx_plane_get_id(sd_gfx_plane *plane);
+sd_gfx_card *sd_gfx_plane_get_card(sd_gfx_plane *plane);
+sd_gfx_pipe *sd_gfx_plane_get_pipe(sd_gfx_plane *plane);
+
+bool sd_gfx_plane_is_enabled(sd_gfx_plane *plane);
+void sd_gfx_plane_enable(sd_gfx_plane *plane);
+void sd_gfx_plane_disable(sd_gfx_plane *plane);
+
+bool sd_gfx_plane_supports_format(sd_gfx_plane *plane, uint32_t format);
+
+bool sd_gfx_plane_supports_pipe(sd_gfx_plane *plane, sd_gfx_pipe *pipe);
+
+sd_gfx_fb *sd_gfx_plane_get_front(sd_gfx_plane *plane);
+sd_gfx_fb *sd_gfx_plane_get_back(sd_gfx_plane *plane);
+void sd_gfx_plane_swap_to(sd_gfx_plane *plane, sd_gfx_fb *fb);
+
+/* mode */
+
+void sd_gfx_mode_ref(sd_gfx_mode *mode);
+void sd_gfx_mode_unref(sd_gfx_mode *mode);
+
+unsigned int sd_gfx_mode_get_width(sd_gfx_mode *mode);
+unsigned int sd_gfx_mode_get_height(sd_gfx_mode *mode);
+const char *sd_gfx_mode_get_name(sd_gfx_mode *mode);
+
+/* pipe */
+
+enum {
+        SD_GFX_DPMS_ON,
+        SD_GFX_DPMS_STANDBY,
+        SD_GFX_DPMS_SUSPEND,
+        SD_GFX_DPMS_OFF,
+        SD_GFX_DPMS_UNKNOWN,
+};
+
+void sd_gfx_pipe_ref(sd_gfx_pipe *pipe);
+void sd_gfx_pipe_unref(sd_gfx_pipe *pipe);
+
+void sd_gfx_pipe_set_data(sd_gfx_pipe *pipe, void *data);
+void *sd_gfx_pipe_get_data(sd_gfx_pipe *pipe);
+void sd_gfx_pipe_set_fn_data(sd_gfx_pipe *pipe, void *fn_data);
+void *sd_gfx_pipe_get_fn_data(sd_gfx_pipe *pipe);
+
+unsigned int sd_gfx_pipe_get_id(sd_gfx_pipe *pipe);
+
+unsigned int sd_gfx_pipe_get_dpms(sd_gfx_pipe *pipe);
+void sd_gfx_pipe_set_dpms(sd_gfx_pipe *pipe, unsigned int dpms);
+
+sd_gfx_mode *sd_gfx_pipe_get_mode(sd_gfx_pipe *pipe);
+sd_gfx_plane *sd_gfx_pipe_get_cursor(sd_gfx_pipe *pipe);
+sd_gfx_plane *sd_gfx_pipe_get_primary_plane(sd_gfx_pipe *pipe);
+
+int sd_gfx_pipe_attach_plane(sd_gfx_pipe *pipe, sd_gfx_plane *plane);
+void sd_gfx_pipe_detach_plane(sd_gfx_pipe *pipe, sd_gfx_plane *plane);
+
+int sd_gfx_pipe_commit(sd_gfx_pipe *pipe);
+
+/* card */
+
+enum {
+        SD_GFX_CARD_HUP,
+
+        SD_GFX_CARD_PIPE_CREATE,
+        SD_GFX_CARD_PIPE_DESTROY,
+        SD_GFX_CARD_PIPE_WAKE_UP,
+        SD_GFX_CARD_PIPE_SLEEP,
+        SD_GFX_CARD_PIPE_SWAP,
+
+        SD_GFX_CARD_PLANE_CREATE,
+        SD_GFX_CARD_PLANE_DESTROY,
+};
+
+struct sd_gfx_card_event {
+        unsigned int type;
+
+        sd_gfx_pipe *pipe;
+        sd_gfx_plane *plane;
+        sd_gfx_mode *old_mode;
+        sd_gfx_fb *fb;
+};
+
+typedef void (*sd_gfx_card_event_fn) (sd_gfx_card *card, void *fn_data, sd_gfx_card_event *event);
+
+int sd_gfx_card_new(sd_gfx_card **out,
+                    const char *name,
+                    int fd,
+                    sd_event *event);
+void sd_gfx_card_free(sd_gfx_card *card);
+
+void sd_gfx_card_set_data(sd_gfx_card *card, void *data);
+void *sd_gfx_card_get_data(sd_gfx_card *card);
+void sd_gfx_card_set_fn_data(sd_gfx_card *card, void *fn_data);
+void *sd_gfx_card_get_fn_data(sd_gfx_card *card);
+void sd_gfx_card_set_event_fn(sd_gfx_card *card, sd_gfx_card_event_fn event_fn);
+
+int sd_gfx_card_wake_up(sd_gfx_card *card);
+void sd_gfx_card_sleep(sd_gfx_card *card);
+bool sd_gfx_card_is_awake(sd_gfx_card *card);
+void sd_gfx_card_hotplug(sd_gfx_card *card);
+void sd_gfx_card_restore(sd_gfx_card *card);
+
 /* keyboard */
 
 enum {
-- 
1.8.4.2



More information about the systemd-devel mailing list