X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=src%2Flibsystemd-terminal%2Fgrdev-drm.c;h=bc4d4c9e76d4d769108cd85321c576536ad0cb16;hb=b57b06258e0b1894edb6d1fc52a80b3c33164892;hp=3481584fbf760bc4e3331b9a2e76a6c4b0e5240d;hpb=f22e0bce3732c1fd005b7a886042394e036bc1b3;p=elogind.git diff --git a/src/libsystemd-terminal/grdev-drm.c b/src/libsystemd-terminal/grdev-drm.c index 3481584fb..bc4d4c9e7 100644 --- a/src/libsystemd-terminal/grdev-drm.c +++ b/src/libsystemd-terminal/grdev-drm.c @@ -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; } @@ -1968,7 +2060,7 @@ static void grdrm_card_configure(grdrm_card *card) { * headache to configure on dynamic demands. Therefore, we only * support it if configured statically beforehand. * - * * CRTCs are not created equal. Some might be much more poweful + * * CRTCs are not created equal. Some might be much more powerful * than others, including more advanced plane support. So far, our * CRTC selection is random. You need to supply static * configuration if you want special setups. So far, there is no @@ -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); + } + } +}