mutter/src/backends/native/meta-kms-impl-device-atomic.c
2022-03-04 23:12:24 +00:00

1276 lines
42 KiB
C

/*
* Copyright (C) 2019-2020 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#include "config.h"
#include "backends/native/meta-kms-impl-device-atomic.h"
#include "backends/native/meta-backend-native-private.h"
#include "backends/native/meta-kms-connector-private.h"
#include "backends/native/meta-kms-crtc-private.h"
#include "backends/native/meta-kms-device-private.h"
#include "backends/native/meta-kms-mode-private.h"
#include "backends/native/meta-kms-plane-private.h"
#include "backends/native/meta-kms-private.h"
#include "backends/native/meta-kms-update-private.h"
typedef gboolean (* MetaKmsAtomicProcessFunc) (MetaKmsImplDevice *impl_device,
MetaKmsUpdate *update,
drmModeAtomicReq *req,
GArray *blob_ids,
gpointer entry_data,
gpointer user_data,
GError **error);
struct _MetaKmsImplDeviceAtomic
{
MetaKmsImplDevice parent;
GHashTable *page_flip_datas;
};
static GInitableIface *initable_parent_iface;
static void
initable_iface_init (GInitableIface *iface);
G_DEFINE_TYPE_WITH_CODE (MetaKmsImplDeviceAtomic, meta_kms_impl_device_atomic,
META_TYPE_KMS_IMPL_DEVICE,
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
initable_iface_init))
static uint32_t
store_new_blob (MetaKmsImplDevice *impl_device,
GArray *blob_ids,
const void *data,
size_t size,
GError **error)
{
int fd = meta_kms_impl_device_get_fd (impl_device);
uint32_t blob_id;
int ret;
ret = drmModeCreatePropertyBlob (fd, data, size, &blob_id);
if (ret < 0)
{
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (-ret),
"drmModeCreatePropertyBlob: %s", g_strerror (-ret));
return 0;
}
g_array_append_val (blob_ids, blob_id);
return blob_id;
}
static void
release_blob_ids (MetaKmsImplDevice *impl_device,
GArray *blob_ids)
{
int fd = meta_kms_impl_device_get_fd (impl_device);
unsigned int i;
for (i = 0; i < blob_ids->len; i++)
{
uint32_t blob_id = g_array_index (blob_ids, uint32_t, i);
drmModeDestroyPropertyBlob (fd, blob_id);
}
}
static gboolean
add_connector_property (MetaKmsImplDevice *impl_device,
MetaKmsConnector *connector,
drmModeAtomicReq *req,
MetaKmsConnectorProp prop,
uint64_t value,
GError **error)
{
int ret;
uint32_t prop_id;
prop_id = meta_kms_connector_get_prop_id (connector, prop);
if (!prop_id)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"Connector property '%s' not found",
meta_kms_connector_get_prop_name (connector, prop));
return FALSE;
}
meta_topic (META_DEBUG_KMS,
"[atomic] Setting connector %u (%s) property '%s' (%u) to %"
G_GUINT64_FORMAT,
meta_kms_connector_get_id (connector),
meta_kms_impl_device_get_path (impl_device),
meta_kms_connector_get_prop_name (connector, prop),
meta_kms_connector_get_prop_id (connector, prop),
value);
ret = drmModeAtomicAddProperty (req,
meta_kms_connector_get_id (connector),
prop_id,
value);
if (ret < 0)
{
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (-ret),
"drmModeAtomicAddProperty, connector: %u, prop id: %u: %s",
meta_kms_connector_get_id (connector),
prop_id,
g_strerror (-ret));
return FALSE;
}
return TRUE;
}
static gboolean
process_connector_update (MetaKmsImplDevice *impl_device,
MetaKmsUpdate *update,
drmModeAtomicReq *req,
GArray *blob_ids,
gpointer update_entry,
gpointer user_data,
GError **error)
{
MetaKmsConnectorUpdate *connector_update = update_entry;
MetaKmsConnector *connector = connector_update->connector;
if (connector_update->underscanning.has_update &&
connector_update->underscanning.is_active)
{
meta_topic (META_DEBUG_KMS,
"[atomic] Setting underscanning on connector %u (%s) to "
"%" G_GUINT64_FORMAT "x%" G_GUINT64_FORMAT,
meta_kms_connector_get_id (connector),
meta_kms_impl_device_get_path (impl_device),
connector_update->underscanning.hborder,
connector_update->underscanning.vborder);
if (!add_connector_property (impl_device,
connector, req,
META_KMS_CONNECTOR_PROP_UNDERSCAN,
1,
error))
return FALSE;
if (!add_connector_property (impl_device,
connector, req,
META_KMS_CONNECTOR_PROP_UNDERSCAN_HBORDER,
connector_update->underscanning.hborder,
error))
return FALSE;
if (!add_connector_property (impl_device,
connector, req,
META_KMS_CONNECTOR_PROP_UNDERSCAN_VBORDER,
connector_update->underscanning.vborder,
error))
return FALSE;
}
else if (connector_update->underscanning.has_update)
{
meta_topic (META_DEBUG_KMS,
"[atomic] Unsetting underscanning on connector %u (%s)",
meta_kms_connector_get_id (connector),
meta_kms_impl_device_get_path (impl_device));
if (!add_connector_property (impl_device,
connector, req,
META_KMS_CONNECTOR_PROP_UNDERSCAN,
0,
error))
return FALSE;
}
if (connector_update->privacy_screen.has_update)
{
meta_topic (META_DEBUG_KMS,
"[atomic] Toggling privacy screen to %d on connector %u (%s)",
connector_update->privacy_screen.is_enabled,
meta_kms_connector_get_id (connector),
meta_kms_impl_device_get_path (impl_device));
if (!add_connector_property (impl_device,
connector, req,
META_KMS_CONNECTOR_PROP_PRIVACY_SCREEN_SW_STATE,
connector_update->privacy_screen.is_enabled,
error))
return FALSE;
}
return TRUE;
}
static gboolean
add_crtc_property (MetaKmsImplDevice *impl_device,
MetaKmsCrtc *crtc,
drmModeAtomicReq *req,
MetaKmsCrtcProp prop,
uint64_t value,
GError **error)
{
int ret;
uint32_t prop_id;
prop_id = meta_kms_crtc_get_prop_id (crtc, prop);
if (!prop_id)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"CRTC property (%s) not found",
meta_kms_crtc_get_prop_name (crtc, prop));
return FALSE;
}
meta_topic (META_DEBUG_KMS,
"[atomic] Setting CRTC %u (%s) property '%s' (%u) to %"
G_GUINT64_FORMAT,
meta_kms_crtc_get_id (crtc),
meta_kms_impl_device_get_path (impl_device),
meta_kms_crtc_get_prop_name (crtc, prop),
meta_kms_crtc_get_prop_id (crtc, prop),
value);
ret = drmModeAtomicAddProperty (req,
meta_kms_crtc_get_id (crtc),
prop_id,
value);
if (ret < 0)
{
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (-ret),
"drmModeAtomicAddProperty, crtc: %u, prop: %s (%u): %s",
meta_kms_crtc_get_id (crtc),
meta_kms_crtc_get_prop_name (crtc, prop),
prop_id,
g_strerror (-ret));
return FALSE;
}
return TRUE;
}
static gboolean
process_mode_set (MetaKmsImplDevice *impl_device,
MetaKmsUpdate *update,
drmModeAtomicReq *req,
GArray *blob_ids,
gpointer update_entry,
gpointer user_data,
GError **error)
{
MetaKmsModeSet *mode_set = update_entry;
MetaKmsCrtc *crtc = mode_set->crtc;
MetaKmsMode *mode;
mode = (MetaKmsMode *) mode_set->mode;
if (mode)
{
uint32_t mode_id;
GList *l;
mode_id = meta_kms_mode_create_blob_id (mode, error);
if (mode_id == 0)
return FALSE;
g_array_append_val (blob_ids, mode_id);
meta_topic (META_DEBUG_KMS,
"[atomic] Setting mode of CRTC %u (%s) to %s",
meta_kms_crtc_get_id (crtc),
meta_kms_impl_device_get_path (impl_device),
meta_kms_mode_get_name (mode));
if (!add_crtc_property (impl_device,
crtc, req,
META_KMS_CRTC_PROP_MODE_ID,
mode_id,
error))
return FALSE;
if (!add_crtc_property (impl_device,
crtc, req,
META_KMS_CRTC_PROP_ACTIVE,
1,
error))
return FALSE;
for (l = mode_set->connectors; l; l = l->next)
{
MetaKmsConnector *connector = l->data;
if (!add_connector_property (impl_device,
connector, req,
META_KMS_CONNECTOR_PROP_CRTC_ID,
meta_kms_crtc_get_id (crtc),
error))
return FALSE;
}
}
else
{
if (!add_crtc_property (impl_device,
crtc, req,
META_KMS_CRTC_PROP_MODE_ID,
0,
error))
return FALSE;
if (!add_crtc_property (impl_device,
crtc, req,
META_KMS_CRTC_PROP_ACTIVE,
0,
error))
return FALSE;
meta_topic (META_DEBUG_KMS,
"[atomic] Unsetting mode of (%u, %s)",
meta_kms_crtc_get_id (crtc),
meta_kms_impl_device_get_path (impl_device));
}
return TRUE;
}
static gboolean
add_plane_property (MetaKmsImplDevice *impl_device,
MetaKmsPlane *plane,
drmModeAtomicReq *req,
MetaKmsPlaneProp prop,
uint64_t value,
GError **error)
{
int ret;
uint32_t prop_id;
prop_id = meta_kms_plane_get_prop_id (plane, prop);
if (!prop_id)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"Plane property (%s) not found on %u",
meta_kms_plane_get_prop_name (plane, prop),
meta_kms_plane_get_id (plane));
return FALSE;
}
switch (meta_kms_plane_get_prop_internal_type (plane, prop))
{
case META_KMS_PROP_TYPE_RAW:
meta_topic (META_DEBUG_KMS,
"[atomic] Setting plane %u (%s) property '%s' (%u) to %"
G_GUINT64_FORMAT,
meta_kms_plane_get_id (plane),
meta_kms_impl_device_get_path (impl_device),
meta_kms_plane_get_prop_name (plane, prop),
meta_kms_plane_get_prop_id (plane, prop),
value);
break;
case META_KMS_PROP_TYPE_FIXED_16:
meta_topic (META_DEBUG_KMS,
"[atomic] Setting plane %u (%s) property '%s' (%u) to %.2f",
meta_kms_plane_get_id (plane),
meta_kms_impl_device_get_path (impl_device),
meta_kms_plane_get_prop_name (plane, prop),
meta_kms_plane_get_prop_id (plane, prop),
meta_fixed_16_to_double (value));
break;
}
ret = drmModeAtomicAddProperty (req,
meta_kms_plane_get_id (plane),
prop_id,
value);
if (ret < 0)
{
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (-ret),
"drmModeAtomicAddProperty, plane: %u, prop: %s (%u): %s",
meta_kms_plane_get_id (plane),
meta_kms_plane_get_prop_name (plane, prop),
prop_id,
g_strerror (-ret));
return FALSE;
}
return TRUE;
}
static const char *
get_plane_type_string (MetaKmsPlane *plane)
{
switch (meta_kms_plane_get_plane_type (plane))
{
case META_KMS_PLANE_TYPE_PRIMARY:
return "primary";
case META_KMS_PLANE_TYPE_CURSOR:
return "cursor";
case META_KMS_PLANE_TYPE_OVERLAY:
return "overlay";
}
g_assert_not_reached ();
}
static gboolean
process_plane_assignment (MetaKmsImplDevice *impl_device,
MetaKmsUpdate *update,
drmModeAtomicReq *req,
GArray *blob_ids,
gpointer update_entry,
gpointer user_data,
GError **error)
{
MetaKmsPlaneAssignment *plane_assignment = update_entry;
MetaKmsPlane *plane = plane_assignment->plane;
MetaDrmBuffer *buffer;
MetaKmsFbDamage *fb_damage;
uint32_t prop_id;
buffer = plane_assignment->buffer;
if (buffer && !meta_drm_buffer_ensure_fb_id (buffer, error))
return FALSE;
meta_topic (META_DEBUG_KMS,
"[atomic] Assigning %s plane (%u, %s) to %u, "
"%hdx%hd+%hd+%hd -> %dx%d+%d+%d",
get_plane_type_string (plane),
meta_kms_plane_get_id (plane),
meta_kms_impl_device_get_path (impl_device),
buffer ? meta_drm_buffer_get_fb_id (buffer) : 0,
meta_fixed_16_to_int (plane_assignment->src_rect.width),
meta_fixed_16_to_int (plane_assignment->src_rect.height),
meta_fixed_16_to_int (plane_assignment->src_rect.x),
meta_fixed_16_to_int (plane_assignment->src_rect.y),
plane_assignment->dst_rect.width,
plane_assignment->dst_rect.height,
plane_assignment->dst_rect.x,
plane_assignment->dst_rect.y);
if (buffer)
{
int i;
struct {
MetaKmsPlaneProp prop;
uint64_t value;
} props[] = {
{
.prop = META_KMS_PLANE_PROP_FB_ID,
.value = meta_drm_buffer_get_fb_id (buffer),
},
{
.prop = META_KMS_PLANE_PROP_CRTC_ID,
.value = meta_kms_crtc_get_id (plane_assignment->crtc),
},
{
.prop = META_KMS_PLANE_PROP_SRC_X,
.value = plane_assignment->src_rect.x,
},
{
.prop = META_KMS_PLANE_PROP_SRC_Y,
.value = plane_assignment->src_rect.y,
},
{
.prop = META_KMS_PLANE_PROP_SRC_W,
.value = plane_assignment->src_rect.width,
},
{
.prop = META_KMS_PLANE_PROP_SRC_H,
.value = plane_assignment->src_rect.height,
},
{
.prop = META_KMS_PLANE_PROP_CRTC_X,
.value = plane_assignment->dst_rect.x,
},
{
.prop = META_KMS_PLANE_PROP_CRTC_Y,
.value = plane_assignment->dst_rect.y,
},
{
.prop = META_KMS_PLANE_PROP_CRTC_W,
.value = plane_assignment->dst_rect.width,
},
{
.prop = META_KMS_PLANE_PROP_CRTC_H,
.value = plane_assignment->dst_rect.height,
},
};
for (i = 0; i < G_N_ELEMENTS (props); i++)
{
if (!add_plane_property (impl_device,
plane, req,
props[i].prop,
props[i].value,
error))
return FALSE;
}
}
else
{
int i;
struct {
MetaKmsPlaneProp prop;
uint64_t value;
} props[] = {
{
.prop = META_KMS_PLANE_PROP_FB_ID,
.value = 0,
},
{
.prop = META_KMS_PLANE_PROP_CRTC_ID,
.value = 0,
},
};
for (i = 0; i < G_N_ELEMENTS (props); i++)
{
if (!add_plane_property (impl_device,
plane, req,
props[i].prop,
props[i].value,
error))
return FALSE;
}
}
if (plane_assignment->rotation)
{
meta_topic (META_DEBUG_KMS,
"[atomic] Setting plane (%u, %s) rotation to %"
G_GUINT64_FORMAT,
meta_kms_plane_get_id (plane),
meta_kms_impl_device_get_path (impl_device),
plane_assignment->rotation);
if (!add_plane_property (impl_device,
plane, req,
META_KMS_PLANE_PROP_ROTATION,
plane_assignment->rotation,
error))
return FALSE;
}
fb_damage = plane_assignment->fb_damage;
if (fb_damage &&
meta_kms_plane_get_prop_id (plane,
META_KMS_PLANE_PROP_FB_DAMAGE_CLIPS_ID))
{
meta_topic (META_DEBUG_KMS,
"[atomic] Setting %d damage clips on %u",
fb_damage->n_rects,
meta_kms_plane_get_id (plane));
prop_id = store_new_blob (impl_device,
blob_ids,
fb_damage->rects,
fb_damage->n_rects *
sizeof (struct drm_mode_rect),
error);
if (!prop_id)
return FALSE;
if (!add_plane_property (impl_device,
plane, req,
META_KMS_PLANE_PROP_FB_DAMAGE_CLIPS_ID,
prop_id,
error))
return FALSE;
}
return TRUE;
}
static gboolean
process_crtc_gamma (MetaKmsImplDevice *impl_device,
MetaKmsUpdate *update,
drmModeAtomicReq *req,
GArray *blob_ids,
gpointer update_entry,
gpointer user_data,
GError **error)
{
MetaKmsCrtcGamma *gamma = update_entry;
MetaKmsCrtc *crtc = gamma->crtc;
struct drm_color_lut drm_color_lut[gamma->size];
int i;
uint32_t color_lut_blob_id;
for (i = 0; i < gamma->size; i++)
{
drm_color_lut[i].red = gamma->red[i];
drm_color_lut[i].green = gamma->green[i];
drm_color_lut[i].blue = gamma->blue[i];
}
color_lut_blob_id = store_new_blob (impl_device,
blob_ids,
drm_color_lut,
sizeof drm_color_lut,
error);
if (!color_lut_blob_id)
return FALSE;
meta_topic (META_DEBUG_KMS,
"[atomic] Setting CRTC (%u, %s) gamma, size: %d",
meta_kms_crtc_get_id (crtc),
meta_kms_impl_device_get_path (impl_device),
gamma->size);
if (!add_crtc_property (impl_device,
crtc, req,
META_KMS_CRTC_PROP_GAMMA_LUT,
color_lut_blob_id,
error))
return FALSE;
return TRUE;
}
static gboolean
process_page_flip_listener (MetaKmsImplDevice *impl_device,
MetaKmsUpdate *update,
drmModeAtomicReq *req,
GArray *blob_ids,
gpointer update_entry,
gpointer user_data,
GError **error)
{
MetaKmsImplDeviceAtomic *impl_device_atomic =
META_KMS_IMPL_DEVICE_ATOMIC (impl_device);
MetaKmsPageFlipListener *listener = update_entry;
MetaKmsPageFlipData *page_flip_data;
uint32_t crtc_id;
gpointer listener_user_data;
GDestroyNotify listener_destroy_notify;
crtc_id = meta_kms_crtc_get_id (listener->crtc);
page_flip_data = g_hash_table_lookup (impl_device_atomic->page_flip_datas,
GUINT_TO_POINTER (crtc_id));
if (!page_flip_data)
{
page_flip_data = meta_kms_page_flip_data_new (impl_device,
listener->crtc);
g_hash_table_insert (impl_device_atomic->page_flip_datas,
GUINT_TO_POINTER (crtc_id),
page_flip_data);
meta_kms_impl_device_hold_fd (impl_device);
meta_topic (META_DEBUG_KMS,
"[atomic] Adding page flip data for (%u, %s): %p",
crtc_id,
meta_kms_impl_device_get_path (impl_device),
page_flip_data);
}
listener_user_data = g_steal_pointer (&listener->user_data);
listener_destroy_notify = g_steal_pointer (&listener->destroy_notify);
meta_kms_page_flip_data_add_listener (page_flip_data,
listener->vtable,
listener->flags,
listener_user_data,
listener_destroy_notify);
return TRUE;
}
static gboolean
discard_page_flip_listener (MetaKmsImplDevice *impl_device,
MetaKmsUpdate *update,
drmModeAtomicReq *req,
GArray *blob_ids,
gpointer update_entry,
gpointer user_data,
GError **error)
{
MetaKmsPageFlipListener *listener = update_entry;
GError *commit_error = user_data;
MetaKmsPageFlipData *page_flip_data;
gpointer listener_user_data;
GDestroyNotify listener_destroy_notify;
page_flip_data = meta_kms_page_flip_data_new (impl_device,
listener->crtc);
meta_topic (META_DEBUG_KMS,
"[atomic] Creating transient page flip data for (%u, %s): %p",
meta_kms_crtc_get_id (listener->crtc),
meta_kms_impl_device_get_path (impl_device),
page_flip_data);
listener_user_data = g_steal_pointer (&listener->user_data);
listener_destroy_notify = g_steal_pointer (&listener->destroy_notify);
meta_kms_page_flip_data_add_listener (page_flip_data,
listener->vtable,
listener->flags,
listener_user_data,
listener_destroy_notify);
meta_kms_page_flip_data_discard_in_impl (page_flip_data, commit_error);
return TRUE;
}
static gboolean
process_entries (MetaKmsImplDevice *impl_device,
MetaKmsUpdate *update,
drmModeAtomicReq *req,
GArray *blob_ids,
GList *entries,
gpointer user_data,
MetaKmsAtomicProcessFunc func,
GError **error)
{
GList *l;
for (l = entries; l; l = l->next)
{
if (!func (impl_device,
update,
req,
blob_ids,
l->data,
user_data,
error))
return FALSE;
}
return TRUE;
}
static void
atomic_page_flip_handler (int fd,
unsigned int sequence,
unsigned int tv_sec,
unsigned int tv_usec,
unsigned int crtc_id,
void *user_data)
{
MetaKmsImplDeviceAtomic *impl_device_atomic = user_data;
MetaKmsImplDevice *impl_device = META_KMS_IMPL_DEVICE (impl_device_atomic);
MetaKmsPageFlipData *page_flip_data = NULL;
g_hash_table_steal_extended (impl_device_atomic->page_flip_datas,
GUINT_TO_POINTER (crtc_id),
NULL,
(gpointer *) &page_flip_data);
meta_topic (META_DEBUG_KMS,
"[atomic] Page flip callback for CRTC (%u, %s), data: %p",
crtc_id, meta_kms_impl_device_get_path (impl_device),
page_flip_data);
if (!page_flip_data)
return;
meta_kms_impl_device_unhold_fd (impl_device);
meta_kms_page_flip_data_set_timings_in_impl (page_flip_data,
sequence, tv_sec, tv_usec);
meta_kms_impl_device_handle_page_flip_callback (impl_device, page_flip_data);
}
static void
meta_kms_impl_device_atomic_setup_drm_event_context (MetaKmsImplDevice *impl_device,
drmEventContext *drm_event_context)
{
drm_event_context->version = 3;
drm_event_context->page_flip_handler2 = atomic_page_flip_handler;
}
static const char *
commit_flags_string (uint32_t commit_flags)
{
static char static_commit_flags_string[255];
const char *commit_flag_strings[4] = { NULL };
int i = 0;
g_autofree char *commit_flags_string = NULL;
if (commit_flags & DRM_MODE_ATOMIC_NONBLOCK)
commit_flag_strings[i++] = "ATOMIC_NONBLOCK";
if (commit_flags & DRM_MODE_ATOMIC_ALLOW_MODESET)
commit_flag_strings[i++] = "ATOMIC_ALLOW_MODESET";
if (commit_flags & DRM_MODE_PAGE_FLIP_EVENT)
commit_flag_strings[i++] = "PAGE_FLIP_EVENT";
if (commit_flags & DRM_MODE_ATOMIC_TEST_ONLY)
commit_flag_strings[i++] = "TEST_ONLY";
commit_flags_string = g_strjoinv ("|", (char **) commit_flag_strings);
strncpy (static_commit_flags_string, commit_flags_string,
(sizeof static_commit_flags_string) - 1);
return static_commit_flags_string;
}
static gboolean
disable_connectors (MetaKmsImplDevice *impl_device,
drmModeAtomicReq *req,
GError **error)
{
GList *l;
for (l = meta_kms_impl_device_peek_connectors (impl_device); l; l = l->next)
{
MetaKmsConnector *connector = l->data;
if (!add_connector_property (impl_device,
connector, req,
META_KMS_CONNECTOR_PROP_CRTC_ID,
0,
error))
return FALSE;
}
return TRUE;
}
static gboolean
disable_planes (MetaKmsImplDevice *impl_device,
drmModeAtomicReq *req,
GError **error)
{
GList *l;
for (l = meta_kms_impl_device_peek_planes (impl_device); l; l = l->next)
{
MetaKmsPlane *plane = l->data;
if (!add_plane_property (impl_device,
plane, req,
META_KMS_PLANE_PROP_CRTC_ID,
0,
error))
return FALSE;
if (!add_plane_property (impl_device,
plane, req,
META_KMS_PLANE_PROP_FB_ID,
0,
error))
return FALSE;
}
return TRUE;
}
static gboolean
disable_crtcs (MetaKmsImplDevice *impl_device,
drmModeAtomicReq *req,
GError **error)
{
GList *l;
for (l = meta_kms_impl_device_peek_crtcs (impl_device); l; l = l->next)
{
MetaKmsCrtc *crtc = l->data;
if (!add_crtc_property (impl_device,
crtc, req,
META_KMS_CRTC_PROP_ACTIVE,
0,
error))
return FALSE;
if (!add_crtc_property (impl_device,
crtc, req,
META_KMS_CRTC_PROP_MODE_ID,
0,
error))
return FALSE;
}
return TRUE;
}
static gboolean
disable_planes_and_connectors (MetaKmsImplDevice *impl_device,
drmModeAtomicReq *req,
GError **error)
{
if (!disable_connectors (impl_device, req, error))
return FALSE;
if (!disable_planes (impl_device, req, error))
return FALSE;
return TRUE;
}
static MetaKmsFeedback *
meta_kms_impl_device_atomic_process_update (MetaKmsImplDevice *impl_device,
MetaKmsUpdate *update,
MetaKmsUpdateFlag flags)
{
GError *error = NULL;
GList *failed_planes = NULL;
drmModeAtomicReq *req;
g_autoptr (GArray) blob_ids = NULL;
int fd;
uint32_t commit_flags = 0;
int ret;
blob_ids = g_array_new (FALSE, TRUE, sizeof (uint32_t));
meta_topic (META_DEBUG_KMS,
"[atomic] Processing update %" G_GUINT64_FORMAT,
meta_kms_update_get_sequence_number (update));
req = drmModeAtomicAlloc ();
if (!req)
{
g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create atomic transaction request: %s",
g_strerror (errno));
goto err;
}
if (meta_kms_update_get_mode_sets (update))
{
if (!disable_planes_and_connectors (impl_device, req, &error))
goto err;
}
if (!process_entries (impl_device,
update,
req,
blob_ids,
meta_kms_update_get_connector_updates (update),
NULL,
process_connector_update,
&error))
goto err;
if (!process_entries (impl_device,
update,
req,
blob_ids,
meta_kms_update_get_mode_sets (update),
NULL,
process_mode_set,
&error))
goto err;
if (!process_entries (impl_device,
update,
req,
blob_ids,
meta_kms_update_get_plane_assignments (update),
NULL,
process_plane_assignment,
&error))
goto err;
if (!process_entries (impl_device,
update,
req,
blob_ids,
meta_kms_update_get_crtc_gammas (update),
NULL,
process_crtc_gamma,
&error))
goto err;
if (meta_kms_update_get_mode_sets (update))
commit_flags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
else
commit_flags |= DRM_MODE_ATOMIC_NONBLOCK;
if (meta_kms_update_get_page_flip_listeners (update))
commit_flags |= DRM_MODE_PAGE_FLIP_EVENT;
if (flags & META_KMS_UPDATE_FLAG_TEST_ONLY)
commit_flags |= DRM_MODE_ATOMIC_TEST_ONLY;
meta_topic (META_DEBUG_KMS,
"[atomic] Committing update %" G_GUINT64_FORMAT ", flags: %s",
meta_kms_update_get_sequence_number (update),
commit_flags_string (commit_flags));
fd = meta_kms_impl_device_get_fd (impl_device);
ret = drmModeAtomicCommit (fd, req, commit_flags, impl_device);
drmModeAtomicFree (req);
if (ret < 0)
{
g_set_error (&error, G_IO_ERROR, g_io_error_from_errno (-ret),
"drmModeAtomicCommit: %s", g_strerror (-ret));
goto err;
}
process_entries (impl_device,
update,
req,
blob_ids,
meta_kms_update_get_page_flip_listeners (update),
NULL,
process_page_flip_listener,
NULL);
release_blob_ids (impl_device, blob_ids);
return meta_kms_feedback_new_passed (NULL);
err:
meta_topic (META_DEBUG_KMS, "[atomic] KMS update failed: %s", error->message);
if (!(flags & META_KMS_UPDATE_FLAG_PRESERVE_ON_ERROR))
{
process_entries (impl_device,
update,
req,
blob_ids,
meta_kms_update_get_page_flip_listeners (update),
error,
discard_page_flip_listener,
NULL);
}
release_blob_ids (impl_device, blob_ids);
return meta_kms_feedback_new_failed (failed_planes, error);
}
static void
meta_kms_impl_device_atomic_disable (MetaKmsImplDevice *impl_device)
{
g_autoptr (GError) error = NULL;
drmModeAtomicReq *req;
int fd;
int ret;
meta_topic (META_DEBUG_KMS, "[atomic] Disabling '%s'",
meta_kms_impl_device_get_path (impl_device));
req = drmModeAtomicAlloc ();
if (!req)
{
g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create atomic transaction request: %s",
g_strerror (errno));
goto err;
}
if (!disable_connectors (impl_device, req, &error))
goto err;
if (!disable_planes (impl_device, req, &error))
goto err;
if (!disable_crtcs (impl_device, req, &error))
goto err;
meta_topic (META_DEBUG_KMS, "[atomic] Committing disable-device transaction");
fd = meta_kms_impl_device_get_fd (impl_device);
ret = drmModeAtomicCommit (fd, req, DRM_MODE_ATOMIC_ALLOW_MODESET, impl_device);
drmModeAtomicFree (req);
if (ret < 0)
{
g_set_error (&error, G_IO_ERROR, g_io_error_from_errno (-ret),
"drmModeAtomicCommit: %s", g_strerror (-ret));
goto err;
}
return;
err:
g_warning ("[atomic] Failed to disable device '%s': %s",
meta_kms_impl_device_get_path (impl_device),
error->message);
}
static void
meta_kms_impl_device_atomic_handle_page_flip_callback (MetaKmsImplDevice *impl_device,
MetaKmsPageFlipData *page_flip_data)
{
meta_kms_page_flip_data_flipped_in_impl (page_flip_data);
}
static void
meta_kms_impl_device_atomic_discard_pending_page_flips (MetaKmsImplDevice *impl_device)
{
}
static gboolean
dispose_page_flip_data (gpointer key,
gpointer value,
gpointer user_data)
{
MetaKmsPageFlipData *page_flip_data = value;
MetaKmsImplDevice *impl_device = user_data;
meta_kms_page_flip_data_discard_in_impl (page_flip_data, NULL);
meta_kms_impl_device_unhold_fd (impl_device);
return TRUE;
}
static void
meta_kms_impl_device_atomic_prepare_shutdown (MetaKmsImplDevice *impl_device)
{
MetaKmsImplDeviceAtomic *impl_device_atomic =
META_KMS_IMPL_DEVICE_ATOMIC (impl_device);
g_hash_table_foreach_remove (impl_device_atomic->page_flip_datas,
dispose_page_flip_data,
impl_device);
}
static void
meta_kms_impl_device_atomic_finalize (GObject *object)
{
MetaKmsImplDeviceAtomic *impl_device_atomic =
META_KMS_IMPL_DEVICE_ATOMIC (object);
g_assert (g_hash_table_size (impl_device_atomic->page_flip_datas) == 0);
g_hash_table_unref (impl_device_atomic->page_flip_datas);
G_OBJECT_CLASS (meta_kms_impl_device_atomic_parent_class)->finalize (object);
}
static MetaDeviceFile *
meta_kms_impl_device_atomic_open_device_file (MetaKmsImplDevice *impl_device,
const char *path,
GError **error)
{
MetaKmsDevice *device = meta_kms_impl_device_get_device (impl_device);
MetaKms *kms = meta_kms_device_get_kms (device);
MetaBackend *backend = meta_kms_get_backend (kms);
MetaDevicePool *device_pool =
meta_backend_native_get_device_pool (META_BACKEND_NATIVE (backend));
g_autoptr (MetaDeviceFile) device_file = NULL;
device_file = meta_device_pool_open (device_pool, path,
META_DEVICE_FILE_FLAG_TAKE_CONTROL,
error);
if (!device_file)
return NULL;
if (!meta_device_file_has_tag (device_file,
META_DEVICE_FILE_TAG_KMS,
META_KMS_DEVICE_FILE_TAG_ATOMIC))
{
int fd = meta_device_file_get_fd (device_file);
g_warn_if_fail (!meta_device_file_has_tag (device_file,
META_DEVICE_FILE_TAG_KMS,
META_KMS_DEVICE_FILE_TAG_SIMPLE));
if (drmSetClientCap (fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1) != 0)
{
g_set_error (error, META_KMS_ERROR, META_KMS_ERROR_NOT_SUPPORTED,
"DRM_CLIENT_CAP_UNIVERSAL_PLANES not supported");
return NULL;
}
if (drmSetClientCap (fd, DRM_CLIENT_CAP_ATOMIC, 1) != 0)
{
g_set_error (error, META_KMS_ERROR, META_KMS_ERROR_NOT_SUPPORTED,
"DRM_CLIENT_CAP_ATOMIC not supported");
return NULL;
}
meta_device_file_tag (device_file,
META_DEVICE_FILE_TAG_KMS,
META_KMS_DEVICE_FILE_TAG_ATOMIC);
}
return g_steal_pointer (&device_file);
}
static gboolean
is_atomic_allowed (const char *driver_name)
{
const char *atomic_driver_deny_list[] = {
"qxl",
"vmwgfx",
"vboxvideo",
"nvidia-drm",
"virtio_gpu",
NULL,
};
return !g_strv_contains (atomic_driver_deny_list, driver_name);
}
static gboolean
meta_kms_impl_device_atomic_initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
MetaKmsImplDevice *impl_device = META_KMS_IMPL_DEVICE (initable);
const char *atomic_kms_enable_env;
atomic_kms_enable_env = getenv ("MUTTER_DEBUG_ENABLE_ATOMIC_KMS");
if (atomic_kms_enable_env && g_strcmp0 (atomic_kms_enable_env, "1") != 0)
{
g_set_error (error, META_KMS_ERROR, META_KMS_ERROR_USER_INHIBITED,
"Atomic mode setting disable via env var");
return FALSE;
}
if (!initable_parent_iface->init (initable, cancellable, error))
return FALSE;
if (!is_atomic_allowed (meta_kms_impl_device_get_driver_name (impl_device)))
{
g_set_error (error, META_KMS_ERROR, META_KMS_ERROR_DENY_LISTED,
"Atomic mode setting disable via driver deny list");
return FALSE;
}
if (!meta_kms_impl_device_init_mode_setting (impl_device, error))
return FALSE;
g_message ("Added device '%s' (%s) using atomic mode setting.",
meta_kms_impl_device_get_path (impl_device),
meta_kms_impl_device_get_driver_name (impl_device));
return TRUE;
}
static void
meta_kms_impl_device_atomic_init (MetaKmsImplDeviceAtomic *impl_device_atomic)
{
impl_device_atomic->page_flip_datas = g_hash_table_new (NULL, NULL);
}
static void
initable_iface_init (GInitableIface *iface)
{
initable_parent_iface = g_type_interface_peek_parent (iface);
iface->init = meta_kms_impl_device_atomic_initable_init;
}
static void
meta_kms_impl_device_atomic_class_init (MetaKmsImplDeviceAtomicClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
MetaKmsImplDeviceClass *impl_device_class =
META_KMS_IMPL_DEVICE_CLASS (klass);
object_class->finalize = meta_kms_impl_device_atomic_finalize;
impl_device_class->open_device_file =
meta_kms_impl_device_atomic_open_device_file;
impl_device_class->setup_drm_event_context =
meta_kms_impl_device_atomic_setup_drm_event_context;
impl_device_class->process_update =
meta_kms_impl_device_atomic_process_update;
impl_device_class->disable =
meta_kms_impl_device_atomic_disable;
impl_device_class->handle_page_flip_callback =
meta_kms_impl_device_atomic_handle_page_flip_callback;
impl_device_class->discard_pending_page_flips =
meta_kms_impl_device_atomic_discard_pending_page_flips;
impl_device_class->prepare_shutdown =
meta_kms_impl_device_atomic_prepare_shutdown;
}