chiark / gitweb /
treewide: use log_*_errno whenever %m is in the format string
[elogind.git] / src / libsystemd-terminal / grdev-drm.c
index 3481584fbf760bc4e3331b9a2e76a6c4b0e5240d..2df63537f09b216e95a015f55119715df5ba5a99 100644 (file)
@@ -264,6 +264,7 @@ struct grdrm_card {
         Hashmap *object_map;
 
         bool async_hotplug : 1;
+        bool hotplug : 1;
         bool running : 1;
         bool ready : 1;
         bool cap_dumb : 1;
@@ -346,6 +347,8 @@ static bool grdrm_modes_compatible(const struct drm_mode_modeinfo *a, const stru
                 return false;
         if (a->vdisplay != b->vdisplay)
                 return false;
+        if (a->vrefresh != b->vrefresh)
+                return false;
 
         return true;
 }
@@ -465,9 +468,11 @@ static int grdrm_plane_resync(grdrm_plane *plane) {
                         if (r == -ENOENT) {
                                 card->async_hotplug = true;
                                 r = 0;
-                                log_debug("grdrm: %s: plane %u removed during resync", card->base.name, plane->object.id);
+                                log_debug("grdrm: %s: plane %u removed during resync",
+                                          card->base.name, plane->object.id);
                         } else {
-                                log_debug("grdrm: %s: cannot retrieve plane %u: %m", card->base.name, plane->object.id);
+                                log_debug_errno(errno, "grdrm: %s: cannot retrieve plane %u: %m",
+                                                card->base.name, plane->object.id);
                         }
 
                         return r;
@@ -601,12 +606,19 @@ static int grdrm_connector_resync(grdrm_connector *connector) {
                 res.count_encoders = connector->kern.max_encoders;
                 res.count_props = connector->kern.max_props;
 
-                /* Retrieve modes only if we have none. This avoids expensive
-                 * EDID reads in the kernel, that can slow down resyncs
-                 * considerably! */
-                if (connector->kern.n_modes == 0) {
-                        res.modes_ptr = PTR_TO_UINT64(connector->kern.modes);
-                        res.count_modes = connector->kern.max_modes;
+                /* The kernel reads modes from the EDID information only if we
+                 * pass count_modes==0. This is a legacy hack for libdrm (which
+                 * called every ioctl twice). Now we have to adopt.. *sigh*.
+                 * If we never received an hotplug event, there's no reason to
+                 * sync modes. EDID reads are heavy, so skip that if not
+                 * required. */
+                if (card->hotplug) {
+                        if (tries > 0) {
+                                res.modes_ptr = PTR_TO_UINT64(connector->kern.modes);
+                                res.count_modes = connector->kern.max_modes;
+                        } else {
+                                resized = true;
+                        }
                 }
 
                 r = ioctl(card->fd, DRM_IOCTL_MODE_GETCONNECTOR, &res);
@@ -615,9 +627,11 @@ static int grdrm_connector_resync(grdrm_connector *connector) {
                         if (r == -ENOENT) {
                                 card->async_hotplug = true;
                                 r = 0;
-                                log_debug("grdrm: %s: connector %u removed during resync", card->base.name, connector->object.id);
+                                log_debug("grdrm: %s: connector %u removed during resync",
+                                          card->base.name, connector->object.id);
                         } else {
-                                log_debug("grdrm: %s: cannot retrieve connector %u: %m", card->base.name, connector->object.id);
+                                log_debug_errno(errno, "grdrm: %s: cannot retrieve connector %u: %m",
+                                                card->base.name, connector->object.id);
                         }
 
                         return r;
@@ -687,7 +701,6 @@ static int grdrm_connector_resync(grdrm_connector *connector) {
                         continue;
 
                 connector->kern.n_encoders = res.count_encoders;
-                connector->kern.n_modes = res.count_modes;
                 connector->kern.n_props = res.count_props;
                 connector->kern.type = res.connector_type;
                 connector->kern.type_id = res.connector_type_id;
@@ -696,6 +709,8 @@ static int grdrm_connector_resync(grdrm_connector *connector) {
                 connector->kern.mm_width = res.mm_width;
                 connector->kern.mm_height = res.mm_height;
                 connector->kern.subpixel = res.subpixel;
+                if (res.modes_ptr == PTR_TO_UINT64(connector->kern.modes))
+                        connector->kern.n_modes = res.count_modes;
 
                 break;
         }
@@ -772,9 +787,11 @@ static int grdrm_encoder_resync(grdrm_encoder *encoder) {
                 if (r == -ENOENT) {
                         card->async_hotplug = true;
                         r = 0;
-                        log_debug("grdrm: %s: encoder %u removed during resync", card->base.name, encoder->object.id);
+                        log_debug("grdrm: %s: encoder %u removed during resync",
+                                  card->base.name, encoder->object.id);
                 } else {
-                        log_debug("grdrm: %s: cannot retrieve encoder %u: %m", card->base.name, encoder->object.id);
+                        log_debug_errno(errno, "grdrm: %s: cannot retrieve encoder %u: %m",
+                                        card->base.name, encoder->object.id);
                 }
 
                 return r;
@@ -905,9 +922,11 @@ static int grdrm_crtc_resync(grdrm_crtc *crtc) {
                 if (r == -ENOENT) {
                         card->async_hotplug = true;
                         r = 0;
-                        log_debug("grdrm: %s: crtc %u removed during resync", card->base.name, crtc->object.id);
+                        log_debug("grdrm: %s: crtc %u removed during resync",
+                                  card->base.name, crtc->object.id);
                 } else {
-                        log_debug("grdrm: %s: cannot retrieve crtc %u: %m", card->base.name, crtc->object.id);
+                        log_debug_errno(errno, "grdrm: %s: cannot retrieve crtc %u: %m",
+                                        card->base.name, crtc->object.id);
                 }
 
                 return r;
@@ -1038,7 +1057,8 @@ static void grdrm_crtc_expose(grdrm_crtc *crtc) {
         pipe = crtc->pipe;
         if (pipe) {
                 if (pipe->base.width != crtc->set.mode.hdisplay ||
-                    pipe->base.height != crtc->set.mode.vdisplay) {
+                    pipe->base.height != crtc->set.mode.vdisplay ||
+                    pipe->base.vrefresh != crtc->set.mode.vrefresh) {
                         grdev_pipe_free(&pipe->base);
                         crtc->pipe = NULL;
                         pipe = NULL;
@@ -1083,15 +1103,133 @@ static void grdrm_crtc_expose(grdrm_crtc *crtc) {
         grdev_pipe_ready(&crtc->pipe->base, true);
 }
 
-static void grdrm_crtc_commit(grdrm_crtc *crtc) {
-        struct drm_mode_crtc_page_flip page_flip = { .crtc_id = crtc->object.id };
+static void grdrm_crtc_commit_deep(grdrm_crtc *crtc, grdev_fb *basefb) {
         struct drm_mode_crtc set_crtc = { .crtc_id = crtc->object.id };
         grdrm_card *card = crtc->object.card;
-        grdrm_pipe *pipe;
-        grdev_fb **slot;
+        grdrm_pipe *pipe = crtc->pipe;
+        grdrm_fb *fb;
+        int r;
+
+        assert(crtc);
+        assert(basefb);
+        assert(pipe);
+
+        fb = fb_from_base(basefb);
+
+        set_crtc.set_connectors_ptr = PTR_TO_UINT64(crtc->set.connectors);
+        set_crtc.count_connectors = crtc->set.n_connectors;
+        set_crtc.fb_id = fb->id;
+        set_crtc.x = 0;
+        set_crtc.y = 0;
+        set_crtc.mode_valid = 1;
+        set_crtc.mode = crtc->set.mode;
+
+        r = ioctl(card->fd, DRM_IOCTL_MODE_SETCRTC, &set_crtc);
+        if (r < 0) {
+                r = -errno;
+                log_debug_errno(errno, "grdrm: %s: cannot set crtc %" PRIu32 ": %m",
+                                card->base.name, crtc->object.id);
+
+                grdrm_card_async(card, r);
+                return;
+        }
+
+        if (!crtc->applied) {
+                log_debug("grdrm: %s: crtc %" PRIu32 " applied via deep modeset",
+                          card->base.name, crtc->object.id);
+                crtc->applied = true;
+        }
+
+        pipe->base.back = NULL;
+        pipe->base.front = &fb->base;
+        fb->flipid = 0;
+        ++pipe->counter;
+        pipe->base.flipping = false;
+        pipe->base.flip = false;
+
+        /* We cannot schedule dummy page-flips on pipes, hence, the
+         * application would have to schedule their own frame-timers.
+         * To avoid duplicating that everywhere, we schedule our own
+         * timer and raise a fake FRAME event when it fires. */
+        grdev_pipe_schedule(&pipe->base, 1);
+}
+
+static int grdrm_crtc_commit_flip(grdrm_crtc *crtc, grdev_fb *basefb) {
+        struct drm_mode_crtc_page_flip page_flip = { .crtc_id = crtc->object.id };
+        grdrm_card *card = crtc->object.card;
+        grdrm_pipe *pipe = crtc->pipe;
         grdrm_fb *fb;
         uint32_t cnt;
-        size_t i;
+        int r;
+
+        assert(crtc);
+        assert(basefb);
+        assert(pipe);
+
+        if (!crtc->applied) {
+                if (!grdrm_modes_compatible(&crtc->kern.mode, &crtc->set.mode))
+                        return 0;
+
+                /* TODO: Theoretically, we should be able to page-flip to our
+                 * framebuffer here. We didn't perform any deep modeset, but the
+                 * DRM driver is really supposed to reject our page-flip in case
+                 * the FB is not compatible. We then properly fall back to a
+                 * deep modeset.
+                 * As it turns out, drivers don't to this. Therefore, we need to
+                 * perform a full modeset on enter now. We might avoid this in
+                 * the future with fixed drivers.. */
+
+                return 0;
+        }
+
+        fb = fb_from_base(basefb);
+
+        cnt = ++pipe->counter ? : ++pipe->counter;
+        page_flip.fb_id = fb->id;
+        page_flip.flags = DRM_MODE_PAGE_FLIP_EVENT;
+        page_flip.user_data = grdrm_encode_vblank_data(crtc->object.id, cnt);
+
+        r = ioctl(card->fd, DRM_IOCTL_MODE_PAGE_FLIP, &page_flip);
+        if (r < 0) {
+                r = -errno;
+                /* Avoid excessive logging on EINVAL; it is currently not
+                 * possible to see whether cards support page-flipping, so
+                 * avoid logging on each frame. */
+                if (r != -EINVAL)
+                        log_debug_errno(errno, "grdrm: %s: cannot schedule page-flip on crtc %" PRIu32 ": %m",
+                                        card->base.name, crtc->object.id);
+
+                if (grdrm_card_async(card, r))
+                        return r;
+
+                return 0;
+        }
+
+        if (!crtc->applied) {
+                log_debug("grdrm: %s: crtc %" PRIu32 " applied via page flip",
+                          card->base.name, crtc->object.id);
+                crtc->applied = true;
+        }
+
+        pipe->base.flipping = true;
+        pipe->base.flip = false;
+        pipe->counter = cnt;
+        fb->flipid = cnt;
+        pipe->base.back = NULL;
+
+        /* Raise fake FRAME event if it takes longer than 2
+         * frames to receive the pageflip event. We assume the
+         * queue ran over or some other error happened. */
+        grdev_pipe_schedule(&pipe->base, 2);
+
+        return 1;
+}
+
+static void grdrm_crtc_commit(grdrm_crtc *crtc) {
+        struct drm_mode_crtc set_crtc = { .crtc_id = crtc->object.id };
+        grdrm_card *card = crtc->object.card;
+        grdrm_pipe *pipe;
+        grdev_fb *fb;
         int r;
 
         assert(crtc);
@@ -1111,8 +1249,8 @@ static void grdrm_crtc_commit(grdrm_crtc *crtc) {
                         r = ioctl(card->fd, DRM_IOCTL_MODE_SETCRTC, &set_crtc);
                         if (r < 0) {
                                 r = -errno;
-                                log_debug("grdrm: %s: cannot shutdown crtc %" PRIu32 ": %m",
-                                          card->base.name, crtc->object.id);
+                                log_debug_errno(errno, "grdrm: %s: cannot shutdown crtc %" PRIu32 ": %m",
+                                                card->base.name, crtc->object.id);
 
                                 grdrm_card_async(card, r);
                                 return;
@@ -1132,111 +1270,20 @@ static void grdrm_crtc_commit(grdrm_crtc *crtc) {
         assert(crtc->set.n_connectors > 0);
 
         if (pipe->base.flip)
-                slot = &pipe->base.back;
+                fb = pipe->base.back;
         else if (!crtc->applied)
-                slot = &pipe->base.front;
+                fb = pipe->base.front;
         else
                 return;
 
-        if (!*slot)
+        if (!fb)
                 return;
 
-        fb = fb_from_base(*slot);
-
-        if (crtc->applied || grdrm_modes_compatible(&crtc->kern.mode, &crtc->set.mode)) {
-                cnt = ++pipe->counter ? : ++pipe->counter;
-                page_flip.fb_id = fb->id;
-                page_flip.flags = DRM_MODE_PAGE_FLIP_EVENT;
-                page_flip.user_data = grdrm_encode_vblank_data(crtc->object.id, cnt);
-
-                r = ioctl(card->fd, DRM_IOCTL_MODE_PAGE_FLIP, &page_flip);
-                if (r < 0) {
-                        r = -errno;
-                        log_debug("grdrm: %s: cannot schedule page-flip on crtc %" PRIu32 ": %m",
-                                  card->base.name, crtc->object.id);
-
-                        if (grdrm_card_async(card, r))
-                                return;
-
-                        /* fall through to deep modeset */
-                } else {
-                        if (!crtc->applied) {
-                                log_debug("grdrm: %s: crtc %" PRIu32 " applied via page flip",
-                                          card->base.name, crtc->object.id);
-                                crtc->applied = true;
-                        }
-
-                        pipe->base.flipping = true;
-                        pipe->counter = cnt;
-                        fb->flipid = cnt;
-                        *slot = NULL;
-
-                        if (!pipe->base.back) {
-                                for (i = 0; i < pipe->base.max_fbs; ++i) {
-                                        if (!pipe->base.fbs[i])
-                                                continue;
-
-                                        fb = fb_from_base(pipe->base.fbs[i]);
-                                        if (&fb->base == pipe->base.front)
-                                                continue;
-                                        if (fb->flipid)
-                                                continue;
-
-                                        pipe->base.back = &fb->base;
-                                        break;
-                                }
-                        }
-                }
+        r = grdrm_crtc_commit_flip(crtc, fb);
+        if (r == 0) {
+                /* in case we couldn't page-flip, perform deep modeset */
+                grdrm_crtc_commit_deep(crtc, fb);
         }
-
-        if (!crtc->applied) {
-                set_crtc.set_connectors_ptr = PTR_TO_UINT64(crtc->set.connectors);
-                set_crtc.count_connectors = crtc->set.n_connectors;
-                set_crtc.fb_id = fb->id;
-                set_crtc.x = 0;
-                set_crtc.y = 0;
-                set_crtc.mode_valid = 1;
-                set_crtc.mode = crtc->set.mode;
-
-                r = ioctl(card->fd, DRM_IOCTL_MODE_SETCRTC, &set_crtc);
-                if (r < 0) {
-                        r = -errno;
-                        log_debug("grdrm: %s: cannot set crtc %" PRIu32 ": %m",
-                                  card->base.name, crtc->object.id);
-
-                        grdrm_card_async(card, r);
-                        return;
-                }
-
-                if (!crtc->applied) {
-                        log_debug("grdrm: %s: crtc %" PRIu32 " applied via deep modeset",
-                                  card->base.name, crtc->object.id);
-                        crtc->applied = true;
-                }
-
-                *slot = NULL;
-                pipe->base.front = &fb->base;
-                fb->flipid = 0;
-                ++pipe->counter;
-                pipe->base.flipping = false;
-
-                if (!pipe->base.back) {
-                        for (i = 0; i < pipe->base.max_fbs; ++i) {
-                                if (!pipe->base.fbs[i])
-                                        continue;
-
-                                fb = fb_from_base(pipe->base.fbs[i]);
-                                if (&fb->base == pipe->base.front)
-                                        continue;
-
-                                fb->flipid = 0;
-                                pipe->base.back = &fb->base;
-                                break;
-                        }
-                }
-        }
-
-        pipe->base.flip = false;
 }
 
 static void grdrm_crtc_restore(grdrm_crtc *crtc) {
@@ -1259,8 +1306,8 @@ static void grdrm_crtc_restore(grdrm_crtc *crtc) {
         r = ioctl(card->fd, DRM_IOCTL_MODE_SETCRTC, &set_crtc);
         if (r < 0) {
                 r = -errno;
-                log_debug("grdrm: %s: cannot restore crtc %" PRIu32 ": %m",
-                          card->base.name, crtc->object.id);
+                log_debug_errno(errno, "grdrm: %s: cannot restore crtc %" PRIu32 ": %m",
+                                card->base.name, crtc->object.id);
 
                 grdrm_card_async(card, r);
                 return;
@@ -1278,7 +1325,6 @@ static void grdrm_crtc_restore(grdrm_crtc *crtc) {
 static void grdrm_crtc_flip_complete(grdrm_crtc *crtc, uint32_t counter, struct drm_event_vblank *event) {
         bool flipped = false;
         grdrm_pipe *pipe;
-        grdrm_fb *back = NULL;
         size_t i;
 
         assert(crtc);
@@ -1305,20 +1351,13 @@ static void grdrm_crtc_flip_complete(grdrm_crtc *crtc, uint32_t counter, struct
                 fb = fb_from_base(pipe->base.fbs[i]);
                 if (counter != 0 && counter == pipe->counter && fb->flipid == counter) {
                         pipe->base.front = &fb->base;
+                        fb->flipid = 0;
                         flipped = true;
-                }
-
-                if (counter - fb->flipid < UINT16_MAX) {
+                } else if (counter - fb->flipid < UINT16_MAX) {
                         fb->flipid = 0;
-                        back = fb;
-                } else if (fb->flipid == 0) {
-                        back = fb;
                 }
         }
 
-        if (!pipe->base.back)
-                pipe->base.back = &back->base;
-
         if (flipped) {
                 crtc->pipe->base.flipping = false;
                 grdev_pipe_frame(&pipe->base);
@@ -1362,9 +1401,9 @@ static int grdrm_fb_new(grdrm_fb **out, grdrm_card *card, const struct drm_mode_
 
         r = ioctl(card->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb);
         if (r < 0) {
-                r = -errno;
-                log_debug("grdrm: %s: cannot create dumb buffer %" PRIu32 "x%" PRIu32": %m",
-                          card->base.name, fb->base.width, fb->base.height);
+                r = negative_errno();
+                log_debug_errno(errno, "grdrm: %s: cannot create dumb buffer %" PRIu32 "x%" PRIu32": %m",
+                                card->base.name, fb->base.width, fb->base.height);
                 return r;
         }
 
@@ -1376,17 +1415,17 @@ static int grdrm_fb_new(grdrm_fb **out, grdrm_card *card, const struct drm_mode_
 
         r = ioctl(card->fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb);
         if (r < 0) {
-                r = -errno;
-                log_debug("grdrm: %s: cannot map dumb buffer %" PRIu32 "x%" PRIu32": %m",
-                          card->base.name, fb->base.width, fb->base.height);
+                r = negative_errno();
+                log_debug_errno(errno, "grdrm: %s: cannot map dumb buffer %" PRIu32 "x%" PRIu32": %m",
+                                card->base.name, fb->base.width, fb->base.height);
                 return r;
         }
 
         fb->base.maps[0] = mmap(0, fb->sizes[0], PROT_WRITE, MAP_SHARED, card->fd, map_dumb.offset);
         if (fb->base.maps[0] == MAP_FAILED) {
-                r = -errno;
-                log_debug("grdrm: %s: cannot memory-map dumb buffer %" PRIu32 "x%" PRIu32": %m",
-                          card->base.name, fb->base.width, fb->base.height);
+                r = negative_errno();
+                log_debug_errno(errno, "grdrm: %s: cannot memory-map dumb buffer %" PRIu32 "x%" PRIu32": %m",
+                                card->base.name, fb->base.width, fb->base.height);
                 return r;
         }
 
@@ -1402,9 +1441,9 @@ static int grdrm_fb_new(grdrm_fb **out, grdrm_card *card, const struct drm_mode_
 
         r = ioctl(card->fd, DRM_IOCTL_MODE_ADDFB2, &add_fb);
         if (r < 0) {
-                r = -errno;
-                log_debug("grdrm: %s: cannot add framebuffer %" PRIu32 "x%" PRIu32": %m",
-                          card->base.name, fb->base.width, fb->base.height);
+                r = negative_errno();
+                log_debug_errno(errno, "grdrm: %s: cannot add framebuffer %" PRIu32 "x%" PRIu32": %m",
+                                card->base.name, fb->base.width, fb->base.height);
                 return r;
         }
 
@@ -1417,14 +1456,22 @@ static int grdrm_fb_new(grdrm_fb **out, grdrm_card *card, const struct drm_mode_
 
 grdrm_fb *grdrm_fb_free(grdrm_fb *fb) {
         unsigned int i;
+        int r;
 
         if (!fb)
                 return NULL;
 
         assert(fb->card);
 
-        if (fb->id > 0 && fb->card->fd >= 0)
-                ioctl(fb->card->fd, DRM_IOCTL_MODE_RMFB, fb->id);
+        if (fb->base.free_fn)
+                fb->base.free_fn(fb->base.data.ptr);
+
+        if (fb->id > 0 && fb->card->fd >= 0) {
+                r = ioctl(fb->card->fd, DRM_IOCTL_MODE_RMFB, fb->id);
+                if (r < 0)
+                        log_debug_errno(errno, "grdrm: %s: cannot delete framebuffer %" PRIu32 ": %m",
+                                        fb->card->base.name, fb->id);
+        }
 
         for (i = 0; i < ELEMENTSOF(fb->handles); ++i) {
                 struct drm_mode_destroy_dumb destroy_dumb = { };
@@ -1434,7 +1481,10 @@ grdrm_fb *grdrm_fb_free(grdrm_fb *fb) {
 
                 if (fb->handles[i] > 0 && fb->card->fd >= 0) {
                         destroy_dumb.handle = fb->handles[i];
-                        ioctl(fb->card->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_dumb);
+                        r = ioctl(fb->card->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_dumb);
+                        if (r < 0)
+                                log_debug_errno(errno, "grdrm: %s: cannot destroy dumb-buffer %" PRIu32 ": %m",
+                                                fb->card->base.name, fb->handles[i]);
                 }
         }
 
@@ -1471,6 +1521,7 @@ static int grdrm_pipe_new(grdrm_pipe **out, grdrm_crtc *crtc, struct drm_mode_mo
         pipe->crtc = crtc;
         pipe->base.width = mode->hdisplay;
         pipe->base.height = mode->vdisplay;
+        pipe->base.vrefresh = mode->vrefresh ? : 25;
 
         grdrm_pipe_name(name, crtc);
         r = grdev_pipe_add(&pipe->base, name, n_fbs);
@@ -1496,8 +1547,46 @@ static void grdrm_pipe_free(grdev_pipe *basepipe) {
         free(pipe);
 }
 
+static grdev_fb *grdrm_pipe_target(grdev_pipe *basepipe) {
+        grdrm_fb *fb;
+        size_t i;
+
+        if (!basepipe->back) {
+                for (i = 0; i < basepipe->max_fbs; ++i) {
+                        if (!basepipe->fbs[i])
+                                continue;
+
+                        fb = fb_from_base(basepipe->fbs[i]);
+                        if (&fb->base == basepipe->front)
+                                continue;
+                        if (basepipe->flipping && fb->flipid)
+                                continue;
+
+                        basepipe->back = &fb->base;
+                        break;
+                }
+        }
+
+        return basepipe->back;
+}
+
+static void grdrm_pipe_enable(grdev_pipe *basepipe) {
+        grdrm_pipe *pipe = grdrm_pipe_from_base(basepipe);
+
+        pipe->crtc->applied = false;
+}
+
+static void grdrm_pipe_disable(grdev_pipe *basepipe) {
+        grdrm_pipe *pipe = grdrm_pipe_from_base(basepipe);
+
+        pipe->crtc->applied = false;
+}
+
 static const grdev_pipe_vtable grdrm_pipe_vtable = {
         .free                   = grdrm_pipe_free,
+        .target                 = grdrm_pipe_target,
+        .enable                 = grdrm_pipe_enable,
+        .disable                = grdrm_pipe_disable,
 };
 
 /*
@@ -1696,7 +1785,8 @@ static int grdrm_card_resync(grdrm_card *card) {
                 r = ioctl(card->fd, DRM_IOCTL_MODE_GETRESOURCES, &res);
                 if (r < 0) {
                         r = -errno;
-                        log_debug("grdrm: %s: cannot retrieve drm resources: %m", card->base.name);
+                        log_debug_errno(errno, "grdrm: %s: cannot retrieve drm resources: %m",
+                                        card->base.name);
                         return r;
                 }
 
@@ -1707,7 +1797,8 @@ static int grdrm_card_resync(grdrm_card *card) {
                 r = ioctl(card->fd, DRM_IOCTL_MODE_GETPLANERESOURCES, &pres);
                 if (r < 0) {
                         r = -errno;
-                        log_debug("grdrm: %s: cannot retrieve drm plane-resources: %m", card->base.name);
+                        log_debug_errno(errno, "grdrm: %s: cannot retrieve drm plane-resources: %m",
+                                        card->base.name);
                         return r;
                 }
 
@@ -1718,7 +1809,8 @@ static int grdrm_card_resync(grdrm_card *card) {
 
                         n = ALIGN_POWER2(max);
                         if (!n || n > UINT16_MAX) {
-                                log_debug("grdrm: %s: excessive DRM resource limit: %" PRIu32, card->base.name, max);
+                                log_debug("grdrm: %s: excessive DRM resource limit: %" PRIu32,
+                                          card->base.name, max);
                                 return -ERANGE;
                         }
 
@@ -2096,20 +2188,29 @@ static void grdrm_card_hotplug(grdrm_card *card) {
         int r;
 
         assert(card);
-        assert(!card->ready);
 
+        if (!card->running)
+                return;
+
+        log_debug("grdrm: %s/%s: reconfigure card", card->base.session->name, card->base.name);
+
+        card->ready = false;
         r = grdrm_card_resync(card);
         if (r < 0) {
-                log_debug("grdrm: %s/%s: cannot re-sync card: %s",
-                          card->base.session->name, card->base.name, strerror(-r));
+                log_debug_errno(r, "grdrm: %s/%s: cannot re-sync card: %m",
+                                card->base.session->name, card->base.name);
                 return;
         }
 
         grdev_session_pin(card->base.session);
 
-        grdrm_card_print(card);
+        /* debug statement to print card information */
+        if (0)
+                grdrm_card_print(card);
+
         grdrm_card_configure(card);
         card->ready = true;
+        card->hotplug = false;
 
         grdev_session_unpin(card->base.session);
 }
@@ -2121,7 +2222,8 @@ static int grdrm_card_io_fn(sd_event_source *s, int fd, uint32_t revents, void *
         uint32_t id, counter;
         grdrm_object *object;
         char buf[4096];
-        ssize_t l, i;
+        size_t len;
+        ssize_t l;
 
         if (revents & (EPOLLHUP | EPOLLERR)) {
                 /* Immediately close device on HUP; no need to flush pending
@@ -2137,19 +2239,18 @@ static int grdrm_card_io_fn(sd_event_source *s, int fd, uint32_t revents, void *
                         if (errno == EAGAIN || errno == EINTR)
                                 return 0;
 
-                        log_debug("grdrm: %s/%s: read error: %m", card->base.session->name, card->base.name);
+                        log_debug_errno(errno, "grdrm: %s/%s: read error: %m",
+                                        card->base.session->name, card->base.name);
                         grdrm_card_close(card);
                         return 0;
-                } else if ((size_t)l < sizeof(*event)) {
-                        log_debug("grdrm: %s/%s: short read of %zd bytes", card->base.session->name, card->base.name, l);
-                        return 0;
                 }
 
-                for (i = 0; i < l; i += event->length) {
-                        event = (void*)&buf[i];
+                for (len = l; len > 0; len -= event->length) {
+                        event = (void*)buf;
 
-                        if (i + event->length > l) {
-                                log_debug("grdrm: %s/%s: truncated event", card->base.session->name, card->base.name);
+                        if (len < sizeof(*event) || len < event->length) {
+                                log_debug("grdrm: %s/%s: truncated event",
+                                          card->base.session->name, card->base.name);
                                 break;
                         }
 
@@ -2157,7 +2258,8 @@ static int grdrm_card_io_fn(sd_event_source *s, int fd, uint32_t revents, void *
                         case DRM_EVENT_FLIP_COMPLETE:
                                 vblank = (void*)event;
                                 if (event->length < sizeof(*vblank)) {
-                                        log_debug("grdrm: %s/%s: truncated vblank event", card->base.session->name, card->base.name);
+                                        log_debug("grdrm: %s/%s: truncated vblank event",
+                                                  card->base.session->name, card->base.name);
                                         break;
                                 }
 
@@ -2317,6 +2419,7 @@ static int grdrm_card_open(grdrm_card *card, int dev_fd) {
 
         sd_event_source_set_enabled(card->fd_src, SD_EVENT_OFF);
 
+        card->hotplug = true;
         card->fd = fd;
         fd = -1;
 
@@ -2326,8 +2429,8 @@ static int grdrm_card_open(grdrm_card *card, int dev_fd) {
         r = ioctl(card->fd, DRM_IOCTL_GET_CAP, &cap);
         card->cap_dumb = r >= 0 && cap.value;
         if (r < 0)
-                log_debug("grdrm: %s/%s: cannot retrieve DUMB_BUFFER capability: %s",
-                          card->base.session->name, card->base.name, strerror(-r));
+                log_debug_errno(r, "grdrm: %s/%s: cannot retrieve DUMB_BUFFER capability: %m",
+                                card->base.session->name, card->base.name);
         else if (!card->cap_dumb)
                 log_debug("grdrm: %s/%s: DUMB_BUFFER capability not supported",
                           card->base.session->name, card->base.name);
@@ -2338,8 +2441,8 @@ static int grdrm_card_open(grdrm_card *card, int dev_fd) {
         r = ioctl(card->fd, DRM_IOCTL_GET_CAP, &cap);
         card->cap_monotonic = r >= 0 && cap.value;
         if (r < 0)
-                log_debug("grdrm: %s/%s: cannot retrieve TIMESTAMP_MONOTONIC capability: %s",
-                          card->base.session->name, card->base.name, strerror(-r));
+                log_debug_errno(r, "grdrm: %s/%s: cannot retrieve TIMESTAMP_MONOTONIC capability: %m",
+                                card->base.session->name, card->base.name);
         else if (!card->cap_monotonic)
                 log_debug("grdrm: %s/%s: TIMESTAMP_MONOTONIC is disabled globally, fix this NOW!",
                           card->base.session->name, card->base.name);
@@ -2409,8 +2512,8 @@ static void unmanaged_card_enable(grdev_card *basecard) {
                 fd = open(cu->devnode, O_RDWR | O_CLOEXEC | O_NOCTTY | O_NONBLOCK);
                 if (fd < 0) {
                         /* not fatal; simply ignore the device */
-                        log_debug("grdrm: %s/%s: cannot open node %s: %m",
-                                  basecard->session->name, basecard->name, cu->devnode);
+                        log_debug_errno(errno, "grdrm: %s/%s: cannot open node %s: %m",
+                                        basecard->session->name, basecard->name, cu->devnode);
                         return;
                 }
 
@@ -2418,16 +2521,16 @@ static void unmanaged_card_enable(grdev_card *basecard) {
 
                 r = grdrm_card_open(&cu->card, fd);
                 if (r < 0) {
-                        log_debug("grdrm: %s/%s: cannot open: %s",
-                                  basecard->session->name, basecard->name, strerror(-r));
+                        log_debug_errno(r, "grdrm: %s/%s: cannot open: %m",
+                                        basecard->session->name, basecard->name);
                         return;
                 }
         }
 
         r = ioctl(cu->card.fd, DRM_IOCTL_SET_MASTER, 0);
         if (r < 0) {
-                log_debug("grdrm: %s/%s: cannot acquire DRM-Master: %m",
-                          basecard->session->name, basecard->name);
+                log_debug_errno(errno, "grdrm: %s/%s: cannot acquire DRM-Master: %m",
+                                basecard->session->name, basecard->name);
                 return;
         }
 
@@ -2477,17 +2580,22 @@ static int unmanaged_card_new(grdev_card **out, grdev_session *session, struct u
         fd = open(cu->devnode, O_RDWR | O_CLOEXEC | O_NOCTTY | O_NONBLOCK);
         if (fd < 0) {
                 /* not fatal; allow uaccess based control on activation */
-                log_debug("grdrm: %s/%s: cannot open node %s: %m",
-                          basecard->session->name, basecard->name, cu->devnode);
+                log_debug_errno(errno, "grdrm: %s/%s: cannot open node %s: %m",
+                                basecard->session->name, basecard->name, cu->devnode);
         } else {
                 /* We might get DRM-Master implicitly on open(); drop it immediately
-                 * so we acquire it only once we're actually enabled. */
-                ioctl(fd, DRM_IOCTL_DROP_MASTER, 0);
+                 * so we acquire it only once we're actually enabled. We don't
+                 * really care whether this call fails or not, but lets log any
+                 * weird errors, anyway. */
+                r = ioctl(fd, DRM_IOCTL_DROP_MASTER, 0);
+                if (r < 0 && errno != EACCES && errno != EINVAL)
+                        log_debug_errno(errno, "grdrm: %s/%s: cannot drop DRM-Master: %m",
+                                        basecard->session->name, basecard->name);
 
                 r = grdrm_card_open(&cu->card, fd);
                 if (r < 0)
-                        log_debug("grdrm: %s/%s: cannot open: %s",
-                                  basecard->session->name, basecard->name, strerror(-r));
+                        log_debug_errno(r, "grdrm: %s/%s: cannot open: %m",
+                                        basecard->session->name, basecard->name);
         }
 
         if (out)
@@ -2630,8 +2738,8 @@ static int managed_card_pause_device_fn(sd_bus *bus,
                 }
 
                 if (r < 0)
-                        log_debug("grdrm: %s/%s: cannot send PauseDeviceComplete: %s",
-                                  session->name, cm->card.base.name, strerror(-r));
+                        log_debug_errno(r, "grdrm: %s/%s: cannot send PauseDeviceComplete: %m",
+                                        session->name, cm->card.base.name);
         }
 
         return 0;
@@ -2677,15 +2785,15 @@ static int managed_card_resume_device_fn(sd_bus *bus,
                  * and our code works fine this way. */
                 fd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
                 if (fd < 0) {
-                        log_debug("grdrm: %s/%s: cannot duplicate fd: %m",
-                                  session->name, cm->card.base.name);
+                        log_debug_errno(errno, "grdrm: %s/%s: cannot duplicate fd: %m",
+                                        session->name, cm->card.base.name);
                         return 0;
                 }
 
                 r = grdrm_card_open(&cm->card, fd);
                 if (r < 0) {
-                        log_debug("grdrm: %s/%s: cannot open: %s",
-                                  session->name, cm->card.base.name, strerror(-r));
+                        log_debug_errno(r, "grdrm: %s/%s: cannot open: %m",
+                                        session->name, cm->card.base.name);
                         return 0;
                 }
         }
@@ -2769,15 +2877,15 @@ static int managed_card_take_device_fn(sd_bus *bus,
 
         fd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
         if (fd < 0) {
-                log_debug("grdrm: %s/%s: cannot duplicate fd: %m",
-                          session->name, cm->card.base.name);
+                log_debug_errno(errno, "grdrm: %s/%s: cannot duplicate fd: %m",
+                                session->name, cm->card.base.name);
                 return 0;
         }
 
         r = grdrm_card_open(&cm->card, fd);
         if (r < 0) {
-                log_debug("grdrm: %s/%s: cannot open: %s",
-                          session->name, cm->card.base.name, strerror(-r));
+                log_debug_errno(r, "grdrm: %s/%s: cannot open: %m",
+                                session->name, cm->card.base.name);
                 return 0;
         }
 
@@ -2818,8 +2926,8 @@ static void managed_card_take_device(managed_card *cm) {
         return;
 
 error:
-        log_debug("grdrm: %s/%s: cannot send TakeDevice request: %s",
-                  session->name, cm->card.base.name, strerror(-r));
+        log_debug_errno(r, "grdrm: %s/%s: cannot send TakeDevice request: %m",
+                        session->name, cm->card.base.name);
 }
 
 static void managed_card_release_device(managed_card *cm) {
@@ -2861,8 +2969,8 @@ static void managed_card_release_device(managed_card *cm) {
         }
 
         if (r < 0 && r != -ENOTCONN)
-                log_debug("grdrm: %s/%s: cannot send ReleaseDevice: %s",
-                          session->name, cm->card.base.name, strerror(-r));
+                log_debug_errno(r, "grdrm: %s/%s: cannot send ReleaseDevice: %m",
+                                session->name, cm->card.base.name);
 }
 
 static int managed_card_new(grdev_card **out, grdev_session *session, struct udev_device *ud) {
@@ -2955,3 +3063,34 @@ int grdev_drm_card_new(grdev_card **out, grdev_session *session, struct udev_dev
 
         return session->managed ? managed_card_new(out, session, ud) : unmanaged_card_new(out, session, ud);
 }
+
+void grdev_drm_card_hotplug(grdev_card *basecard, struct udev_device *ud) {
+        const char *p, *action;
+        grdrm_card *card;
+        dev_t devnum;
+
+        assert(basecard);
+        assert(grdev_is_drm_card(basecard));
+        assert(ud);
+
+        card = grdrm_card_from_base(basecard);
+
+        action = udev_device_get_action(ud);
+        if (!action || streq(action, "add") || streq(action, "remove")) {
+                /* If we get add/remove events on DRM nodes without devnum, we
+                 * got hotplugged DRM objects so refresh the device. */
+                devnum = udev_device_get_devnum(ud);
+                if (devnum == 0) {
+                        card->hotplug = true;
+                        grdrm_card_hotplug(card);
+                }
+        } else if (streq_ptr(action, "change")) {
+                /* A change event with HOTPLUG=1 is sent whenever a connector
+                 * changed state. Refresh the device to update our state. */
+                p = udev_device_get_property_value(ud, "HOTPLUG");
+                if (streq_ptr(p, "1")) {
+                        card->hotplug = true;
+                        grdrm_card_hotplug(card);
+                }
+        }
+}