/*
* Copyright (C) 2019 Red Hat
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
#include "config.h"
#include "backends/native/meta-kms-impl-device.h"
#include
#include
#include
#include
#include "backends/native/meta-backend-native.h"
#include "backends/native/meta-device-pool.h"
#include "backends/native/meta-kms-connector-private.h"
#include "backends/native/meta-kms-connector.h"
#include "backends/native/meta-kms-crtc-private.h"
#include "backends/native/meta-kms-crtc.h"
#include "backends/native/meta-kms-device-private.h"
#include "backends/native/meta-kms-impl.h"
#include "backends/native/meta-kms-mode-private.h"
#include "backends/native/meta-kms-page-flip-private.h"
#include "backends/native/meta-kms-plane-private.h"
#include "backends/native/meta-kms-plane.h"
#include "backends/native/meta-kms-private.h"
#include "backends/native/meta-thread-private.h"
#include "meta-default-modes.h"
#include "meta-private-enum-types.h"
enum
{
PROP_0,
PROP_DEVICE,
PROP_IMPL,
PROP_PATH,
PROP_FLAGS,
N_PROPS
};
static GParamSpec *obj_props[N_PROPS];
typedef struct _CrtcDeadline
{
MetaKmsImplDevice *impl_device;
MetaKmsCrtc *crtc;
MetaKmsUpdate *pending_update;
gboolean await_flush;
gboolean pending_page_flip;
struct {
int timer_fd;
GSource *source;
gboolean armed;
gboolean is_deadline_page_flip;
int64_t expected_presentation_time_us;
} deadline;
} CrtcFrame;
typedef struct _MetaKmsImplDevicePrivate
{
MetaKmsDevice *device;
MetaKmsImpl *impl;
int fd_hold_count;
MetaDeviceFile *device_file;
GSource *fd_source;
char *path;
MetaKmsDeviceFlag flags;
gboolean has_latched_fd_hold;
char *driver_name;
char *driver_description;
GList *crtcs;
GList *connectors;
GList *planes;
MetaKmsDeviceCaps caps;
GList *fallback_modes;
GHashTable *crtc_frames;
gboolean deadline_timer_inhibited;
} MetaKmsImplDevicePrivate;
static void
initable_iface_init (GInitableIface *iface);
static CrtcFrame * get_crtc_frame (MetaKmsImplDevice *impl_device,
MetaKmsCrtc *latch_crtc);
G_DEFINE_TYPE_WITH_CODE (MetaKmsImplDevice, meta_kms_impl_device,
G_TYPE_OBJECT,
G_ADD_PRIVATE (MetaKmsImplDevice)
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
initable_iface_init))
MetaKmsImpl *
meta_kms_impl_device_get_impl (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
return priv->impl;
}
MetaKmsDevice *
meta_kms_impl_device_get_device (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
return priv->device;
}
GList *
meta_kms_impl_device_copy_connectors (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
return g_list_copy (priv->connectors);
}
GList *
meta_kms_impl_device_copy_crtcs (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
return g_list_copy (priv->crtcs);
}
GList *
meta_kms_impl_device_copy_planes (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
return g_list_copy (priv->planes);
}
GList *
meta_kms_impl_device_peek_connectors (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
return priv->connectors;
}
GList *
meta_kms_impl_device_peek_crtcs (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
return priv->crtcs;
}
GList *
meta_kms_impl_device_peek_planes (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
return priv->planes;
}
const MetaKmsDeviceCaps *
meta_kms_impl_device_get_caps (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
return &priv->caps;
}
GList *
meta_kms_impl_device_copy_fallback_modes (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
return g_list_copy (priv->fallback_modes);
}
const char *
meta_kms_impl_device_get_driver_name (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
return priv->driver_name;
}
const char *
meta_kms_impl_device_get_driver_description (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
return priv->driver_description;
}
const char *
meta_kms_impl_device_get_path (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
return priv->path;
}
gboolean
meta_kms_impl_device_dispatch (MetaKmsImplDevice *impl_device,
GError **error)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
MetaKmsImplDeviceClass *klass = META_KMS_IMPL_DEVICE_GET_CLASS (impl_device);
int fd;
drmEventContext drm_event_context;
meta_assert_in_kms_impl (meta_kms_impl_get_kms (priv->impl));
drm_event_context = (drmEventContext) { 0 };
klass->setup_drm_event_context (impl_device, &drm_event_context);
fd = meta_device_file_get_fd (priv->device_file);
while (TRUE)
{
if (drmHandleEvent (fd, &drm_event_context) != 0)
{
struct pollfd pfd;
int ret;
if (errno != EAGAIN)
{
g_set_error_literal (error, G_IO_ERROR,
g_io_error_from_errno (errno),
strerror (errno));
return FALSE;
}
pfd.fd = fd;
pfd.events = POLL_IN | POLL_ERR;
do
{
ret = poll (&pfd, 1, -1);
}
while (ret == -1 && errno == EINTR);
}
else
{
break;
}
}
return TRUE;
}
static gpointer
kms_event_dispatch_in_impl (MetaThreadImpl *impl,
gpointer user_data,
GError **error)
{
MetaKmsImplDevice *impl_device = user_data;
gboolean ret;
ret = meta_kms_impl_device_dispatch (impl_device, error);
return GINT_TO_POINTER (ret);
}
drmModePropertyPtr
meta_kms_impl_device_find_property (MetaKmsImplDevice *impl_device,
drmModeObjectProperties *props,
const char *prop_name,
int *out_idx)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
int fd;
unsigned int i;
meta_assert_in_kms_impl (meta_kms_impl_get_kms (priv->impl));
fd = meta_device_file_get_fd (priv->device_file);
for (i = 0; i < props->count_props; i++)
{
drmModePropertyPtr prop;
prop = drmModeGetProperty (fd, props->props[i]);
if (!prop)
continue;
if (strcmp (prop->name, prop_name) == 0)
{
*out_idx = i;
return prop;
}
drmModeFreeProperty (prop);
}
return NULL;
}
static void
init_caps (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
int fd;
uint64_t cursor_width, cursor_height;
uint64_t prefer_shadow;
uint64_t uses_monotonic_clock;
uint64_t addfb2_modifiers;
fd = meta_device_file_get_fd (priv->device_file);
if (drmGetCap (fd, DRM_CAP_CURSOR_WIDTH, &cursor_width) == 0 &&
drmGetCap (fd, DRM_CAP_CURSOR_HEIGHT, &cursor_height) == 0)
{
priv->caps.has_cursor_size = TRUE;
priv->caps.cursor_width = cursor_width;
priv->caps.cursor_height = cursor_height;
}
if (drmGetCap (fd, DRM_CAP_DUMB_PREFER_SHADOW, &prefer_shadow) == 0)
{
if (prefer_shadow)
g_message ("Device '%s' prefers shadow buffer", priv->path);
priv->caps.prefers_shadow_buffer = prefer_shadow;
}
if (drmGetCap (fd, DRM_CAP_TIMESTAMP_MONOTONIC, &uses_monotonic_clock) == 0)
{
priv->caps.uses_monotonic_clock = uses_monotonic_clock;
}
if (drmGetCap (fd, DRM_CAP_ADDFB2_MODIFIERS, &addfb2_modifiers) == 0)
{
priv->caps.addfb2_modifiers = (addfb2_modifiers != 0);
}
}
static void
init_crtcs (MetaKmsImplDevice *impl_device,
drmModeRes *drm_resources)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
int idx;
int fd;
fd = meta_device_file_get_fd (priv->device_file);
for (idx = 0; idx < drm_resources->count_crtcs; idx++)
{
uint32_t crtc_id;
drmModeCrtc *drm_crtc;
MetaKmsCrtc *crtc;
g_autoptr (GError) error = NULL;
crtc_id = drm_resources->crtcs[idx];
drm_crtc = drmModeGetCrtc (fd, crtc_id);
if (!drm_crtc)
{
g_warning ("Failed to get CRTC %u info on '%s': %s",
crtc_id, priv->path, error->message);
continue;
}
crtc = meta_kms_crtc_new (impl_device, drm_crtc, idx, &error);
drmModeFreeCrtc (drm_crtc);
if (!crtc)
{
g_warning ("Failed to create CRTC for %u on '%s': %s",
crtc_id, priv->path, error->message);
continue;
}
priv->crtcs = g_list_prepend (priv->crtcs, crtc);
}
priv->crtcs = g_list_reverse (priv->crtcs);
}
static MetaKmsConnector *
find_existing_connector (MetaKmsImplDevice *impl_device,
drmModeConnector *drm_connector)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
GList *l;
for (l = priv->connectors; l; l = l->next)
{
MetaKmsConnector *connector = l->data;
if (meta_kms_connector_is_same_as (connector, drm_connector))
return connector;
}
return NULL;
}
static MetaKmsResourceChanges
update_connectors (MetaKmsImplDevice *impl_device,
drmModeRes *drm_resources,
uint32_t updated_connector_id)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
g_autolist (MetaKmsConnector) connectors = NULL;
gboolean added_connector = FALSE;
MetaKmsResourceChanges changes = META_KMS_RESOURCE_CHANGE_NONE;
unsigned int i;
int fd;
fd = meta_device_file_get_fd (priv->device_file);
for (i = 0; i < drm_resources->count_connectors; i++)
{
drmModeConnector *drm_connector;
MetaKmsConnector *connector;
drm_connector = drmModeGetConnector (fd, drm_resources->connectors[i]);
if (!drm_connector)
continue;
connector = find_existing_connector (impl_device, drm_connector);
if (connector)
{
connector = g_object_ref (connector);
if (updated_connector_id == 0 ||
meta_kms_connector_get_id (connector) == updated_connector_id)
{
changes |= meta_kms_connector_update_state_in_impl (connector,
drm_resources,
drm_connector);
}
}
else
{
connector = meta_kms_connector_new (impl_device, drm_connector,
drm_resources);
added_connector = TRUE;
}
drmModeFreeConnector (drm_connector);
connectors = g_list_prepend (connectors, connector);
}
if (!added_connector &&
g_list_length (connectors) == g_list_length (priv->connectors))
return changes;
g_list_free_full (priv->connectors, g_object_unref);
priv->connectors = g_list_reverse (g_steal_pointer (&connectors));
return META_KMS_RESOURCE_CHANGE_FULL;
}
static MetaKmsPlaneType
get_plane_type (MetaKmsImplDevice *impl_device,
drmModeObjectProperties *props)
{
drmModePropertyPtr prop;
int idx;
prop = meta_kms_impl_device_find_property (impl_device, props, "type", &idx);
if (!prop)
return FALSE;
drmModeFreeProperty (prop);
switch (props->prop_values[idx])
{
case DRM_PLANE_TYPE_PRIMARY:
return META_KMS_PLANE_TYPE_PRIMARY;
case DRM_PLANE_TYPE_CURSOR:
return META_KMS_PLANE_TYPE_CURSOR;
case DRM_PLANE_TYPE_OVERLAY:
return META_KMS_PLANE_TYPE_OVERLAY;
default:
g_warning ("Unhandled plane type %" G_GUINT64_FORMAT,
props->prop_values[idx]);
return -1;
}
}
MetaKmsPlane *
meta_kms_impl_device_add_fake_plane (MetaKmsImplDevice *impl_device,
MetaKmsPlaneType plane_type,
MetaKmsCrtc *crtc)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
MetaKmsPlane *plane;
plane = meta_kms_plane_new_fake (plane_type, crtc);
priv->planes = g_list_append (priv->planes, plane);
return plane;
}
uint64_t
meta_kms_prop_convert_value (MetaKmsProp *prop,
uint64_t value)
{
switch (prop->type)
{
case DRM_MODE_PROP_RANGE:
case DRM_MODE_PROP_SIGNED_RANGE:
case DRM_MODE_PROP_BLOB:
case DRM_MODE_PROP_OBJECT:
return value;
case DRM_MODE_PROP_ENUM:
g_assert (prop->enum_values[value].valid);
return prop->enum_values[value].value;
case DRM_MODE_PROP_BITMASK:
{
int i;
uint64_t result = 0;
for (i = 0; i < prop->num_enum_values; i++)
{
if (!prop->enum_values[i].valid)
continue;
if (value & prop->enum_values[i].bitmask)
{
result |= (1 << prop->enum_values[i].value);
value &= ~(prop->enum_values[i].bitmask);
}
}
g_assert (value == 0);
return result;
}
default:
g_assert_not_reached ();
}
return 0;
}
static void
update_prop_value (MetaKmsProp *prop,
uint64_t drm_value)
{
switch (prop->type)
{
case DRM_MODE_PROP_RANGE:
case DRM_MODE_PROP_SIGNED_RANGE:
case DRM_MODE_PROP_BLOB:
case DRM_MODE_PROP_OBJECT:
prop->value = drm_value;
return;
case DRM_MODE_PROP_ENUM:
{
int i;
uint64_t result = prop->default_value;
uint64_t supported = 0;
for (i = 0; i < prop->num_enum_values; i++)
{
if (!prop->enum_values[i].valid)
continue;
if (prop->enum_values[i].value == drm_value)
{
result = i;
}
supported |= (1 << i);
}
prop->value = result;
prop->supported_variants = supported;
return;
}
case DRM_MODE_PROP_BITMASK:
{
int i;
uint64_t result = 0;
uint64_t supported = 0;
for (i = 0; i < prop->num_enum_values; i++)
{
if (!prop->enum_values[i].valid)
continue;
if (drm_value & (1 << prop->enum_values[i].value))
{
result |= prop->enum_values[i].bitmask;
drm_value &= ~(1 << prop->enum_values[i].value);
}
supported |= prop->enum_values[i].bitmask;
}
if (drm_value != 0)
result |= prop->default_value;
prop->value = result;
prop->supported_variants = supported;
return;
}
default:
g_assert_not_reached ();
}
}
static void
update_prop_enum_value(MetaKmsEnum *prop_enum,
drmModePropertyRes *drm_prop)
{
int i;
for (i = 0; i < drm_prop->count_enums; i++)
{
if (strcmp (prop_enum->name, drm_prop->enums[i].name) == 0)
{
prop_enum->value = drm_prop->enums[i].value;
prop_enum->valid = TRUE;
return;
}
}
prop_enum->valid = FALSE;
}
static MetaKmsProp *
find_prop (MetaKmsProp *props,
int n_props,
const char *name)
{
int i;
for (i = 0; i < n_props; i++)
{
MetaKmsProp *prop = &props[i];
g_warn_if_fail (prop->name);
if (g_strcmp0 (prop->name, name) == 0)
return prop;
}
return NULL;
}
void
meta_kms_impl_device_update_prop_table (MetaKmsImplDevice *impl_device,
uint32_t *drm_props,
uint64_t *drm_prop_values,
int n_drm_props,
MetaKmsProp *props,
int n_props)
{
int fd;
uint32_t i, j;
fd = meta_kms_impl_device_get_fd (impl_device);
for (i = 0; i < n_props; i++)
{
MetaKmsProp *prop = &props[i];
prop->prop_id = 0;
prop->value = 0;
for (j = 0; j < prop->num_enum_values; j++)
{
prop->enum_values[j].valid = FALSE;
prop->enum_values[j].value = 0;
}
}
for (i = 0; i < n_drm_props; i++)
{
uint32_t prop_id;
uint64_t prop_value;
drmModePropertyRes *drm_prop;
MetaKmsProp *prop;
prop_id = drm_props[i];
prop_value = drm_prop_values[i];
drm_prop = drmModeGetProperty (fd, prop_id);
if (!drm_prop)
continue;
prop = find_prop (props, n_props, drm_prop->name);
if (!prop)
{
drmModeFreeProperty (drm_prop);
continue;
}
if (!(drm_prop->flags & prop->type))
{
g_warning ("DRM property '%s' (%u) had unexpected flags (0x%x), "
"ignoring",
drm_prop->name, prop_id, drm_prop->flags);
drmModeFreeProperty (drm_prop);
continue;
}
prop->prop_id = prop_id;
if (prop->type == DRM_MODE_PROP_BITMASK ||
prop->type == DRM_MODE_PROP_ENUM)
{
for (j = 0; j < prop->num_enum_values; j++)
update_prop_enum_value (&prop->enum_values[j], drm_prop);
}
update_prop_value (prop, prop_value);
if (prop->type == DRM_MODE_PROP_RANGE)
{
if (drm_prop->count_values == 2)
{
prop->range_min = drm_prop->values[0];
prop->range_max = drm_prop->values[1];
}
else
{
g_warning ("DRM property '%s' is a range with %d values, ignoring",
drm_prop->name, drm_prop->count_values);
}
}
if (prop->type == DRM_MODE_PROP_SIGNED_RANGE)
{
if (drm_prop->count_values == 2)
{
prop->range_min_signed = (int64_t) drm_prop->values[0];
prop->range_max_signed = (int64_t) drm_prop->values[1];
}
else
{
g_warning ("DRM property '%s' is a signed range with %d values, ignoring",
drm_prop->name, drm_prop->count_values);
}
}
drmModeFreeProperty (drm_prop);
}
}
static void
init_planes (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
int fd;
drmModePlaneRes *drm_planes;
unsigned int i;
fd = meta_device_file_get_fd (priv->device_file);
drm_planes = drmModeGetPlaneResources (fd);
if (!drm_planes)
return;
for (i = 0; i < drm_planes->count_planes; i++)
{
drmModePlane *drm_plane;
drmModeObjectProperties *props;
drm_plane = drmModeGetPlane (fd, drm_planes->planes[i]);
if (!drm_plane)
continue;
props = drmModeObjectGetProperties (fd,
drm_plane->plane_id,
DRM_MODE_OBJECT_PLANE);
if (props)
{
MetaKmsPlaneType plane_type;
plane_type = get_plane_type (impl_device, props);
if (plane_type != -1)
{
MetaKmsPlane *plane;
plane = meta_kms_plane_new (plane_type,
impl_device,
drm_plane, props);
priv->planes = g_list_prepend (priv->planes, plane);
}
}
g_clear_pointer (&props, drmModeFreeObjectProperties);
drmModeFreePlane (drm_plane);
}
priv->planes = g_list_reverse (priv->planes);
}
static void
init_fallback_modes (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
GList *modes = NULL;
int i;
for (i = 0; i < G_N_ELEMENTS (meta_default_landscape_drm_mode_infos); i++)
{
MetaKmsMode *mode;
mode = meta_kms_mode_new (impl_device,
&meta_default_landscape_drm_mode_infos[i],
META_KMS_MODE_FLAG_FALLBACK_LANDSCAPE);
modes = g_list_prepend (modes, mode);
}
for (i = 0; i < G_N_ELEMENTS (meta_default_portrait_drm_mode_infos); i++)
{
MetaKmsMode *mode;
mode = meta_kms_mode_new (impl_device,
&meta_default_portrait_drm_mode_infos[i],
META_KMS_MODE_FLAG_FALLBACK_PORTRAIT);
modes = g_list_prepend (modes, mode);
}
priv->fallback_modes = g_list_reverse (modes);
}
static MetaDeviceFile *
meta_kms_impl_device_open_device_file (MetaKmsImplDevice *impl_device,
const char *path,
GError **error)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
MetaKmsImplDeviceClass *klass = META_KMS_IMPL_DEVICE_GET_CLASS (impl_device);
return klass->open_device_file (impl_device, priv->path, error);
}
static gboolean
ensure_device_file (MetaKmsImplDevice *impl_device,
GError **error)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
MetaDeviceFile *device_file;
if (priv->device_file)
return TRUE;
device_file = meta_kms_impl_device_open_device_file (impl_device,
priv->path,
error);
if (!device_file)
return FALSE;
priv->device_file = device_file;
if (!(priv->flags & META_KMS_DEVICE_FLAG_NO_MODE_SETTING))
{
priv->fd_source =
meta_thread_impl_register_fd (META_THREAD_IMPL (priv->impl),
meta_device_file_get_fd (device_file),
kms_event_dispatch_in_impl,
impl_device);
g_source_set_priority (priv->fd_source, G_PRIORITY_HIGH);
}
return TRUE;
}
static void
ensure_latched_fd_hold (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
if (!priv->has_latched_fd_hold)
{
meta_kms_impl_device_hold_fd (impl_device);
priv->has_latched_fd_hold = TRUE;
}
}
static void
clear_latched_fd_hold (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
if (priv->has_latched_fd_hold)
{
meta_kms_impl_device_unhold_fd (impl_device);
priv->has_latched_fd_hold = FALSE;
}
}
MetaKmsResourceChanges
meta_kms_impl_device_update_states (MetaKmsImplDevice *impl_device,
uint32_t crtc_id,
uint32_t connector_id)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
g_autoptr (GError) error = NULL;
int fd;
drmModeRes *drm_resources;
MetaKmsResourceChanges changes;
GList *l;
meta_assert_in_kms_impl (meta_kms_impl_get_kms (priv->impl));
meta_topic (META_DEBUG_KMS, "Updating device state for %s", priv->path);
if (!ensure_device_file (impl_device, &error))
{
g_warning ("Failed to reopen '%s': %s", priv->path, error->message);
goto err;
}
ensure_latched_fd_hold (impl_device);
fd = meta_device_file_get_fd (priv->device_file);
drm_resources = drmModeGetResources (fd);
if (!drm_resources)
{
meta_topic (META_DEBUG_KMS, "Device '%s' didn't return any resources",
priv->path);
goto err;
}
changes = update_connectors (impl_device, drm_resources, connector_id);
for (l = priv->crtcs; l; l = l->next)
{
MetaKmsCrtc *crtc = META_KMS_CRTC (l->data);
if (crtc_id > 0 &&
meta_kms_crtc_get_id (crtc) != crtc_id)
continue;
changes |= meta_kms_crtc_update_state_in_impl (crtc);
}
drmModeFreeResources (drm_resources);
return changes;
err:
g_clear_list (&priv->planes, g_object_unref);
g_clear_list (&priv->crtcs, g_object_unref);
g_clear_list (&priv->connectors, g_object_unref);
g_clear_pointer (&priv->crtc_frames, g_hash_table_unref);
return META_KMS_RESOURCE_CHANGE_FULL;
}
static MetaKmsResourceChanges
meta_kms_impl_device_predict_states (MetaKmsImplDevice *impl_device,
MetaKmsUpdate *update)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
MetaKmsResourceChanges changes = META_KMS_RESOURCE_CHANGE_NONE;
GList *l;
g_list_foreach (priv->crtcs,
(GFunc) meta_kms_crtc_predict_state_in_impl,
update);
for (l = priv->connectors; l; l = l->next)
{
MetaKmsConnector *connector = l->data;
changes |= meta_kms_connector_predict_state_in_impl (connector, update);
}
return changes;
}
void
meta_kms_impl_device_notify_modes_set (MetaKmsImplDevice *impl_device)
{
clear_latched_fd_hold (impl_device);
}
int
meta_kms_impl_device_get_fd (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
meta_assert_in_kms_impl (meta_kms_impl_get_kms (priv->impl));
return meta_device_file_get_fd (priv->device_file);
}
static void
disarm_crtc_frame_deadline_timer (CrtcFrame *crtc_frame)
{
struct itimerspec its = {};
if (!crtc_frame->deadline.source)
return;
meta_topic (META_DEBUG_KMS, "Disarming deadline timer for crtc %u (%s)",
meta_kms_crtc_get_id (crtc_frame->crtc),
meta_kms_device_get_path (meta_kms_crtc_get_device (crtc_frame->crtc)));
timerfd_settime (crtc_frame->deadline.timer_fd,
TFD_TIMER_ABSTIME, &its, NULL);
crtc_frame->deadline.armed = FALSE;
}
static void
arm_crtc_frame_deadline_timer (CrtcFrame *crtc_frame,
int64_t next_deadline_us,
int64_t next_presentation_us)
{
struct itimerspec its = {};
int64_t tv_sec;
int64_t tv_nsec;
g_warn_if_fail (!crtc_frame->await_flush);
if (!crtc_frame->deadline.source)
return;
meta_topic (META_DEBUG_KMS, "Arming deadline timer for crtc %u (%s): %ld",
meta_kms_crtc_get_id (crtc_frame->crtc),
meta_kms_device_get_path (meta_kms_crtc_get_device (crtc_frame->crtc)),
next_deadline_us);
tv_sec = us2s (next_deadline_us);
tv_nsec = us2ns (next_deadline_us - s2us (tv_sec));
its.it_value.tv_sec = tv_sec;
its.it_value.tv_nsec = tv_nsec;
timerfd_settime (crtc_frame->deadline.timer_fd,
TFD_TIMER_ABSTIME, &its, NULL);
crtc_frame->deadline.expected_presentation_time_us = next_presentation_us;
crtc_frame->deadline.armed = TRUE;
}
static void
notify_crtc_frame_ready (CrtcFrame *crtc_frame)
{
MetaKmsCrtc *crtc = crtc_frame->crtc;
crtc_frame->pending_page_flip = FALSE;
crtc_frame->deadline.is_deadline_page_flip = FALSE;
if (!crtc_frame->pending_update)
return;
if (crtc_frame->await_flush)
return;
meta_kms_impl_device_schedule_process (crtc_frame->impl_device, crtc);
}
static void
crtc_page_flip_feedback_flipped (MetaKmsCrtc *crtc,
unsigned int sequence,
unsigned int tv_sec,
unsigned int tv_usec,
gpointer user_data)
{
CrtcFrame *crtc_frame = user_data;
if (crtc_frame->deadline.is_deadline_page_flip &&
meta_is_topic_enabled (META_DEBUG_KMS))
{
struct timeval page_flip_timeval;
int64_t presentation_time_us;
page_flip_timeval = (struct timeval) {
.tv_sec = tv_sec,
.tv_usec = tv_usec,
};
presentation_time_us = meta_timeval_to_microseconds (&page_flip_timeval);
meta_topic (META_DEBUG_KMS,
"Deadline page flip presentation time: %"G_GINT64_FORMAT" us, "
"expected %"G_GINT64_FORMAT" us "
"(diff: %"G_GINT64_FORMAT")",
presentation_time_us,
crtc_frame->deadline.expected_presentation_time_us,
crtc_frame->deadline.expected_presentation_time_us -
presentation_time_us);
}
notify_crtc_frame_ready (crtc_frame);
}
static void
crtc_page_flip_feedback_ready (MetaKmsCrtc *crtc,
gpointer user_data)
{
CrtcFrame *crtc_frame = user_data;
notify_crtc_frame_ready (crtc_frame);
}
static void
crtc_page_flip_feedback_mode_set_fallback (MetaKmsCrtc *crtc,
gpointer user_data)
{
CrtcFrame *crtc_frame = user_data;
crtc_frame->pending_page_flip = FALSE;
}
static void
crtc_page_flip_feedback_discarded (MetaKmsCrtc *crtc,
gpointer user_data,
const GError *error)
{
CrtcFrame *crtc_frame = user_data;
crtc_frame->pending_page_flip = FALSE;
}
static const MetaKmsPageFlipListenerVtable crtc_page_flip_listener_vtable = {
.flipped = crtc_page_flip_feedback_flipped,
.ready = crtc_page_flip_feedback_ready,
.mode_set_fallback = crtc_page_flip_feedback_mode_set_fallback,
.discarded = crtc_page_flip_feedback_discarded,
};
static void
emit_resources_changed_callback (MetaThread *thread,
gpointer user_data)
{
MetaKmsResourceChanges changes = GPOINTER_TO_UINT (user_data);
meta_kms_emit_resources_changed (META_KMS (thread), changes);
}
static void
queue_result_feedback (MetaKmsImplDevice *impl_device,
MetaKmsUpdate *update,
MetaKmsFeedback *feedback)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
MetaKms *kms = meta_kms_device_get_kms (priv->device);
g_autoptr (GList) result_listeners = NULL;
GList *l;
result_listeners = meta_kms_update_take_result_listeners (update);
for (l = result_listeners; l; l = l->next)
{
MetaKmsResultListener *listener = l->data;
meta_kms_result_listener_set_feedback (listener, feedback);
meta_kms_queue_result_callback (kms, listener);
}
}
static MetaKmsFeedback *
do_process (MetaKmsImplDevice *impl_device,
MetaKmsCrtc *latch_crtc,
MetaKmsUpdate *update,
MetaKmsUpdateFlag flags)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
MetaKms *kms = meta_kms_device_get_kms (priv->device);
MetaKmsImpl *impl = meta_kms_impl_device_get_impl (impl_device);
MetaThreadImpl *thread_impl = META_THREAD_IMPL (impl);
MetaKmsImplDeviceClass *klass = META_KMS_IMPL_DEVICE_GET_CLASS (impl_device);
CrtcFrame *crtc_frame = NULL;
MetaKmsFeedback *feedback;
MetaKmsResourceChanges changes = META_KMS_RESOURCE_CHANGE_NONE;
COGL_TRACE_BEGIN_SCOPED (MetaKmsImplDeviceProcess,
"KMS device impl (processing)");
update = meta_kms_impl_filter_update (impl, latch_crtc, update, flags);
if (!update || meta_kms_update_is_empty (update))
{
GError *error;
error = g_error_new (META_KMS_ERROR,
META_KMS_ERROR_EMPTY_UPDATE,
"Empty update");
feedback = meta_kms_feedback_new_failed (NULL, error);
if (update)
{
queue_result_feedback (impl_device, update, feedback);
meta_kms_update_free (update);
}
return feedback;
}
if (!(flags & META_KMS_UPDATE_FLAG_TEST_ONLY))
{
if (latch_crtc)
{
crtc_frame = get_crtc_frame (impl_device, latch_crtc);
if (crtc_frame && crtc_frame->pending_update)
{
meta_kms_update_merge_from (crtc_frame->pending_update, update);
meta_kms_update_free (update);
update = g_steal_pointer (&crtc_frame->pending_update);
}
}
if (crtc_frame)
{
GMainContext *thread_context =
meta_thread_impl_get_main_context (thread_impl);
meta_kms_update_add_page_flip_listener (update,
crtc_frame->crtc,
&crtc_page_flip_listener_vtable,
META_KMS_PAGE_FLIP_LISTENER_FLAG_NONE,
thread_context,
crtc_frame, NULL);
crtc_frame->pending_page_flip = TRUE;
}
}
feedback = klass->process_update (impl_device, update, flags);
if (meta_kms_feedback_get_result (feedback) != META_KMS_FEEDBACK_PASSED &&
crtc_frame)
crtc_frame->pending_page_flip = FALSE;
if (!(flags & META_KMS_UPDATE_FLAG_TEST_ONLY))
changes = meta_kms_impl_device_predict_states (impl_device, update);
queue_result_feedback (impl_device, update, feedback);
meta_kms_update_free (update);
if (changes != META_KMS_RESOURCE_CHANGE_NONE)
{
meta_kms_queue_callback (kms,
NULL,
emit_resources_changed_callback,
GUINT_TO_POINTER (changes), NULL);
}
return feedback;
}
static gpointer
crtc_frame_deadline_dispatch (MetaThreadImpl *thread_impl,
gpointer user_data,
GError **error)
{
CrtcFrame *crtc_frame = user_data;
MetaKmsDevice *device = meta_kms_crtc_get_device (crtc_frame->crtc);
MetaKmsImplDevice *impl_device = meta_kms_device_get_impl_device (device);
g_autoptr (MetaKmsFeedback) feedback = NULL;
uint64_t timer_value;
ssize_t ret;
ret = read (crtc_frame->deadline.timer_fd,
&timer_value,
sizeof (timer_value));
if (ret == -1)
{
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
"Failed to read from timerfd: %s", g_strerror (errno));
return GINT_TO_POINTER (FALSE);
}
else if (ret != sizeof (timer_value))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to read from timerfd: unexpected size %zd", ret);
return GINT_TO_POINTER (FALSE);
}
feedback = do_process (impl_device,
crtc_frame->crtc,
g_steal_pointer (&crtc_frame->pending_update),
META_KMS_UPDATE_FLAG_NONE);
if (meta_kms_feedback_did_pass (feedback))
crtc_frame->deadline.is_deadline_page_flip = TRUE;
disarm_crtc_frame_deadline_timer (crtc_frame);
return GINT_TO_POINTER (TRUE);
}
static void
crtc_frame_free (CrtcFrame *crtc_frame)
{
g_clear_fd (&crtc_frame->deadline.timer_fd, NULL);
g_clear_pointer (&crtc_frame->deadline.source, g_source_destroy);
g_clear_pointer (&crtc_frame->pending_update, meta_kms_update_free);
g_free (crtc_frame);
}
static CrtcFrame *
get_crtc_frame (MetaKmsImplDevice *impl_device,
MetaKmsCrtc *latch_crtc)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
return g_hash_table_lookup (priv->crtc_frames, latch_crtc);
}
static gboolean
is_using_deadline_timer (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
if (priv->deadline_timer_inhibited)
{
return FALSE;
}
else
{
MetaKmsImpl *impl = meta_kms_impl_device_get_impl (impl_device);
MetaThreadImpl *thread_impl = META_THREAD_IMPL (impl);
return meta_thread_impl_is_realtime (thread_impl);
}
}
static CrtcFrame *
ensure_crtc_frame (MetaKmsImplDevice *impl_device,
MetaKmsCrtc *latch_crtc)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
MetaKmsImpl *impl = meta_kms_impl_device_get_impl (impl_device);
MetaThreadImpl *thread_impl = META_THREAD_IMPL (impl);
CrtcFrame *crtc_frame;
crtc_frame = get_crtc_frame (impl_device, latch_crtc);
if (crtc_frame)
return crtc_frame;
crtc_frame = g_new0 (CrtcFrame, 1);
crtc_frame->impl_device = impl_device;
crtc_frame->crtc = latch_crtc;
crtc_frame->deadline.timer_fd = -1;
crtc_frame->await_flush = TRUE;
if (is_using_deadline_timer (impl_device))
{
int timer_fd;
GSource *source;
g_autofree char *name = NULL;
timer_fd = timerfd_create (CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
source = meta_thread_impl_register_fd (thread_impl,
timer_fd,
crtc_frame_deadline_dispatch,
crtc_frame);
name = g_strdup_printf ("[mutter] KMS deadline clock (crtc: %u, %s)",
meta_kms_crtc_get_id (latch_crtc),
priv->path);
g_source_set_name (source, name);
g_source_set_priority (source, G_PRIORITY_HIGH + 1);
g_source_set_can_recurse (source, FALSE);
g_source_set_ready_time (source, -1);
crtc_frame->deadline.timer_fd = timer_fd;
crtc_frame->deadline.source = source;
g_source_unref (source);
}
g_hash_table_insert (priv->crtc_frames, latch_crtc, crtc_frame);
return crtc_frame;
}
static void
queue_update (MetaKmsImplDevice *impl_device,
CrtcFrame *crtc_frame,
MetaKmsUpdate *update)
{
g_assert (update);
if (crtc_frame->pending_update)
{
meta_kms_update_merge_from (crtc_frame->pending_update, update);
meta_kms_update_free (update);
}
else
{
crtc_frame->pending_update = update;
}
}
void
meta_kms_impl_device_handle_update (MetaKmsImplDevice *impl_device,
MetaKmsUpdate *update,
MetaKmsUpdateFlag flags)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
g_autoptr (GError) error = NULL;
MetaKmsCrtc *latch_crtc;
CrtcFrame *crtc_frame;
MetaKmsFeedback *feedback;
meta_assert_in_kms_impl (meta_kms_impl_get_kms (priv->impl));
latch_crtc = meta_kms_update_get_latch_crtc (update);
if (!latch_crtc)
{
g_set_error (&error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
"Only single-CRTC updates supported");
goto err;
}
if (!priv->crtc_frames)
{
g_set_error (&error, G_IO_ERROR, G_IO_ERROR_CLOSED, "Shutting down");
goto err;
}
if (!ensure_device_file (impl_device, &error))
goto err;
meta_kms_update_realize (update, impl_device);
crtc_frame = ensure_crtc_frame (impl_device, latch_crtc);
crtc_frame->await_flush = FALSE;
if (crtc_frame->pending_page_flip &&
!meta_kms_update_get_mode_sets (update))
{
g_assert (latch_crtc);
meta_topic (META_DEBUG_KMS,
"Queuing update on CRTC %u (%s): pending page flip",
meta_kms_crtc_get_id (latch_crtc),
priv->path);
queue_update (impl_device, crtc_frame, update);
return;
}
if (crtc_frame->pending_update)
{
meta_kms_update_merge_from (crtc_frame->pending_update, update);
meta_kms_update_free (update);
update = g_steal_pointer (&crtc_frame->pending_update);
disarm_crtc_frame_deadline_timer (crtc_frame);
}
meta_kms_device_handle_flush (priv->device, latch_crtc);
feedback = do_process (impl_device, latch_crtc, update, flags);
meta_kms_feedback_unref (feedback);
return;
err:
feedback = meta_kms_feedback_new_failed (NULL, g_steal_pointer (&error));
queue_result_feedback (impl_device, update, feedback);
meta_kms_feedback_unref (feedback);
meta_kms_update_free (update);
}
void
meta_kms_impl_device_await_flush (MetaKmsImplDevice *impl_device,
MetaKmsCrtc *crtc)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
CrtcFrame *crtc_frame;
meta_topic (META_DEBUG_KMS, "Awaiting flush on CRTC %u (%s)",
meta_kms_crtc_get_id (crtc),
priv->path);
crtc_frame = ensure_crtc_frame (impl_device, crtc);
crtc_frame->await_flush = TRUE;
if (crtc_frame->deadline.armed)
disarm_crtc_frame_deadline_timer (crtc_frame);
}
static gboolean
ensure_deadline_timer_armed (MetaKmsImplDevice *impl_device,
CrtcFrame *crtc_frame,
GError **error)
{
int64_t next_deadline_us;
int64_t next_presentation_us;
if (crtc_frame->deadline.armed)
return TRUE;
if (!meta_kms_crtc_determine_deadline (crtc_frame->crtc,
&next_deadline_us,
&next_presentation_us,
error))
return FALSE;
arm_crtc_frame_deadline_timer (crtc_frame,
next_deadline_us,
next_presentation_us);
return TRUE;
}
void
meta_kms_impl_device_schedule_process (MetaKmsImplDevice *impl_device,
MetaKmsCrtc *crtc)
{
CrtcFrame *crtc_frame;
g_autoptr (GError) error = NULL;
MetaKmsImplDevicePrivate *priv;
crtc_frame = ensure_crtc_frame (impl_device, crtc);
if (crtc_frame->await_flush)
return;
if (!is_using_deadline_timer (impl_device))
goto needs_flush;
if (crtc_frame->pending_page_flip)
return;
if (ensure_deadline_timer_armed (impl_device, crtc_frame, &error))
return;
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
g_warning ("Failed to determine deadline: %s", error->message);
priv = meta_kms_impl_device_get_instance_private (impl_device);
priv->deadline_timer_inhibited = TRUE;
needs_flush:
meta_kms_device_set_needs_flush (meta_kms_crtc_get_device (crtc), crtc);
}
static MetaKmsFeedback *
process_mode_set_update (MetaKmsImplDevice *impl_device,
MetaKmsUpdate *update,
MetaKmsUpdateFlag flags)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
MetaKmsImpl *kms_impl = meta_kms_impl_device_get_impl (impl_device);
MetaThreadImpl *thread_impl = META_THREAD_IMPL (kms_impl);
MetaThread *thread = meta_thread_impl_get_thread (thread_impl);
MetaKmsFeedback *feedback;
CrtcFrame *crtc_frame;
GList *l;
GHashTableIter iter;
for (l = meta_kms_update_get_mode_sets (update); l; l = l->next)
{
MetaKmsModeSet *mode_set = l->data;
MetaKmsCrtc *crtc = mode_set->crtc;
crtc_frame = get_crtc_frame (impl_device, crtc);
if (!crtc_frame)
continue;
if (!crtc_frame->pending_update)
continue;
meta_kms_update_merge_from (crtc_frame->pending_update, update);
meta_kms_update_free (update);
update = g_steal_pointer (&crtc_frame->pending_update);
}
g_hash_table_iter_init (&iter, priv->crtc_frames);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &crtc_frame))
{
crtc_frame->deadline.is_deadline_page_flip = FALSE;
crtc_frame->await_flush = FALSE;
crtc_frame->pending_page_flip = FALSE;
g_clear_pointer (&crtc_frame->pending_update, meta_kms_update_free);
disarm_crtc_frame_deadline_timer (crtc_frame);
}
meta_thread_inhibit_realtime_in_impl (thread);
feedback = do_process (impl_device, NULL, update, flags);
meta_thread_uninhibit_realtime_in_impl (thread);
return feedback;
}
MetaKmsFeedback *
meta_kms_impl_device_process_update (MetaKmsImplDevice *impl_device,
MetaKmsUpdate *update,
MetaKmsUpdateFlag flags)
{
g_autoptr (GError) error = NULL;
if (!ensure_device_file (impl_device, &error))
{
MetaKmsFeedback *feedback = NULL;
feedback = meta_kms_feedback_new_failed (NULL, g_steal_pointer (&error));
queue_result_feedback (impl_device, update, feedback);
meta_kms_update_free (update);
return feedback;
}
meta_kms_update_realize (update, impl_device);
if (flags & META_KMS_UPDATE_FLAG_TEST_ONLY)
{
return do_process (impl_device,
meta_kms_update_get_latch_crtc (update),
update, flags);
}
else if (flags & META_KMS_UPDATE_FLAG_MODE_SET)
{
return process_mode_set_update (impl_device, update, flags);
}
else
{
g_assert_not_reached ();
}
}
void
meta_kms_impl_device_disable (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
MetaKmsImpl *kms_impl = meta_kms_impl_device_get_impl (impl_device);
MetaThreadImpl *thread_impl = META_THREAD_IMPL (kms_impl);
MetaThread *thread = meta_thread_impl_get_thread (thread_impl);
MetaKmsImplDeviceClass *klass = META_KMS_IMPL_DEVICE_GET_CLASS (impl_device);
if (!priv->device_file)
return;
meta_kms_impl_device_hold_fd (impl_device);
meta_thread_inhibit_realtime_in_impl (thread);
klass->disable (impl_device);
meta_thread_uninhibit_realtime_in_impl (thread);
g_list_foreach (priv->crtcs,
(GFunc) meta_kms_crtc_disable_in_impl, NULL);
g_list_foreach (priv->connectors,
(GFunc) meta_kms_connector_disable_in_impl, NULL);
meta_kms_impl_device_unhold_fd (impl_device);
}
void
meta_kms_impl_device_handle_page_flip_callback (MetaKmsImplDevice *impl_device,
MetaKmsPageFlipData *page_flip_data)
{
MetaKmsImplDeviceClass *klass = META_KMS_IMPL_DEVICE_GET_CLASS (impl_device);
klass->handle_page_flip_callback (impl_device, page_flip_data);
}
void
meta_kms_impl_device_discard_pending_page_flips (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDeviceClass *klass = META_KMS_IMPL_DEVICE_GET_CLASS (impl_device);
klass->discard_pending_page_flips (impl_device);
}
void
meta_kms_impl_device_hold_fd (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
MetaKms *kms = meta_kms_device_get_kms (priv->device);
meta_assert_in_kms_impl (kms);
g_assert (priv->device_file);
priv->fd_hold_count++;
}
static void
clear_fd_source (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
if (!priv->fd_source)
return;
g_source_destroy (priv->fd_source);
g_clear_pointer (&priv->fd_source, g_source_unref);
}
void
meta_kms_impl_device_unhold_fd (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
MetaKms *kms = meta_kms_device_get_kms (priv->device);
meta_assert_in_kms_impl (kms);
g_return_if_fail (priv->fd_hold_count > 0);
priv->fd_hold_count--;
if (priv->fd_hold_count == 0)
{
g_clear_pointer (&priv->device_file, meta_device_file_release);
clear_fd_source (impl_device);
}
}
static void
meta_kms_impl_device_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
MetaKmsImplDevice *impl_device = META_KMS_IMPL_DEVICE (object);
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
switch (prop_id)
{
case PROP_DEVICE:
g_value_set_object (value, priv->device);
break;
case PROP_IMPL:
g_value_set_object (value, priv->impl);
break;
case PROP_FLAGS:
g_value_set_flags (value, priv->flags);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
meta_kms_impl_device_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
MetaKmsImplDevice *impl_device = META_KMS_IMPL_DEVICE (object);
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
switch (prop_id)
{
case PROP_DEVICE:
priv->device = g_value_get_object (value);
break;
case PROP_IMPL:
priv->impl = g_value_get_object (value);
break;
case PROP_PATH:
priv->path = g_value_dup_string (value);
break;
case PROP_FLAGS:
priv->flags = g_value_get_flags (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
meta_kms_impl_device_finalize (GObject *object)
{
MetaKmsImplDevice *impl_device = META_KMS_IMPL_DEVICE (object);
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
meta_kms_impl_remove_impl_device (priv->impl, impl_device);
g_list_free_full (priv->planes, g_object_unref);
g_list_free_full (priv->crtcs, g_object_unref);
g_list_free_full (priv->connectors, g_object_unref);
g_list_free_full (priv->fallback_modes,
(GDestroyNotify) meta_kms_mode_free);
clear_latched_fd_hold (impl_device);
g_warn_if_fail (!priv->device_file);
g_free (priv->driver_name);
g_free (priv->driver_description);
g_free (priv->path);
G_OBJECT_CLASS (meta_kms_impl_device_parent_class)->finalize (object);
}
gboolean
meta_kms_impl_device_init_mode_setting (MetaKmsImplDevice *impl_device,
GError **error)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
int fd;
drmModeRes *drm_resources;
fd = meta_device_file_get_fd (priv->device_file);
drm_resources = drmModeGetResources (fd);
if (!drm_resources)
{
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
"Failed to activate universal planes: %s",
g_strerror (errno));
return FALSE;
}
init_caps (impl_device);
init_crtcs (impl_device, drm_resources);
init_planes (impl_device);
init_fallback_modes (impl_device);
update_connectors (impl_device, drm_resources, 0);
drmModeFreeResources (drm_resources);
return TRUE;
}
void
meta_kms_impl_device_prepare_shutdown (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
MetaKmsImplDeviceClass *klass = META_KMS_IMPL_DEVICE_GET_CLASS (impl_device);
if (klass->prepare_shutdown)
klass->prepare_shutdown (impl_device);
clear_fd_source (impl_device);
g_clear_pointer (&priv->crtc_frames, g_hash_table_unref);
}
static gboolean
get_driver_info (int fd,
char **name,
char **description)
{
drmVersion *drm_version;
drm_version = drmGetVersion (fd);
if (!drm_version)
return FALSE;
*name = g_strndup (drm_version->name,
drm_version->name_len);
*description = g_strndup (drm_version->desc,
drm_version->desc_len);
drmFreeVersion (drm_version);
return TRUE;
}
static void
maybe_inhibit_deadline_timer (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
static const char *deadline_timer_deny_list[] = {
"vc4",
};
int i;
for (i = 0; i < G_N_ELEMENTS (deadline_timer_deny_list); i++)
{
if (g_strcmp0 (deadline_timer_deny_list[i], priv->driver_name) == 0)
{
priv->deadline_timer_inhibited = TRUE;
break;
}
}
}
static gboolean
meta_kms_impl_device_initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
MetaKmsImplDevice *impl_device = META_KMS_IMPL_DEVICE (initable);
MetaKmsImplDevicePrivate *priv =
meta_kms_impl_device_get_instance_private (impl_device);
int fd;
if (!ensure_device_file (impl_device, error))
return FALSE;
ensure_latched_fd_hold (impl_device);
g_clear_pointer (&priv->path, g_free);
priv->path = g_strdup (meta_device_file_get_path (priv->device_file));
fd = meta_device_file_get_fd (priv->device_file);
if (!get_driver_info (fd, &priv->driver_name, &priv->driver_description))
{
priv->driver_name = g_strdup ("unknown");
priv->driver_description = g_strdup ("Unknown");
}
maybe_inhibit_deadline_timer (impl_device);
priv->crtc_frames =
g_hash_table_new_full (NULL, NULL,
NULL, (GDestroyNotify) crtc_frame_free);
return TRUE;
}
static void
meta_kms_impl_device_init (MetaKmsImplDevice *impl_device)
{
}
static void
initable_iface_init (GInitableIface *iface)
{
iface->init = meta_kms_impl_device_initable_init;
}
static void
meta_kms_impl_device_class_init (MetaKmsImplDeviceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->get_property = meta_kms_impl_device_get_property;
object_class->set_property = meta_kms_impl_device_set_property;
object_class->finalize = meta_kms_impl_device_finalize;
obj_props[PROP_DEVICE] =
g_param_spec_object ("device", NULL, NULL,
META_TYPE_KMS_DEVICE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS);
obj_props[PROP_IMPL] =
g_param_spec_object ("impl", NULL, NULL,
META_TYPE_KMS_IMPL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS);
obj_props[PROP_PATH] =
g_param_spec_string ("path", NULL, NULL,
NULL,
G_PARAM_WRITABLE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS);
obj_props[PROP_FLAGS] =
g_param_spec_flags ("flags", NULL, NULL,
META_TYPE_KMS_DEVICE_FLAG,
META_KMS_DEVICE_FLAG_NONE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, N_PROPS, obj_props);
}