6e3ecadb79
Even when a direct client buffer has a compatible format, stride and modifier for direct scanout, drmModePageFlip() may still fail sometimes. From testing, it has been observed that it may seemingly randomly fail with ENOSPC, where all subsequent attempts later on the same CRTC failing with EBUSY. Handle this by falling back to flipping after having composited a full frame again. Closes: https://gitlab.gnome.org/GNOME/mutter/-/issues/1410
1127 lines
36 KiB
C
1127 lines
36 KiB
C
/*
|
|
* Copyright (C) 2018-2019 Red Hat
|
|
* Copyright (C) 2019-2020 DisplayLink (UK) Ltd.
|
|
*
|
|
* 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-simple.h"
|
|
|
|
#include <errno.h>
|
|
#include <gbm.h>
|
|
#include <xf86drmMode.h>
|
|
|
|
#include "backends/native/meta-kms-connector.h"
|
|
#include "backends/native/meta-kms-crtc.h"
|
|
#include "backends/native/meta-kms-device-private.h"
|
|
#include "backends/native/meta-kms-page-flip-private.h"
|
|
#include "backends/native/meta-kms-plane.h"
|
|
#include "backends/native/meta-kms-private.h"
|
|
#include "backends/native/meta-kms-update-private.h"
|
|
#include "backends/native/meta-kms-utils.h"
|
|
|
|
typedef struct _CachedModeSet
|
|
{
|
|
GList *connectors;
|
|
drmModeModeInfo *drm_mode;
|
|
} CachedModeSet;
|
|
|
|
struct _MetaKmsImplSimple
|
|
{
|
|
MetaKmsImpl parent;
|
|
|
|
GSource *mode_set_fallback_feedback_source;
|
|
GList *mode_set_fallback_page_flip_datas;
|
|
|
|
GList *pending_page_flip_retries;
|
|
GSource *retry_page_flips_source;
|
|
|
|
GList *postponed_page_flip_datas;
|
|
GList *postponed_mode_set_fallback_datas;
|
|
|
|
GHashTable *cached_mode_sets;
|
|
};
|
|
|
|
G_DEFINE_TYPE (MetaKmsImplSimple, meta_kms_impl_simple,
|
|
META_TYPE_KMS_IMPL)
|
|
|
|
static void
|
|
flush_postponed_page_flip_datas (MetaKmsImplSimple *impl_simple);
|
|
|
|
MetaKmsImplSimple *
|
|
meta_kms_impl_simple_new (MetaKms *kms,
|
|
GError **error)
|
|
{
|
|
return g_object_new (META_TYPE_KMS_IMPL_SIMPLE,
|
|
"kms", kms,
|
|
NULL);
|
|
}
|
|
|
|
static gboolean
|
|
process_connector_property (MetaKmsImpl *impl,
|
|
MetaKmsUpdate *update,
|
|
gpointer update_entry,
|
|
GError **error)
|
|
{
|
|
MetaKmsConnectorProperty *connector_property = update_entry;
|
|
MetaKmsConnector *connector = connector_property->connector;
|
|
MetaKmsDevice *device = meta_kms_connector_get_device (connector);
|
|
MetaKmsImplDevice *impl_device = meta_kms_device_get_impl_device (device);
|
|
int fd;
|
|
int ret;
|
|
|
|
fd = meta_kms_impl_device_get_fd (impl_device);
|
|
|
|
ret = drmModeObjectSetProperty (fd,
|
|
meta_kms_connector_get_id (connector),
|
|
DRM_MODE_OBJECT_CONNECTOR,
|
|
connector_property->prop_id,
|
|
connector_property->value);
|
|
if (ret != 0)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (-ret),
|
|
"Failed to set connector %u property %u: %s",
|
|
meta_kms_connector_get_id (connector),
|
|
connector_property->prop_id,
|
|
g_strerror (-ret));
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
process_plane_property (MetaKmsImpl *impl,
|
|
MetaKmsPlane *plane,
|
|
MetaKmsProperty *prop,
|
|
GError **error)
|
|
{
|
|
MetaKmsDevice *device = meta_kms_plane_get_device (plane);
|
|
MetaKmsImplDevice *impl_device = meta_kms_device_get_impl_device (device);
|
|
int fd;
|
|
int ret;
|
|
|
|
fd = meta_kms_impl_device_get_fd (impl_device);
|
|
|
|
ret = drmModeObjectSetProperty (fd,
|
|
meta_kms_plane_get_id (plane),
|
|
DRM_MODE_OBJECT_PLANE,
|
|
prop->prop_id,
|
|
prop->value);
|
|
if (ret != 0)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (-ret),
|
|
"Failed to set plane %u property %u: %s",
|
|
meta_kms_plane_get_id (plane),
|
|
prop->prop_id,
|
|
g_strerror (-ret));
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static CachedModeSet *
|
|
cached_mode_set_new (GList *connectors,
|
|
const drmModeModeInfo *drm_mode)
|
|
{
|
|
CachedModeSet *cached_mode_set;
|
|
|
|
cached_mode_set = g_new0 (CachedModeSet, 1);
|
|
*cached_mode_set = (CachedModeSet) {
|
|
.connectors = g_list_copy (connectors),
|
|
.drm_mode = g_memdup (drm_mode, sizeof *drm_mode),
|
|
};
|
|
|
|
return cached_mode_set;
|
|
}
|
|
|
|
static void
|
|
cached_mode_set_free (CachedModeSet *cached_mode_set)
|
|
{
|
|
g_list_free (cached_mode_set->connectors);
|
|
g_free (cached_mode_set->drm_mode);
|
|
g_free (cached_mode_set);
|
|
}
|
|
|
|
static void
|
|
fill_connector_ids_array (GList *connectors,
|
|
uint32_t **out_connectors,
|
|
int *out_n_connectors)
|
|
{
|
|
GList *l;
|
|
int i;
|
|
|
|
*out_n_connectors = g_list_length (connectors);
|
|
*out_connectors = g_new0 (uint32_t, *out_n_connectors);
|
|
i = 0;
|
|
for (l = connectors; l; l = l->next)
|
|
{
|
|
MetaKmsConnector *connector = l->data;
|
|
|
|
(*out_connectors)[i++] = meta_kms_connector_get_id (connector);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
process_mode_set (MetaKmsImpl *impl,
|
|
MetaKmsUpdate *update,
|
|
gpointer update_entry,
|
|
GError **error)
|
|
{
|
|
MetaKmsModeSet *mode_set = update_entry;
|
|
MetaKmsImplSimple *impl_simple = META_KMS_IMPL_SIMPLE (impl);
|
|
MetaKmsCrtc *crtc = mode_set->crtc;
|
|
MetaKmsDevice *device = meta_kms_crtc_get_device (crtc);
|
|
MetaKmsImplDevice *impl_device = meta_kms_device_get_impl_device (device);
|
|
g_autofree uint32_t *connectors = NULL;
|
|
int n_connectors;
|
|
MetaKmsPlaneAssignment *plane_assignment;
|
|
uint32_t x, y;
|
|
uint32_t fb_id;
|
|
int fd;
|
|
int ret;
|
|
|
|
crtc = mode_set->crtc;
|
|
|
|
if (mode_set->drm_mode)
|
|
{
|
|
GList *l;
|
|
|
|
fill_connector_ids_array (mode_set->connectors,
|
|
&connectors,
|
|
&n_connectors);
|
|
|
|
plane_assignment = meta_kms_update_get_primary_plane_assignment (update,
|
|
crtc);
|
|
if (!plane_assignment)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Missing primary plane assignment for legacy mode set on CRTC %u",
|
|
meta_kms_crtc_get_id (crtc));
|
|
return FALSE;
|
|
}
|
|
|
|
x = meta_fixed_16_to_int (plane_assignment->src_rect.x);
|
|
y = meta_fixed_16_to_int (plane_assignment->src_rect.y);
|
|
|
|
for (l = plane_assignment->plane_properties; l; l = l->next)
|
|
{
|
|
MetaKmsProperty *prop = l->data;
|
|
|
|
if (!process_plane_property (impl, plane_assignment->plane,
|
|
prop, error))
|
|
return FALSE;
|
|
}
|
|
|
|
fb_id = plane_assignment->fb_id;
|
|
}
|
|
else
|
|
{
|
|
x = y = 0;
|
|
n_connectors = 0;
|
|
connectors = NULL;
|
|
fb_id = 0;
|
|
}
|
|
|
|
fd = meta_kms_impl_device_get_fd (impl_device);
|
|
ret = drmModeSetCrtc (fd,
|
|
meta_kms_crtc_get_id (crtc),
|
|
fb_id,
|
|
x, y,
|
|
connectors, n_connectors,
|
|
mode_set->drm_mode);
|
|
if (ret != 0)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (-ret),
|
|
"Failed to set mode %s on CRTC %u: %s",
|
|
mode_set->drm_mode ? mode_set->drm_mode->name : "off",
|
|
meta_kms_crtc_get_id (crtc),
|
|
g_strerror (-ret));
|
|
return FALSE;
|
|
}
|
|
|
|
if (mode_set->drm_mode)
|
|
{
|
|
g_hash_table_replace (impl_simple->cached_mode_sets,
|
|
crtc,
|
|
cached_mode_set_new (mode_set->connectors,
|
|
mode_set->drm_mode));
|
|
}
|
|
else
|
|
{
|
|
g_hash_table_remove (impl_simple->cached_mode_sets, crtc);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
process_crtc_gamma (MetaKmsImpl *impl,
|
|
MetaKmsUpdate *update,
|
|
gpointer update_entry,
|
|
GError **error)
|
|
{
|
|
MetaKmsCrtcGamma *gamma = update_entry;
|
|
MetaKmsCrtc *crtc = gamma->crtc;
|
|
MetaKmsDevice *device = meta_kms_crtc_get_device (crtc);
|
|
MetaKmsImplDevice *impl_device = meta_kms_device_get_impl_device (device);
|
|
int fd;
|
|
int ret;
|
|
|
|
fd = meta_kms_impl_device_get_fd (impl_device);
|
|
ret = drmModeCrtcSetGamma (fd, meta_kms_crtc_get_id (crtc),
|
|
gamma->size,
|
|
gamma->red,
|
|
gamma->green,
|
|
gamma->blue);
|
|
if (ret != 0)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (-ret),
|
|
"drmModeCrtcSetGamma on CRTC %u failed: %s",
|
|
meta_kms_crtc_get_id (crtc),
|
|
g_strerror (-ret));
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
is_timestamp_earlier_than (uint64_t ts1,
|
|
uint64_t ts2)
|
|
{
|
|
if (ts1 == ts2)
|
|
return FALSE;
|
|
else
|
|
return ts2 - ts1 < UINT64_MAX / 2;
|
|
}
|
|
|
|
typedef struct _RetryPageFlipData
|
|
{
|
|
MetaKmsCrtc *crtc;
|
|
uint32_t fb_id;
|
|
MetaKmsPageFlipData *page_flip_data;
|
|
float refresh_rate;
|
|
uint64_t retry_time_us;
|
|
MetaKmsCustomPageFlipFunc custom_page_flip_func;
|
|
gpointer custom_page_flip_user_data;
|
|
} RetryPageFlipData;
|
|
|
|
static void
|
|
retry_page_flip_data_free (RetryPageFlipData *retry_page_flip_data)
|
|
{
|
|
g_assert (!retry_page_flip_data->page_flip_data);
|
|
g_free (retry_page_flip_data);
|
|
}
|
|
|
|
static CachedModeSet *
|
|
get_cached_mode_set (MetaKmsImplSimple *impl_simple,
|
|
MetaKmsCrtc *crtc)
|
|
{
|
|
return g_hash_table_lookup (impl_simple->cached_mode_sets, crtc);
|
|
}
|
|
|
|
static float
|
|
get_cached_crtc_refresh_rate (MetaKmsImplSimple *impl_simple,
|
|
MetaKmsCrtc *crtc)
|
|
{
|
|
CachedModeSet *cached_mode_set;
|
|
|
|
cached_mode_set = g_hash_table_lookup (impl_simple->cached_mode_sets,
|
|
crtc);
|
|
g_assert (cached_mode_set);
|
|
|
|
return meta_calculate_drm_mode_refresh_rate (cached_mode_set->drm_mode);
|
|
}
|
|
|
|
static gboolean
|
|
retry_page_flips (gpointer user_data)
|
|
{
|
|
MetaKmsImplSimple *impl_simple = META_KMS_IMPL_SIMPLE (user_data);
|
|
uint64_t now_us;
|
|
GList *l;
|
|
|
|
meta_assert_in_kms_impl (meta_kms_impl_get_kms (META_KMS_IMPL (impl_simple)));
|
|
|
|
now_us = g_source_get_time (impl_simple->retry_page_flips_source);
|
|
|
|
l = impl_simple->pending_page_flip_retries;
|
|
while (l)
|
|
{
|
|
RetryPageFlipData *retry_page_flip_data = l->data;
|
|
MetaKmsCrtc *crtc = retry_page_flip_data->crtc;
|
|
MetaKmsDevice *device = meta_kms_crtc_get_device (crtc);
|
|
MetaKmsImplDevice *impl_device = meta_kms_device_get_impl_device (device);
|
|
GList *l_next = l->next;
|
|
int fd;
|
|
int ret;
|
|
MetaKmsPageFlipData *page_flip_data;
|
|
MetaKmsCustomPageFlipFunc custom_page_flip_func;
|
|
|
|
if (is_timestamp_earlier_than (now_us,
|
|
retry_page_flip_data->retry_time_us))
|
|
{
|
|
l = l_next;
|
|
continue;
|
|
}
|
|
|
|
custom_page_flip_func = retry_page_flip_data->custom_page_flip_func;
|
|
if (custom_page_flip_func)
|
|
{
|
|
ret = custom_page_flip_func (retry_page_flip_data->custom_page_flip_user_data,
|
|
retry_page_flip_data->page_flip_data);
|
|
}
|
|
else
|
|
{
|
|
fd = meta_kms_impl_device_get_fd (impl_device);
|
|
ret = drmModePageFlip (fd,
|
|
meta_kms_crtc_get_id (crtc),
|
|
retry_page_flip_data->fb_id,
|
|
DRM_MODE_PAGE_FLIP_EVENT,
|
|
retry_page_flip_data->page_flip_data);
|
|
}
|
|
|
|
if (ret == -EBUSY)
|
|
{
|
|
float refresh_rate;
|
|
|
|
refresh_rate = get_cached_crtc_refresh_rate (impl_simple, crtc);
|
|
retry_page_flip_data->retry_time_us +=
|
|
(uint64_t) (G_USEC_PER_SEC / refresh_rate);
|
|
l = l_next;
|
|
continue;
|
|
}
|
|
|
|
impl_simple->pending_page_flip_retries =
|
|
g_list_remove_link (impl_simple->pending_page_flip_retries, l);
|
|
|
|
page_flip_data = g_steal_pointer (&retry_page_flip_data->page_flip_data);
|
|
if (ret != 0)
|
|
{
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
g_set_error (&error, G_IO_ERROR, g_io_error_from_errno (-ret),
|
|
"drmModePageFlip on CRTC %u failed: %s",
|
|
meta_kms_crtc_get_id (crtc),
|
|
g_strerror (-ret));
|
|
if (!g_error_matches (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_PERMISSION_DENIED))
|
|
g_critical ("Failed to page flip: %s", error->message);
|
|
|
|
meta_kms_page_flip_data_discard_in_impl (page_flip_data, error);
|
|
}
|
|
|
|
retry_page_flip_data_free (retry_page_flip_data);
|
|
|
|
l = l_next;
|
|
}
|
|
|
|
if (impl_simple->pending_page_flip_retries)
|
|
{
|
|
GList *l;
|
|
uint64_t earliest_retry_time_us = 0;
|
|
|
|
for (l = impl_simple->pending_page_flip_retries; l; l = l->next)
|
|
{
|
|
RetryPageFlipData *retry_page_flip_data = l->data;
|
|
|
|
if (l == impl_simple->pending_page_flip_retries ||
|
|
is_timestamp_earlier_than (retry_page_flip_data->retry_time_us,
|
|
earliest_retry_time_us))
|
|
earliest_retry_time_us = retry_page_flip_data->retry_time_us;
|
|
}
|
|
|
|
g_source_set_ready_time (impl_simple->retry_page_flips_source,
|
|
earliest_retry_time_us);
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
else
|
|
{
|
|
g_clear_pointer (&impl_simple->retry_page_flips_source,
|
|
g_source_unref);
|
|
|
|
flush_postponed_page_flip_datas (impl_simple);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
schedule_retry_page_flip (MetaKmsImplSimple *impl_simple,
|
|
MetaKmsCrtc *crtc,
|
|
uint32_t fb_id,
|
|
float refresh_rate,
|
|
MetaKmsPageFlipData *page_flip_data,
|
|
MetaKmsCustomPageFlipFunc custom_page_flip_func,
|
|
gpointer custom_page_flip_user_data)
|
|
{
|
|
RetryPageFlipData *retry_page_flip_data;
|
|
uint64_t now_us;
|
|
uint64_t retry_time_us;
|
|
|
|
now_us = g_get_monotonic_time ();
|
|
retry_time_us = now_us + (uint64_t) (G_USEC_PER_SEC / refresh_rate);
|
|
|
|
retry_page_flip_data = g_new0 (RetryPageFlipData, 1);
|
|
*retry_page_flip_data = (RetryPageFlipData) {
|
|
.crtc = crtc,
|
|
.fb_id = fb_id,
|
|
.page_flip_data = meta_kms_page_flip_data_ref (page_flip_data),
|
|
.refresh_rate = refresh_rate,
|
|
.retry_time_us = retry_time_us,
|
|
.custom_page_flip_func = custom_page_flip_func,
|
|
.custom_page_flip_user_data = custom_page_flip_user_data,
|
|
};
|
|
|
|
if (!impl_simple->retry_page_flips_source)
|
|
{
|
|
MetaKms *kms = meta_kms_impl_get_kms (META_KMS_IMPL (impl_simple));
|
|
GSource *source;
|
|
|
|
source = meta_kms_add_source_in_impl (kms, retry_page_flips,
|
|
impl_simple, NULL);
|
|
g_source_set_ready_time (source, retry_time_us);
|
|
|
|
impl_simple->retry_page_flips_source = source;
|
|
}
|
|
else
|
|
{
|
|
GList *l;
|
|
|
|
for (l = impl_simple->pending_page_flip_retries; l; l = l->next)
|
|
{
|
|
RetryPageFlipData *pending_retry_page_flip_data = l->data;
|
|
uint64_t pending_retry_time_us =
|
|
pending_retry_page_flip_data->retry_time_us;
|
|
|
|
if (is_timestamp_earlier_than (retry_time_us, pending_retry_time_us))
|
|
{
|
|
g_source_set_ready_time (impl_simple->retry_page_flips_source,
|
|
retry_time_us);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
impl_simple->pending_page_flip_retries =
|
|
g_list_append (impl_simple->pending_page_flip_retries,
|
|
retry_page_flip_data);
|
|
}
|
|
|
|
static void
|
|
invoke_page_flip_datas (GList *page_flip_datas,
|
|
MetaPageFlipDataFeedbackFunc func)
|
|
{
|
|
g_list_foreach (page_flip_datas, (GFunc) func, NULL);
|
|
}
|
|
|
|
static void
|
|
clear_page_flip_datas (GList **page_flip_datas)
|
|
{
|
|
g_list_free_full (*page_flip_datas,
|
|
(GDestroyNotify) meta_kms_page_flip_data_unref);
|
|
*page_flip_datas = NULL;
|
|
}
|
|
|
|
static gboolean
|
|
mode_set_fallback_feedback_idle (gpointer user_data)
|
|
{
|
|
MetaKmsImplSimple *impl_simple = user_data;
|
|
|
|
g_clear_pointer (&impl_simple->mode_set_fallback_feedback_source,
|
|
g_source_unref);
|
|
|
|
if (impl_simple->pending_page_flip_retries)
|
|
{
|
|
impl_simple->postponed_mode_set_fallback_datas =
|
|
g_steal_pointer (&impl_simple->mode_set_fallback_page_flip_datas);
|
|
}
|
|
else
|
|
{
|
|
invoke_page_flip_datas (impl_simple->mode_set_fallback_page_flip_datas,
|
|
meta_kms_page_flip_data_mode_set_fallback_in_impl);
|
|
clear_page_flip_datas (&impl_simple->mode_set_fallback_page_flip_datas);
|
|
}
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static gboolean
|
|
mode_set_fallback (MetaKmsImplSimple *impl_simple,
|
|
MetaKmsUpdate *update,
|
|
MetaKmsPageFlip *page_flip,
|
|
MetaKmsPlaneAssignment *plane_assignment,
|
|
MetaKmsPageFlipData *page_flip_data,
|
|
GError **error)
|
|
{
|
|
MetaKms *kms = meta_kms_impl_get_kms (META_KMS_IMPL (impl_simple));
|
|
MetaKmsCrtc *crtc = page_flip->crtc;
|
|
MetaKmsDevice *device = meta_kms_crtc_get_device (crtc);
|
|
MetaKmsImplDevice *impl_device = meta_kms_device_get_impl_device (device);
|
|
CachedModeSet *cached_mode_set;
|
|
g_autofree uint32_t *connectors = NULL;
|
|
int n_connectors;
|
|
uint32_t x, y;
|
|
int fd;
|
|
int ret;
|
|
|
|
cached_mode_set = g_hash_table_lookup (impl_simple->cached_mode_sets,
|
|
crtc);
|
|
if (!cached_mode_set)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Missing mode set for page flip fallback");
|
|
return FALSE;
|
|
}
|
|
|
|
fill_connector_ids_array (cached_mode_set->connectors,
|
|
&connectors,
|
|
&n_connectors);
|
|
|
|
x = meta_fixed_16_to_int (plane_assignment->src_rect.x);
|
|
y = meta_fixed_16_to_int (plane_assignment->src_rect.y);
|
|
|
|
fd = meta_kms_impl_device_get_fd (impl_device);
|
|
ret = drmModeSetCrtc (fd,
|
|
meta_kms_crtc_get_id (crtc),
|
|
plane_assignment->fb_id,
|
|
x, y,
|
|
connectors, n_connectors,
|
|
cached_mode_set->drm_mode);
|
|
if (ret != 0)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (-ret),
|
|
"drmModeSetCrtc mode '%s' on CRTC %u failed: %s",
|
|
cached_mode_set->drm_mode->name,
|
|
meta_kms_crtc_get_id (crtc),
|
|
g_strerror (-ret));
|
|
return FALSE;
|
|
}
|
|
|
|
if (!impl_simple->mode_set_fallback_feedback_source)
|
|
{
|
|
GSource *source;
|
|
|
|
source = meta_kms_add_source_in_impl (kms,
|
|
mode_set_fallback_feedback_idle,
|
|
impl_simple,
|
|
NULL);
|
|
impl_simple->mode_set_fallback_feedback_source = source;
|
|
}
|
|
|
|
impl_simple->mode_set_fallback_page_flip_datas =
|
|
g_list_prepend (impl_simple->mode_set_fallback_page_flip_datas,
|
|
meta_kms_page_flip_data_ref (page_flip_data));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
process_page_flip (MetaKmsImpl *impl,
|
|
MetaKmsUpdate *update,
|
|
gpointer update_entry,
|
|
GError **error)
|
|
{
|
|
MetaKmsPageFlip *page_flip = update_entry;
|
|
MetaKmsImplSimple *impl_simple = META_KMS_IMPL_SIMPLE (impl);
|
|
MetaKmsCrtc *crtc;
|
|
MetaKmsDevice *device;
|
|
MetaKmsImplDevice *impl_device;
|
|
MetaKmsPlaneAssignment *plane_assignment;
|
|
MetaKmsPageFlipData *page_flip_data;
|
|
MetaKmsCustomPageFlipFunc custom_page_flip_func;
|
|
int fd;
|
|
int ret;
|
|
|
|
crtc = page_flip->crtc;
|
|
plane_assignment = meta_kms_update_get_primary_plane_assignment (update,
|
|
crtc);
|
|
|
|
page_flip_data = meta_kms_page_flip_data_new (impl,
|
|
crtc,
|
|
page_flip->feedback,
|
|
page_flip->user_data);
|
|
|
|
device = meta_kms_crtc_get_device (crtc);
|
|
impl_device = meta_kms_device_get_impl_device (device);
|
|
fd = meta_kms_impl_device_get_fd (impl_device);
|
|
custom_page_flip_func = page_flip->custom_page_flip_func;
|
|
if (custom_page_flip_func)
|
|
{
|
|
ret = custom_page_flip_func (page_flip->custom_page_flip_user_data,
|
|
meta_kms_page_flip_data_ref (page_flip_data));
|
|
}
|
|
else
|
|
{
|
|
ret = drmModePageFlip (fd,
|
|
meta_kms_crtc_get_id (crtc),
|
|
plane_assignment->fb_id,
|
|
DRM_MODE_PAGE_FLIP_EVENT,
|
|
meta_kms_page_flip_data_ref (page_flip_data));
|
|
}
|
|
|
|
if (ret != 0)
|
|
meta_kms_page_flip_data_unref (page_flip_data);
|
|
|
|
if (ret == -EBUSY)
|
|
{
|
|
CachedModeSet *cached_mode_set;
|
|
|
|
cached_mode_set = get_cached_mode_set (impl_simple, crtc);
|
|
if (cached_mode_set)
|
|
{
|
|
drmModeModeInfo *drm_mode;
|
|
float refresh_rate;
|
|
|
|
drm_mode = cached_mode_set->drm_mode;
|
|
refresh_rate = meta_calculate_drm_mode_refresh_rate (drm_mode);
|
|
schedule_retry_page_flip (impl_simple,
|
|
crtc,
|
|
plane_assignment ?
|
|
plane_assignment->fb_id : 0,
|
|
refresh_rate,
|
|
page_flip_data,
|
|
custom_page_flip_func,
|
|
page_flip->custom_page_flip_user_data);
|
|
}
|
|
else
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Page flip of %u failed, and no mode set available",
|
|
meta_kms_crtc_get_id (crtc));
|
|
meta_kms_page_flip_data_unref (page_flip_data);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else if (ret == -EINVAL)
|
|
{
|
|
if (!mode_set_fallback (impl_simple,
|
|
update,
|
|
page_flip,
|
|
plane_assignment,
|
|
page_flip_data,
|
|
error))
|
|
{
|
|
meta_kms_page_flip_data_unref (page_flip_data);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else if (ret != 0)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (-ret),
|
|
"drmModePageFlip on CRTC %u failed: %s",
|
|
meta_kms_crtc_get_id (crtc),
|
|
g_strerror (-ret));
|
|
meta_kms_page_flip_data_unref (page_flip_data);
|
|
return FALSE;
|
|
}
|
|
|
|
meta_kms_page_flip_data_unref (page_flip_data);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
discard_page_flip (MetaKmsImpl *impl,
|
|
MetaKmsUpdate *update,
|
|
MetaKmsPageFlip *page_flip)
|
|
{
|
|
MetaKmsCrtc *crtc;
|
|
MetaKmsPageFlipData *page_flip_data;
|
|
|
|
crtc = page_flip->crtc;
|
|
page_flip_data = meta_kms_page_flip_data_new (impl,
|
|
crtc,
|
|
page_flip->feedback,
|
|
page_flip->user_data);
|
|
meta_kms_page_flip_data_discard_in_impl (page_flip_data, NULL);
|
|
meta_kms_page_flip_data_unref (page_flip_data);
|
|
}
|
|
|
|
static gboolean
|
|
process_entries (MetaKmsImpl *impl,
|
|
MetaKmsUpdate *update,
|
|
GList *entries,
|
|
gboolean (* func) (MetaKmsImpl *impl,
|
|
MetaKmsUpdate *update,
|
|
gpointer entry_data,
|
|
GError **error),
|
|
GError **error)
|
|
{
|
|
GList *l;
|
|
|
|
for (l = entries; l; l = l->next)
|
|
{
|
|
if (!func (impl, update, l->data, error))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
process_cursor_plane_assignment (MetaKmsImpl *impl,
|
|
MetaKmsUpdate *update,
|
|
MetaKmsPlaneAssignment *plane_assignment,
|
|
GError **error)
|
|
{
|
|
MetaKmsPlane *plane;
|
|
MetaKmsDevice *device;
|
|
MetaKmsImplDevice *impl_device;
|
|
int fd;
|
|
|
|
plane = plane_assignment->plane;
|
|
device = meta_kms_plane_get_device (plane);
|
|
impl_device = meta_kms_device_get_impl_device (device);
|
|
fd = meta_kms_impl_device_get_fd (impl_device);
|
|
|
|
if (!(plane_assignment->flags & META_KMS_ASSIGN_PLANE_FLAG_FB_UNCHANGED))
|
|
{
|
|
int width, height;
|
|
int ret = -1;
|
|
|
|
width = meta_fixed_16_to_int (plane_assignment->dst_rect.width);
|
|
height = meta_fixed_16_to_int (plane_assignment->dst_rect.height);
|
|
|
|
if (plane_assignment->cursor_hotspot.is_valid)
|
|
{
|
|
ret = drmModeSetCursor2 (fd, meta_kms_crtc_get_id (plane_assignment->crtc),
|
|
plane_assignment->fb_id,
|
|
width, height,
|
|
plane_assignment->cursor_hotspot.x,
|
|
plane_assignment->cursor_hotspot.y);
|
|
}
|
|
|
|
if (ret != 0)
|
|
{
|
|
ret = drmModeSetCursor (fd, meta_kms_crtc_get_id (plane_assignment->crtc),
|
|
plane_assignment->fb_id,
|
|
width, height);
|
|
}
|
|
|
|
if (ret != 0)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (-ret),
|
|
"drmModeSetCursor failed: %s", g_strerror (-ret));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
drmModeMoveCursor (fd,
|
|
meta_kms_crtc_get_id (plane_assignment->crtc),
|
|
meta_fixed_16_to_int (plane_assignment->dst_rect.x),
|
|
meta_fixed_16_to_int (plane_assignment->dst_rect.y));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
process_plane_assignment (MetaKmsImpl *impl,
|
|
MetaKmsUpdate *update,
|
|
MetaKmsPlaneAssignment *plane_assignment,
|
|
MetaKmsPlaneFeedback **plane_feedback)
|
|
{
|
|
MetaKmsPlane *plane;
|
|
MetaKmsPlaneType plane_type;
|
|
GError *error = NULL;
|
|
|
|
plane = plane_assignment->plane;
|
|
plane_type = meta_kms_plane_get_plane_type (plane);
|
|
switch (plane_type)
|
|
{
|
|
case META_KMS_PLANE_TYPE_PRIMARY:
|
|
/* Handled as part of the mode-set and page flip. */
|
|
return TRUE;
|
|
case META_KMS_PLANE_TYPE_CURSOR:
|
|
if (!process_cursor_plane_assignment (impl, update,
|
|
plane_assignment,
|
|
&error))
|
|
{
|
|
*plane_feedback =
|
|
meta_kms_plane_feedback_new_take_error (plane,
|
|
plane_assignment->crtc,
|
|
g_steal_pointer (&error));
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
return TRUE;
|
|
}
|
|
case META_KMS_PLANE_TYPE_OVERLAY:
|
|
error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Overlay planes cannot be assigned");
|
|
*plane_feedback =
|
|
meta_kms_plane_feedback_new_take_error (plane,
|
|
plane_assignment->crtc,
|
|
g_steal_pointer (&error));
|
|
return TRUE;
|
|
}
|
|
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
static GList *
|
|
process_plane_assignments (MetaKmsImpl *impl,
|
|
MetaKmsUpdate *update)
|
|
{
|
|
GList *failed_planes = NULL;
|
|
GList *l;
|
|
|
|
for (l = meta_kms_update_get_plane_assignments (update); l; l = l->next)
|
|
{
|
|
MetaKmsPlaneAssignment *plane_assignment = l->data;
|
|
MetaKmsPlaneFeedback *plane_feedback;
|
|
|
|
if (!process_plane_assignment (impl, update, plane_assignment,
|
|
&plane_feedback))
|
|
failed_planes = g_list_prepend (failed_planes, plane_feedback);
|
|
}
|
|
|
|
return failed_planes;
|
|
}
|
|
|
|
static GList *
|
|
generate_all_failed_feedbacks (MetaKmsUpdate *update)
|
|
{
|
|
GList *failed_planes = NULL;
|
|
GList *l;
|
|
|
|
for (l = meta_kms_update_get_plane_assignments (update); l; l = l->next)
|
|
{
|
|
MetaKmsPlaneAssignment *plane_assignment = l->data;
|
|
MetaKmsPlane *plane;
|
|
MetaKmsPlaneType plane_type;
|
|
MetaKmsPlaneFeedback *plane_feedback;
|
|
|
|
plane = plane_assignment->plane;
|
|
plane_type = meta_kms_plane_get_plane_type (plane);
|
|
switch (plane_type)
|
|
{
|
|
case META_KMS_PLANE_TYPE_PRIMARY:
|
|
continue;
|
|
case META_KMS_PLANE_TYPE_CURSOR:
|
|
case META_KMS_PLANE_TYPE_OVERLAY:
|
|
break;
|
|
}
|
|
|
|
plane_feedback =
|
|
meta_kms_plane_feedback_new_take_error (plane_assignment->plane,
|
|
plane_assignment->crtc,
|
|
g_error_new (G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"Discarded"));
|
|
failed_planes = g_list_prepend (failed_planes, plane_feedback);
|
|
}
|
|
|
|
return failed_planes;
|
|
}
|
|
|
|
static MetaKmsFeedback *
|
|
meta_kms_impl_simple_process_update (MetaKmsImpl *impl,
|
|
MetaKmsUpdate *update)
|
|
{
|
|
GError *error = NULL;
|
|
GList *failed_planes;
|
|
GList *l;
|
|
|
|
meta_assert_in_kms_impl (meta_kms_impl_get_kms (impl));
|
|
|
|
if (!process_entries (impl,
|
|
update,
|
|
meta_kms_update_get_connector_properties (update),
|
|
process_connector_property,
|
|
&error))
|
|
goto err_planes_not_assigned;
|
|
|
|
if (!process_entries (impl,
|
|
update,
|
|
meta_kms_update_get_mode_sets (update),
|
|
process_mode_set,
|
|
&error))
|
|
goto err_planes_not_assigned;
|
|
|
|
if (!process_entries (impl,
|
|
update,
|
|
meta_kms_update_get_crtc_gammas (update),
|
|
process_crtc_gamma,
|
|
&error))
|
|
goto err_planes_not_assigned;
|
|
|
|
failed_planes = process_plane_assignments (impl, update);
|
|
if (failed_planes)
|
|
{
|
|
g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Failed to assign one or more planes");
|
|
goto err_planes_assigned;
|
|
}
|
|
|
|
if (!process_entries (impl,
|
|
update,
|
|
meta_kms_update_get_page_flips (update),
|
|
process_page_flip,
|
|
&error))
|
|
goto err_planes_assigned;
|
|
|
|
return meta_kms_feedback_new_passed ();
|
|
|
|
err_planes_not_assigned:
|
|
failed_planes = generate_all_failed_feedbacks (update);
|
|
|
|
err_planes_assigned:
|
|
for (l = meta_kms_update_get_page_flips (update); l; l = l->next)
|
|
{
|
|
MetaKmsPageFlip *page_flip = l->data;
|
|
|
|
if (page_flip->flags & META_KMS_PAGE_FLIP_FLAG_NO_DISCARD_FEEDBACK)
|
|
continue;
|
|
|
|
discard_page_flip (impl, update, page_flip);
|
|
}
|
|
|
|
return meta_kms_feedback_new_failed (failed_planes, error);
|
|
}
|
|
|
|
static void
|
|
flush_postponed_page_flip_datas (MetaKmsImplSimple *impl_simple)
|
|
{
|
|
invoke_page_flip_datas (impl_simple->postponed_page_flip_datas,
|
|
meta_kms_page_flip_data_flipped_in_impl);
|
|
clear_page_flip_datas (&impl_simple->postponed_page_flip_datas);
|
|
|
|
invoke_page_flip_datas (impl_simple->postponed_mode_set_fallback_datas,
|
|
meta_kms_page_flip_data_mode_set_fallback_in_impl);
|
|
clear_page_flip_datas (&impl_simple->postponed_mode_set_fallback_datas);
|
|
}
|
|
|
|
static void
|
|
meta_kms_impl_simple_handle_page_flip_callback (MetaKmsImpl *impl,
|
|
MetaKmsPageFlipData *page_flip_data)
|
|
{
|
|
MetaKmsImplSimple *impl_simple = META_KMS_IMPL_SIMPLE (impl);
|
|
|
|
if (impl_simple->pending_page_flip_retries)
|
|
{
|
|
impl_simple->postponed_page_flip_datas =
|
|
g_list_append (impl_simple->postponed_page_flip_datas,
|
|
meta_kms_page_flip_data_ref (page_flip_data));
|
|
}
|
|
else
|
|
{
|
|
meta_kms_page_flip_data_flipped_in_impl (page_flip_data);
|
|
}
|
|
|
|
meta_kms_page_flip_data_unref (page_flip_data);
|
|
}
|
|
|
|
static void
|
|
meta_kms_impl_simple_discard_pending_page_flips (MetaKmsImpl *impl)
|
|
{
|
|
MetaKmsImplSimple *impl_simple = META_KMS_IMPL_SIMPLE (impl);
|
|
GList *l;
|
|
|
|
if (!impl_simple->pending_page_flip_retries)
|
|
return;
|
|
|
|
for (l = impl_simple->pending_page_flip_retries; l; l = l->next)
|
|
{
|
|
RetryPageFlipData *retry_page_flip_data = l->data;
|
|
MetaKmsPageFlipData *page_flip_data;
|
|
|
|
page_flip_data = g_steal_pointer (&retry_page_flip_data->page_flip_data);
|
|
meta_kms_page_flip_data_discard_in_impl (page_flip_data, NULL);
|
|
retry_page_flip_data_free (retry_page_flip_data);
|
|
}
|
|
g_clear_pointer (&impl_simple->pending_page_flip_retries, g_list_free);
|
|
|
|
g_clear_pointer (&impl_simple->retry_page_flips_source,
|
|
g_source_destroy);
|
|
}
|
|
|
|
static void
|
|
meta_kms_impl_simple_dispatch_idle (MetaKmsImpl *impl)
|
|
{
|
|
MetaKmsImplSimple *impl_simple = META_KMS_IMPL_SIMPLE (impl);
|
|
|
|
if (impl_simple->mode_set_fallback_feedback_source)
|
|
mode_set_fallback_feedback_idle (impl_simple);
|
|
}
|
|
|
|
static void
|
|
meta_kms_impl_simple_notify_device_created (MetaKmsImpl *impl,
|
|
MetaKmsDevice *device)
|
|
{
|
|
GList *l;
|
|
|
|
for (l = meta_kms_device_get_crtcs (device); l; l = l->next)
|
|
{
|
|
MetaKmsCrtc *crtc = l->data;
|
|
MetaKmsPlane *plane;
|
|
|
|
plane = meta_kms_device_get_cursor_plane_for (device, crtc);
|
|
if (plane)
|
|
continue;
|
|
|
|
meta_kms_device_add_fake_plane_in_impl (device,
|
|
META_KMS_PLANE_TYPE_CURSOR,
|
|
crtc);
|
|
}
|
|
}
|
|
|
|
static void
|
|
meta_kms_impl_simple_finalize (GObject *object)
|
|
{
|
|
MetaKmsImplSimple *impl_simple = META_KMS_IMPL_SIMPLE (object);
|
|
|
|
g_list_free_full (impl_simple->pending_page_flip_retries,
|
|
(GDestroyNotify) retry_page_flip_data_free);
|
|
g_list_free_full (impl_simple->postponed_page_flip_datas,
|
|
(GDestroyNotify) meta_kms_page_flip_data_unref);
|
|
g_list_free_full (impl_simple->postponed_mode_set_fallback_datas,
|
|
(GDestroyNotify) meta_kms_page_flip_data_unref);
|
|
g_clear_pointer (&impl_simple->mode_set_fallback_feedback_source,
|
|
g_source_destroy);
|
|
g_hash_table_destroy (impl_simple->cached_mode_sets);
|
|
|
|
G_OBJECT_CLASS (meta_kms_impl_simple_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
meta_kms_impl_simple_init (MetaKmsImplSimple *impl_simple)
|
|
{
|
|
impl_simple->cached_mode_sets =
|
|
g_hash_table_new_full (NULL,
|
|
NULL,
|
|
NULL,
|
|
(GDestroyNotify) cached_mode_set_free);
|
|
}
|
|
|
|
static void
|
|
meta_kms_impl_simple_class_init (MetaKmsImplSimpleClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
MetaKmsImplClass *impl_class = META_KMS_IMPL_CLASS (klass);
|
|
|
|
object_class->finalize = meta_kms_impl_simple_finalize;
|
|
|
|
impl_class->process_update = meta_kms_impl_simple_process_update;
|
|
impl_class->handle_page_flip_callback = meta_kms_impl_simple_handle_page_flip_callback;
|
|
impl_class->discard_pending_page_flips = meta_kms_impl_simple_discard_pending_page_flips;
|
|
impl_class->dispatch_idle = meta_kms_impl_simple_dispatch_idle;
|
|
impl_class->notify_device_created = meta_kms_impl_simple_notify_device_created;
|
|
}
|