grdev-drm.c revision fc08079ef25a063f56be48b87035f8fde79153ba
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com>
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 <fcntl.h>
#include <inttypes.h>
#include <libudev.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
/* Yuck! DRM headers need system headers included first.. but we have to
#include <drm.h>
#include <drm_fourcc.h>
#include <drm_mode.h>
#include "sd-bus.h"
#include "sd-event.h"
#include "hashmap.h"
#include "macro.h"
#include "util.h"
#include "bus-util.h"
#include "grdev.h"
#include "grdev-internal.h"
#define GRDRM_MAX_TRIES (16)
typedef struct grdrm_object grdrm_object;
typedef struct grdrm_plane grdrm_plane;
typedef struct grdrm_connector grdrm_connector;
typedef struct grdrm_encoder grdrm_encoder;
typedef struct grdrm_crtc grdrm_crtc;
typedef struct grdrm_pipe grdrm_pipe;
typedef struct grdrm_card grdrm_card;
typedef struct unmanaged_card unmanaged_card;
typedef struct managed_card managed_card;
/*
* Objects
*/
enum {
};
struct grdrm_object {
unsigned int type;
bool present : 1;
bool assigned : 1;
};
struct grdrm_plane {
struct {
} kern;
};
struct grdrm_connector {
struct {
struct drm_mode_modeinfo *modes;
} kern;
};
struct grdrm_encoder {
struct {
} kern;
};
struct grdrm_crtc {
struct {
bool mode_set;
struct drm_mode_modeinfo mode;
} kern;
struct {
bool set;
bool mode_set;
struct drm_mode_modeinfo mode;
} old;
struct {
struct drm_mode_modeinfo mode;
} set;
bool applied : 1;
};
})
/*
* Framebuffers
*/
struct grdrm_fb {
};
/*
* Pipes
*/
struct grdrm_pipe {
};
static const grdev_pipe_vtable grdrm_pipe_vtable;
static int grdrm_pipe_new(grdrm_pipe **out, grdrm_crtc *crtc, struct drm_mode_modeinfo *mode, size_t n_fbs);
/*
* Cards
*/
struct grdrm_card {
int fd;
bool async_hotplug : 1;
bool hotplug : 1;
bool running : 1;
bool ready : 1;
bool cap_dumb : 1;
bool cap_monotonic : 1;
};
struct unmanaged_card {
char *devnode;
};
struct managed_card {
};
#define unmanaged_card_from_base(_e) \
#define managed_card_from_base(_e) \
.fd = -1, \
.max_ids = 32, \
})
static const grdev_card_vtable unmanaged_card_vtable;
static const grdev_card_vtable managed_card_vtable;
/*
* The page-flip event of the kernel provides 64bit of arbitrary user-data. As
* drivers tend to drop events on intermediate deep mode-sets or because we
* might receive events during session activation, we try to avoid allocaing
* dynamic data on those events. Instead, we safe the CRTC id plus a 32bit
* counter in there. This way, we only get 32bit counters, not 64bit, but that
* should be more than enough. On the bright side, we no longer care whether we
* lose events. No memory leaks will occur.
* Modern DRM drivers might be fixed to no longer leak events, but we want to
* be safe. And associating dynamically allocated data with those events is
* kinda ugly, anyway.
*/
}
if (out_id)
if (out_counter)
}
static bool grdrm_modes_compatible(const struct drm_mode_modeinfo *a, const struct drm_mode_modeinfo *b) {
assert(a);
assert(b);
/* Test whether both modes are compatible according to our internal
* assumptions on modes. This comparison is highly dependent on how
* we treat modes in grdrm. If we export mode details, we need to
* make this comparison much stricter. */
return false;
return false;
return false;
return true;
}
/*
* Objects
*/
}
int r;
assert(IN_SET(object->type, GRDRM_TYPE_CRTC, GRDRM_TYPE_ENCODER, GRDRM_TYPE_CONNECTOR, GRDRM_TYPE_PLANE));
if (r < 0)
return r;
return 0;
}
if (!object)
return NULL;
assert(IN_SET(object->type, GRDRM_TYPE_CRTC, GRDRM_TYPE_ENCODER, GRDRM_TYPE_CONNECTOR, GRDRM_TYPE_PLANE));
return NULL;
}
/*
* Planes
*/
}
int r;
if (!plane)
return -ENOMEM;
return -ENOMEM;
return -ENOMEM;
r = grdrm_object_add(object);
if (r < 0)
return r;
if (out)
return 0;
}
int r;
struct drm_mode_get_plane res;
bool resized = false;
if (r < 0) {
r = -errno;
if (r == -ENOENT) {
card->async_hotplug = true;
r = 0;
log_debug("grdrm: %s: plane %u removed during resync",
} else {
}
return r;
}
continue;
continue;
continue;
}
}
return -ERANGE;
}
if (!t)
return -ENOMEM;
resized = true;
}
if (resized)
continue;
break;
}
if (tries >= GRDRM_MAX_TRIES) {
return -EFAULT;
}
return 0;
}
/*
* Connectors
*/
}
int r;
if (!connector)
return -ENOMEM;
return -ENOMEM;
return -ENOMEM;
return -ENOMEM;
r = grdrm_object_add(object);
if (r < 0)
return r;
if (out)
return 0;
}
int r;
struct drm_mode_get_connector res;
bool resized = false;
/* 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 (tries > 0) {
} else {
resized = true;
}
}
if (r < 0) {
r = -errno;
if (r == -ENOENT) {
card->async_hotplug = true;
r = 0;
log_debug("grdrm: %s: connector %u removed during resync",
} else {
}
return r;
}
uint32_t *t;
return -ERANGE;
}
if (!t)
return -ENOMEM;
resized = true;
}
struct drm_mode_modeinfo *t;
return -ERANGE;
}
if (!t)
return -ENOMEM;
resized = true;
}
return -ERANGE;
}
if (!tids)
return -ENOMEM;
if (!tvals)
return -ENOMEM;
resized = true;
}
if (resized)
continue;
break;
}
if (tries >= GRDRM_MAX_TRIES) {
log_debug("grdrm: %s: connector %u not settled for retrieval", card->base.name, connector->object.id);
return -EFAULT;
}
return 0;
}
/*
* Encoders
*/
}
int r;
if (!encoder)
return -ENOMEM;
return -ENOMEM;
return -ENOMEM;
r = grdrm_object_add(object);
if (r < 0)
return r;
if (out)
return 0;
}
struct drm_mode_get_encoder res;
int r;
if (r < 0) {
r = -errno;
if (r == -ENOENT) {
card->async_hotplug = true;
r = 0;
log_debug("grdrm: %s: encoder %u removed during resync",
} else {
}
return r;
}
continue;
continue;
continue;
}
}
continue;
continue;
continue;
}
}
return 0;
}
/*
* Crtcs
*/
}
int r;
if (!crtc)
return -ENOMEM;
return -ENOMEM;
return -ENOMEM;
r = grdrm_object_add(object);
if (r < 0)
return r;
if (out)
return 0;
}
int r;
/* make sure we can cache any combination later */
if (!max)
return -ENOMEM;
if (!t)
return -ENOMEM;
return -ENOMEM;
}
}
/* GETCRTC doesn't return connectors. We have to read all
* encoder-state and deduce the setup ourselves.. */
if (r < 0) {
r = -errno;
if (r == -ENOENT) {
card->async_hotplug = true;
r = 0;
log_debug("grdrm: %s: crtc %u removed during resync",
} else {
}
return r;
}
return 0;
}
int r;
/* always mark both as assigned; even if assignments cannot be set */
if (connector)
/* we will support hw clone mode in the future */
/* bail out if configuration is preserved */
return;
if (!max) {
r = -ENOMEM;
goto error;
}
if (!t) {
r = -ENOMEM;
goto error;
}
}
if (connector) {
uint32_t i;
/* ignore 3D modes by default */
if (m->flags & DRM_MODE_FLAG_3D_MASK)
continue;
if (!pref) {
pref = m;
continue;
}
/* use PREFERRED over non-PREFERRED */
!(m->type & DRM_MODE_TYPE_PREFERRED))
continue;
/* use DRIVER over non-PREFERRED|DRIVER */
continue;
/* always prefer higher resolution */
continue;
pref = m;
}
if (pref) {
} else {
}
}
return;
}
size_t i;
int r;
return;
}
if (pipe) {
}
}
}
} else {
if (r < 0) {
return;
}
if (r < 0) {
return;
}
}
}
}
int r;
set_crtc.x = 0;
set_crtc.y = 0;
if (r < 0) {
r = -errno;
grdrm_card_async(card, r);
return;
}
}
/* 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. */
}
int r;
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;
}
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)
if (grdrm_card_async(card, r))
return r;
return 0;
}
}
/* 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. */
return 1;
}
int r;
if (!pipe) {
/* If a crtc is not assigned any connector, we want any
* previous setup to be cleared, so make sure the CRTC is
* disabled. Otherwise, there might be content on the CRTC
* while we run, which is not what we want.
* If you want to avoid modesets on specific CRTCs, you should
* still keep their assignment, but never enable the resulting
* pipe. This way, we wouldn't touch it at all. */
if (r < 0) {
r = -errno;
grdrm_card_async(card, r);
return;
}
}
return;
}
/* we always fully ignore disabled pipes */
return;
else
return;
if (!fb)
return;
if (r == 0) {
/* in case we couldn't page-flip, perform deep modeset */
}
}
int r;
return;
if (r < 0) {
r = -errno;
grdrm_card_async(card, r);
return;
}
}
}
static void grdrm_crtc_flip_complete(grdrm_crtc *crtc, uint32_t counter, struct drm_event_vblank *event) {
bool flipped = false;
size_t i;
if (!pipe)
return;
/* We got a page-flip event. To be safe, we reset all FBs on the same
* pipe that have smaller flipids than the flip we got as we know they
* are executed in order. We need to do this to guarantee
* queue-overflows or other missed events don't cause starvation.
* Furthermore, if we find the exact FB this event is for, *and* this
* is the most recent event, we mark it as front FB and raise a
* frame event. */
continue;
flipped = true;
}
}
if (flipped) {
}
}
/*
* Framebuffers
*/
struct drm_mode_create_dumb create_dumb = { };
struct drm_mode_map_dumb map_dumb = { };
struct drm_mode_fb_cmd2 add_fb = { };
unsigned int i;
int r;
if (!fb)
return -ENOMEM;
/* TODO: we should choose a compatible format of the previous CRTC
* setting to allow page-flip to it. Only choose fallback if the
* previous setting was crap (non xrgb32'ish). */
if (r < 0) {
r = negative_errno();
return r;
}
if (r < 0) {
r = negative_errno();
return r;
}
r = negative_errno();
return r;
}
if (r < 0) {
r = negative_errno();
return r;
}
return 0;
}
unsigned int i;
int r;
if (!fb)
return NULL;
if (r < 0)
}
struct drm_mode_destroy_dumb destroy_dumb = { };
if (r < 0)
}
}
return NULL;
}
/*
* Pipes
*/
/* @out must be at least of size GRDRM_PIPE_NAME_MAX */
}
static int grdrm_pipe_new(grdrm_pipe **out, grdrm_crtc *crtc, struct drm_mode_modeinfo *mode, size_t n_fbs) {
char name[GRDRM_PIPE_NAME_MAX];
int r;
if (!pipe)
return -ENOMEM;
if (r < 0)
return r;
if (out)
return 0;
}
size_t i;
}
size_t i;
continue;
continue;
continue;
break;
}
}
}
}
}
static const grdev_pipe_vtable grdrm_pipe_vtable = {
.free = grdrm_pipe_free,
};
/*
* Cards
*/
/* @out must be at least of size GRDRM_CARD_NAME_MAX */
}
uint32_t i;
char *p, *buf;
log_debug(" crtcs:");
continue;
else
log_debug(" mode: <none>");
}
log_debug(" encoders:");
continue;
else
log_debug(" crtc: <none>");
if (buf) {
buf[0] = 0;
p = buf;
}
if (buf) {
buf[0] = 0;
p = buf;
}
}
log_debug(" connectors:");
continue;
log_debug(" type: %" PRIu32 "-%" PRIu32 " connection: %" PRIu32 " subpixel: %" PRIu32 " extents: %" PRIu32 "x%" PRIu32,
connector->kern.type, connector->kern.type_id, connector->kern.connection, connector->kern.subpixel,
else
log_debug(" encoder: <none>");
if (buf) {
buf[0] = 0;
p = buf;
}
}
}
log_debug(" planes:");
continue;
else
log_debug(" crtc: <none>");
if (buf) {
buf[0] = 0;
p = buf;
}
if (buf) {
buf[0] = 0;
p = buf;
}
}
}
_cleanup_free_ uint32_t *crtc_ids = NULL, *encoder_ids = NULL, *connector_ids = NULL, *plane_ids = NULL;
int r;
card->async_hotplug = false;
allocated = 0;
/* mark existing objects for possible removal */
struct drm_mode_get_plane_res pres;
struct drm_mode_card_res res;
return -ENOMEM;
}
if (r < 0) {
r = -errno;
return r;
}
if (r < 0) {
r = -errno;
return r;
}
uint32_t n;
n = ALIGN_POWER2(max);
if (!n || n > UINT16_MAX) {
return -ERANGE;
}
/* retry with resized buffers */
continue;
}
/* mark available objects as present */
for (i = 0; i < res.count_crtcs; ++i) {
crtc_ids[i] = 0;
}
}
for (i = 0; i < res.count_encoders; ++i) {
encoder_ids[i] = 0;
}
}
for (i = 0; i < res.count_connectors; ++i) {
connector_ids[i] = 0;
}
}
for (i = 0; i < pres.count_planes; ++i) {
plane_ids[i] = 0;
}
}
/* drop removed objects */
/* add new objects */
for (i = 0; i < res.count_crtcs; ++i) {
if (crtc_ids[i] < 1)
continue;
if (r < 0)
return r;
}
for (i = 0; i < res.count_encoders; ++i) {
if (encoder_ids[i] < 1)
continue;
if (r < 0)
return r;
}
for (i = 0; i < res.count_connectors; ++i) {
if (connector_ids[i] < 1)
continue;
if (r < 0)
return r;
}
for (i = 0; i < pres.count_planes; ++i) {
if (plane_ids[i] < 1)
continue;
if (r < 0)
return r;
}
/* re-sync objects after object_map is synced */
case GRDRM_TYPE_CRTC:
break;
case GRDRM_TYPE_ENCODER:
break;
case GRDRM_TYPE_CONNECTOR:
break;
case GRDRM_TYPE_PLANE:
break;
default:
assert_not_reached("grdrm: invalid object type");
r = 0;
}
if (r < 0)
return r;
if (card->async_hotplug)
break;
}
/* if modeset objects change during sync, start over */
if (card->async_hotplug) {
card->async_hotplug = false;
continue;
}
continue;
continue;
continue;
continue;
continue;
}
/* cache old crtc settings for later restore */
continue;
/* Save data if it is the first time we refresh the CRTC. This data can
* be used optionally to restore any previous configuration. For
* instance, it allows us to restore VT configurations after we close
* our session again. */
memcpy(crtc->old.connectors, crtc->kern.used_connectors, sizeof(uint32_t) * crtc->old.n_connectors);
}
}
/* everything synced */
break;
}
if (tries >= GRDRM_MAX_TRIES) {
/*
* Ugh! We were unable to sync the DRM card state due to heavy
* hotplugging. This should never happen, so print a debug
* message and bail out. The next uevent will trigger
* this again.
*/
return -EFAULT;
}
return 0;
}
uint32_t i, j;
return false;
return false;
continue;
return true;
}
}
}
return false;
}
/*
* Modeset Configuration
* This is where we update our modeset configuration and assign
* connectors to CRTCs. This means, each connector that we want to
* enable needs a CRTC, disabled (or unavailable) connectors are left
* alone in the dark. Once all CRTCs are assigned, the remaining CRTCs
* are disabled.
* Sounds trivial, but there're several caveats:
*
* * Multiple connectors can be driven by the same CRTC. This is
* known as 'hardware clone mode'. Advantage over software clone
* mode is that only a single CRTC is needed to drive multiple
* displays. However, few hardware supports this and it's a huge
* 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 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
* proper way to do advanced CRTC selection on dynamic demands. It
* is not really clear which demands require what CRTC, so, like
* everyone else, we do random CRTC selection unless explicitly
* states otherwise.
*
* * Each Connector has a list of possible encoders that can drive
* it, and each encoder has a list of possible CRTCs. If this graph
* is a tree, assignment is trivial. However, if not, we cannot
* reliably decide on configurations beforehand. The encoder is
* always selected by the kernel, so we have to actually set a mode
* to know which encoder is used. There is no way to ask the kernel
* whether a given configuration is possible. This will change with
* atomic-modesetting, but until then, we keep our configurations
* simple and assume they work all just fine. If one fails
* unexpectedly, we print a warning and disable it.
*
* Configuring a card consists of several steps:
*
* 1) First of all, we apply any user-configuration. If a user wants
* a fixed configuration, we apply it and preserve it.
* So far, we don't support user configuration files, so this step
* is skipped.
*
* 2) Secondly, we need to apply any quirks from hwdb. Some hardware
* might only support limited configurations or require special
* present.
* So far, we don't support this as there is no known quirk, so
* this step is skipped.
*
* 3) As deep modesets are expensive, we try to avoid them if
* possible. Therefore, we read the current configuration from the
* kernel and try to preserve it, if compatible with our demands.
* If not, we break it and reassign it in a following step.
*
* 4) The main step involves configuring all remaining objects. By
* default, all available connectors are enabled, except for those
* disabled by user-configuration. We lookup a suitable CRTC for
* each connector and assign them. As there might be more
* connectors than CRTCs, we apply some ordering so users can
* select which connectors are more important right now.
* So far, we only apply the default ordering, more might be added
* in the future.
*/
Iterator i, j;
/* clear assignments */
/* preserve existing configurations */
continue;
/* If our mode is set, preserve it. If no connector is
* set, modeset either failed or the pipe is unused. In
* both cases, leave it alone. It might be tried again
* below in case there're remaining connectors.
* Otherwise, try restoring the assignments. If they
* are no longer valid, leave the pipe untouched. */
continue;
continue;
/* If our mode is not set on the pipe, we know the kern
* information is valid. Try keeping it. If it's not
* possible, leave the pipe untouched for later
* assignements. */
continue;
}
}
/* assign remaining objects */
continue;
continue;
break;
}
}
/* expose configuration */
continue;
}
}
int r;
return;
r = grdrm_card_resync(card);
if (r < 0) {
log_debug_errno(r, "grdrm: %s/%s: cannot re-sync card: %m",
return;
}
/* debug statement to print card information */
if (0)
}
struct drm_event_vblank *vblank;
char buf[4096];
ssize_t l;
/* Immediately close device on HUP; no need to flush pending
* data.. there're no events we care about here. */
return 0;
}
if (l < 0) {
return 0;
return 0;
}
log_debug("grdrm: %s/%s: truncated event",
break;
}
case DRM_EVENT_FLIP_COMPLETE:
log_debug("grdrm: %s/%s: truncated vblank event",
break;
}
break;
break;
}
}
}
return 0;
}
if (!card->object_map)
return -ENOMEM;
}
}
break;
continue;
}
}
break;
continue;
}
}
return;
/* ignore cards without DUMB_BUFFER capability */
return;
}
return;
/* stop all pipes */
continue;
}
}
struct drm_get_cap cap;
int r, flags;
r = fd_nonblock(fd, true);
if (r < 0)
return r;
r = fd_cloexec(fd, true);
if (r < 0)
return r;
if (flags < 0)
return -errno;
return -EACCES;
fd,
card);
if (r < 0)
return r;
fd = -1;
/* cache DUMB_BUFFER capability */
if (r < 0)
log_debug_errno(r, "grdrm: %s/%s: cannot retrieve DUMB_BUFFER capability: %m",
log_debug("grdrm: %s/%s: DUMB_BUFFER capability not supported",
/* cache TIMESTAMP_MONOTONIC capability */
if (r < 0)
log_debug_errno(r, "grdrm: %s/%s: cannot retrieve TIMESTAMP_MONOTONIC capability: %m",
else if (!card->cap_monotonic)
log_debug("grdrm: %s/%s: TIMESTAMP_MONOTONIC is disabled globally, fix this NOW!",
return 0;
}
return;
}
switch (r) {
case -EACCES:
/* If we get EACCES on runtime DRM calls, we lost DRM-Master
* (or we did something terribly wrong). Immediately disable
* the card, so we stop all pipes and wait to be activated
* again. */
break;
case -ENOENT:
/* DRM objects can be hotplugged at any time. If an object is
* removed that we use, we remember that state so a following
* call can test for this.
* Note that we also get a uevent as followup, this will resync
* the whole device. */
card->async_hotplug = true;
break;
}
}
/*
* Unmanaged Cards
* The unmanaged DRM card opens the device node for a given DRM device
* the device only if we really require it and releases it as soon as we're
* disabled or closed.
* The unmanaged element can be used in all situations where you have direct
* access to DRM device nodes. Unlike managed DRM elements, it can be used
* outside of user sessions and in emergency situations where logind is not
* available.
*/
int r, fd;
/* try open on activation if it failed during allocation */
if (fd < 0) {
/* not fatal; simply ignore the device */
return;
}
/* we might already be DRM-Master by open(); that's fine */
if (r < 0) {
log_debug_errno(r, "grdrm: %s/%s: cannot open: %m",
return;
}
}
if (r < 0) {
return;
}
}
}
char name[GRDRM_CARD_NAME_MAX];
const char *devnode;
int r, fd;
return -ENODEV;
if (!cu)
return -ENOMEM;
return -ENOMEM;
if (r < 0)
return r;
/* try to open but ignore errors */
if (fd < 0) {
/* not fatal; allow uaccess based control on activation */
} else {
/* We might get DRM-Master implicitly on open(); drop it immediately
* so we acquire it only once we're actually enabled. We don't
* really care whether this call fails or not, but let's log any
* weird errors, anyway. */
if (r < 0)
log_debug_errno(r, "grdrm: %s/%s: cannot open: %m",
}
if (out)
return 0;
}
}
static const grdev_card_vtable unmanaged_card_vtable = {
};
/*
* Managed Cards
* The managed DRM card uses systemd-logind to acquire DRM devices. This
* logind passes us a file-descriptor whenever our session is activated. Thus,
* we don't need access to the device node directly.
* Furthermore, whenever the session is put asleep, logind revokes the
* file-descriptor so we loose access to the device.
* Managed DRM cards should be preferred over unmanaged DRM cards whenever
* you run inside a user session with exclusive device access.
*/
/* If the device is manually re-enabled, we try to resume our card
* management. Note that we have no control over DRM-Master and the fd,
* so we have to take over the state from the last logind event. */
}
/* If the device is manually disabled, we keep the FD but put our card
* management asleep. This way, we can wake up at any time, but don't
* touch the device while asleep. */
}
void *userdata,
const char *mode;
int r;
/*
* We get PauseDevice() signals from logind whenever a device we
* number of the device and the mode of the operation.
* In case the event is not about our device, we ignore it. Otherwise,
* we treat it as asynchronous DRM-DROP-MASTER. Note that we might have
* already handled an EACCES error from a modeset ioctl, in which case
* we already disabled the device.
*
* @mode can be one of the following:
* "pause": The device is about to be paused. We must react
* immediately and respond with PauseDeviceComplete(). Once
* we replied, logind will pause the device. Note that
* logind might apply any kind of timeout and force pause
* the device if we don't respond in a timely manner. In
* this case, we will receive a second PauseDevice event
* with @mode set to "force" (or similar).
* "force": The device was disabled forecfully by logind. DRM-Master
* was already dropped. This is just an asynchronous
* notification so we can put the device asleep (in case
* we didn't already notice the dropped DRM-Master).
* "gone": This is like "force" but is sent if the device was
* paused due to a device-removal event.
*
* We always handle PauseDevice signals as "force" as we properly
* support asynchronously dropping DRM-Master, anyway. But in case
* logind sent mode "pause", we also call PauseDeviceComplete() to
* immediately acknowledge the request.
*/
if (r < 0) {
log_debug("grdrm: %s/%s: erroneous PauseDevice signal",
return 0;
}
/* not our device? */
return 0;
/*
* Sending PauseDeviceComplete() is racy if logind triggers the
* timeout. That is, if we take too long and logind pauses the
* device by sending a forced PauseDevice, our
* PauseDeviceComplete call will be stray. That's fine, though.
* logind ignores such stray calls. Only if logind also sent a
* further PauseDevice() signal, it might match our call
* incorrectly to the newer PauseDevice(). That's fine, too, as
* we handle that event asynchronously, anyway. Therefore,
* whatever happens, we're fine. Yay!
*/
&m,
"org.freedesktop.login1",
"org.freedesktop.login1.Session",
"PauseDeviceComplete");
if (r >= 0) {
if (r >= 0)
}
if (r < 0)
log_debug_errno(r, "grdrm: %s/%s: cannot send PauseDeviceComplete: %m",
}
return 0;
}
void *userdata,
int r, fd;
/*
* We get ResumeDevice signals whenever logind resumed a previously
* related device and a new file-descriptor for the freshly opened
* device-node.
* If the signal is not about our device, we simply ignore it.
* Otherwise, we immediately resume the device. Note that we drop the
* new file-descriptor as we already have one from TakeDevice(). logind
* DRM-FBs and BOs) is preserved.
*/
if (r < 0) {
log_debug("grdrm: %s/%s: erroneous ResumeDevice signal",
return 0;
}
/* not our device? */
return 0;
/* This shouldn't happen. We should already own an FD from
* TakeDevice(). However, let's be safe and use this FD in case
* we really don't have one. There is no harm in doing this
* and our code works fine this way. */
if (fd < 0) {
return 0;
}
if (r < 0) {
log_debug_errno(r, "grdrm: %s/%s: cannot open: %m",
return 0;
}
}
return 0;
}
int r;
"sender='org.freedesktop.login1',"
"interface='org.freedesktop.login1.Session',"
"member='PauseDevice',"
NULL);
if (!match)
return -ENOMEM;
cm);
if (r < 0)
return r;
"sender='org.freedesktop.login1',"
"interface='org.freedesktop.login1.Session',"
"member='ResumeDevice',"
NULL);
if (!match)
return -ENOMEM;
cm);
if (r < 0)
return r;
return 0;
}
void *userdata,
log_debug("grdrm: %s/%s: TakeDevice failed: %s: %s",
return 0;
}
if (r < 0) {
log_debug("grdrm: %s/%s: erroneous TakeDevice reply",
return 0;
}
if (fd < 0) {
return 0;
}
if (r < 0) {
log_debug_errno(r, "grdrm: %s/%s: cannot open: %m",
return 0;
}
return 0;
}
int r;
&m,
"org.freedesktop.login1",
"org.freedesktop.login1.Session",
"TakeDevice");
if (r < 0)
goto error;
if (r < 0)
goto error;
m,
cm,
0);
if (r < 0)
goto error;
return;
log_debug_errno(r, "grdrm: %s/%s: cannot send TakeDevice request: %m",
}
int r;
/*
* If TakeDevice() is pending or was successful, make sure to
* release the device again. We don't care for return-values,
* so send it without waiting or callbacks.
* If a failed TakeDevice() is pending, but someone else took
* the device on the same bus-connection, we might incorrectly
* release their device. This is an unlikely race, though.
* Furthermore, you really shouldn't have two users of the
* controller-API on the same session, on the same devices, *AND* on
* the same bus-connection. So we don't care for that race..
*/
return;
&m,
"org.freedesktop.login1",
"org.freedesktop.login1.Session",
"ReleaseDevice");
if (r >= 0) {
if (r >= 0)
}
if (r < 0 && r != -ENOTCONN)
log_debug_errno(r, "grdrm: %s/%s: cannot send ReleaseDevice: %m",
}
char name[GRDRM_CARD_NAME_MAX];
int r;
if (devnum == 0)
return -ENODEV;
if (!cm)
return -ENOMEM;
r = managed_card_setup_bus(cm);
if (r < 0)
return r;
if (r < 0)
return r;
if (out)
return 0;
}
}
static const grdev_card_vtable managed_card_vtable = {
};
/*
* Generic Constructor
* Instead of relying on the caller to choose between managed and unmanaged
* DRM devices, the grdev_drm_new() constructor does that for you (by
* looking at session->managed).
*/
}
char name[GRDRM_CARD_NAME_MAX];
}
return session->managed ? managed_card_new(out, session, ud) : unmanaged_card_new(out, session, ud);
}
const char *p, *action;
* got hotplugged DRM objects so refresh the device. */
if (devnum == 0) {
}
/* A change event with HOTPLUG=1 is sent whenever a connector
* changed state. Refresh the device to update our state. */
if (streq_ptr(p, "1")) {
}
}
}