/* * 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-simple.h" #include "backends/native/meta-drm-buffer-gbm.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" #include "backends/native/meta-kms-utils.h" typedef struct _CachedModeSet { GList *connectors; drmModeModeInfo *drm_mode; } CachedModeSet; struct _MetaKmsImplDeviceSimple { MetaKmsImplDevice 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; }; static GInitableIface *initable_parent_iface; static void initable_iface_init (GInitableIface *iface); G_DEFINE_TYPE_WITH_CODE (MetaKmsImplDeviceSimple, meta_kms_impl_device_simple, META_TYPE_KMS_IMPL_DEVICE, G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)) static void flush_postponed_page_flip_datas (MetaKmsImplDeviceSimple *impl_device_simple); static gboolean set_connector_property (MetaKmsImplDevice *impl_device, MetaKmsConnector *connector, MetaKmsConnectorProp prop, uint64_t value, GError **error) { uint32_t prop_id; int fd; int ret; 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, "Property (%s) not found on connector %u", meta_kms_connector_get_prop_name (connector, prop), meta_kms_connector_get_id (connector)); return FALSE; } fd = meta_kms_impl_device_get_fd (impl_device); ret = drmModeObjectSetProperty (fd, meta_kms_connector_get_id (connector), DRM_MODE_OBJECT_CONNECTOR, prop_id, 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), prop_id, g_strerror (-ret)); return FALSE; } return TRUE; } static gboolean process_connector_update (MetaKmsImplDevice *impl_device, MetaKmsUpdate *update, gpointer update_entry, GError **error) { MetaKmsConnectorUpdate *connector_update = update_entry; MetaKmsConnector *connector = connector_update->connector; if (connector_update->dpms.has_update) { if (!set_connector_property (impl_device, connector, META_KMS_CONNECTOR_PROP_DPMS, connector_update->dpms.state, error)) return FALSE; } if (connector_update->underscanning.has_update && connector_update->underscanning.is_active) { if (!set_connector_property (impl_device, connector, META_KMS_CONNECTOR_PROP_UNDERSCAN, 1, error)) return FALSE; if (!set_connector_property (impl_device, connector, META_KMS_CONNECTOR_PROP_UNDERSCAN_HBORDER, connector_update->underscanning.hborder, error)) return FALSE; if (!set_connector_property (impl_device, connector, META_KMS_CONNECTOR_PROP_UNDERSCAN_VBORDER, connector_update->underscanning.vborder, error)) return FALSE; } else if (connector_update->underscanning.has_update) { if (!set_connector_property (impl_device, connector, META_KMS_CONNECTOR_PROP_UNDERSCAN, 0, error)) 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 set_plane_rotation (MetaKmsImplDevice *impl_device, MetaKmsPlane *plane, uint64_t rotation, GError **error) { int fd; uint32_t rotation_prop_id; int ret; fd = meta_kms_impl_device_get_fd (impl_device); rotation_prop_id = meta_kms_plane_get_prop_id (plane, META_KMS_PLANE_PROP_ROTATION); ret = drmModeObjectSetProperty (fd, meta_kms_plane_get_id (plane), DRM_MODE_OBJECT_PLANE, rotation_prop_id, rotation); if (ret != 0) { g_set_error (error, G_IO_ERROR, g_io_error_from_errno (-ret), "Failed to rotation property (%u) to %" G_GUINT64_FORMAT " on plane %u: %s", rotation_prop_id, rotation, meta_kms_plane_get_id (plane), g_strerror (-ret)); return FALSE; } return TRUE; } static gboolean process_mode_set (MetaKmsImplDevice *impl_device, MetaKmsUpdate *update, gpointer update_entry, GError **error) { MetaKmsImplDeviceSimple *impl_device_simple = META_KMS_IMPL_DEVICE_SIMPLE (impl_device); MetaKmsModeSet *mode_set = update_entry; MetaKmsCrtc *crtc = mode_set->crtc; g_autofree uint32_t *connectors = NULL; int n_connectors; MetaKmsPlaneAssignment *plane_assignment; drmModeModeInfo *drm_mode; uint32_t x, y; uint32_t fb_id; int fd; int ret; crtc = mode_set->crtc; if (mode_set->mode) { MetaDrmBuffer *buffer; drm_mode = g_alloca (sizeof *drm_mode); *drm_mode = *meta_kms_mode_get_drm_mode (mode_set->mode); 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); if (plane_assignment->rotation) { if (!set_plane_rotation (impl_device, plane_assignment->plane, plane_assignment->rotation, error)) return FALSE; } buffer = plane_assignment->buffer; fb_id = meta_drm_buffer_get_fb_id (buffer); } else { drm_mode = NULL; 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, 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", drm_mode ? drm_mode->name : "off", meta_kms_crtc_get_id (crtc), g_strerror (-ret)); return FALSE; } if (drm_mode) { g_hash_table_replace (impl_device_simple->cached_mode_sets, crtc, cached_mode_set_new (mode_set->connectors, drm_mode)); } else { g_hash_table_remove (impl_device_simple->cached_mode_sets, crtc); } return TRUE; } static gboolean process_crtc_gamma (MetaKmsImplDevice *impl_device, MetaKmsUpdate *update, gpointer update_entry, GError **error) { MetaKmsCrtcGamma *gamma = update_entry; MetaKmsCrtc *crtc = gamma->crtc; 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 (MetaKmsImplDeviceSimple *impl_device_simple, MetaKmsCrtc *crtc) { return g_hash_table_lookup (impl_device_simple->cached_mode_sets, crtc); } static float get_cached_crtc_refresh_rate (MetaKmsImplDeviceSimple *impl_device_simple, MetaKmsCrtc *crtc) { CachedModeSet *cached_mode_set; cached_mode_set = g_hash_table_lookup (impl_device_simple->cached_mode_sets, crtc); g_assert (cached_mode_set); return meta_calculate_drm_mode_refresh_rate (cached_mode_set->drm_mode); } #define meta_assert_in_kms_impl(kms) \ g_assert (meta_kms_in_impl_task (kms)) static gboolean retry_page_flips (gpointer user_data) { MetaKmsImplDeviceSimple *impl_device_simple = META_KMS_IMPL_DEVICE_SIMPLE (user_data); MetaKmsImplDevice *impl_device = META_KMS_IMPL_DEVICE (impl_device_simple); uint64_t now_us; GList *l; now_us = g_source_get_time (impl_device_simple->retry_page_flips_source); l = impl_device_simple->pending_page_flip_retries; while (l) { RetryPageFlipData *retry_page_flip_data = l->data; MetaKmsCrtc *crtc = retry_page_flip_data->crtc; 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_device_simple, crtc); retry_page_flip_data->retry_time_us += (uint64_t) (G_USEC_PER_SEC / refresh_rate); l = l_next; continue; } impl_device_simple->pending_page_flip_retries = g_list_remove_link (impl_device_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_device_simple->pending_page_flip_retries) { GList *l; uint64_t earliest_retry_time_us = 0; for (l = impl_device_simple->pending_page_flip_retries; l; l = l->next) { RetryPageFlipData *retry_page_flip_data = l->data; if (l == impl_device_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_device_simple->retry_page_flips_source, earliest_retry_time_us); return G_SOURCE_CONTINUE; } else { g_clear_pointer (&impl_device_simple->retry_page_flips_source, g_source_unref); flush_postponed_page_flip_datas (impl_device_simple); return G_SOURCE_REMOVE; } } static void schedule_retry_page_flip (MetaKmsImplDeviceSimple *impl_device_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_device_simple->retry_page_flips_source) { MetaKmsImplDevice *impl_device = META_KMS_IMPL_DEVICE (impl_device_simple); MetaKmsDevice *device = meta_kms_impl_device_get_device (impl_device); MetaKms *kms = meta_kms_device_get_kms (device); GSource *source; source = meta_kms_add_source_in_impl (kms, retry_page_flips, impl_device_simple, NULL); g_source_set_ready_time (source, retry_time_us); impl_device_simple->retry_page_flips_source = source; } else { GList *l; for (l = impl_device_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_device_simple->retry_page_flips_source, retry_time_us); break; } } } impl_device_simple->pending_page_flip_retries = g_list_append (impl_device_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) { MetaKmsImplDeviceSimple *impl_device_simple = user_data; g_clear_pointer (&impl_device_simple->mode_set_fallback_feedback_source, g_source_unref); if (impl_device_simple->pending_page_flip_retries) { impl_device_simple->postponed_mode_set_fallback_datas = g_steal_pointer (&impl_device_simple->mode_set_fallback_page_flip_datas); } else { invoke_page_flip_datas (impl_device_simple->mode_set_fallback_page_flip_datas, meta_kms_page_flip_data_mode_set_fallback_in_impl); clear_page_flip_datas (&impl_device_simple->mode_set_fallback_page_flip_datas); } return G_SOURCE_REMOVE; } static gboolean mode_set_fallback (MetaKmsImplDeviceSimple *impl_device_simple, MetaKmsUpdate *update, MetaKmsPageFlip *page_flip, MetaKmsPlaneAssignment *plane_assignment, MetaKmsPageFlipData *page_flip_data, GError **error) { MetaKmsImplDevice *impl_device = META_KMS_IMPL_DEVICE (impl_device_simple); MetaKmsDevice *device = meta_kms_impl_device_get_device (impl_device); MetaKms *kms = meta_kms_device_get_kms (device); MetaKmsCrtc *crtc = page_flip->crtc; CachedModeSet *cached_mode_set; g_autofree uint32_t *connectors = NULL; int n_connectors; uint32_t fb_id; uint32_t x, y; int fd; int ret; cached_mode_set = g_hash_table_lookup (impl_device_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); fb_id = meta_drm_buffer_get_fb_id (plane_assignment->buffer); 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), 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_device_simple->mode_set_fallback_feedback_source) { GSource *source; source = meta_kms_add_source_in_impl (kms, mode_set_fallback_feedback_idle, impl_device_simple, NULL); impl_device_simple->mode_set_fallback_feedback_source = source; } impl_device_simple->mode_set_fallback_page_flip_datas = g_list_prepend (impl_device_simple->mode_set_fallback_page_flip_datas, meta_kms_page_flip_data_ref (page_flip_data)); return TRUE; } static gboolean process_page_flip (MetaKmsImplDevice *impl_device, MetaKmsUpdate *update, gpointer update_entry, GError **error) { MetaKmsImplDeviceSimple *impl_device_simple = META_KMS_IMPL_DEVICE_SIMPLE (impl_device); MetaKmsPageFlip *page_flip = update_entry; MetaKmsCrtc *crtc; MetaKmsPlaneAssignment *plane_assignment; MetaKmsPageFlipData *page_flip_data; MetaKmsCustomPageFlipFunc custom_page_flip_func; gpointer custom_page_flip_user_data; 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_device, crtc, page_flip->feedback, page_flip->user_data); fd = meta_kms_impl_device_get_fd (impl_device); meta_kms_update_get_custom_page_flip_func (update, &custom_page_flip_func, &custom_page_flip_user_data); if (custom_page_flip_func) { ret = custom_page_flip_func (custom_page_flip_user_data, meta_kms_page_flip_data_ref (page_flip_data)); } else { uint32_t fb_id; fb_id = meta_drm_buffer_get_fb_id (plane_assignment->buffer); ret = drmModePageFlip (fd, meta_kms_crtc_get_id (crtc), 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_device_simple, crtc); if (cached_mode_set) { uint32_t fb_id; drmModeModeInfo *drm_mode; float refresh_rate; if (plane_assignment) fb_id = meta_drm_buffer_get_fb_id (plane_assignment->buffer); else fb_id = 0; drm_mode = cached_mode_set->drm_mode; refresh_rate = meta_calculate_drm_mode_refresh_rate (drm_mode); schedule_retry_page_flip (impl_device_simple, crtc, fb_id, refresh_rate, page_flip_data, custom_page_flip_func, 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_device_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 gboolean process_entries (MetaKmsImplDevice *impl_device, MetaKmsUpdate *update, GList *entries, gboolean (* func) (MetaKmsImplDevice *impl_device, MetaKmsUpdate *update, gpointer entry_data, GError **error), GError **error) { GList *l; for (l = entries; l; l = l->next) { if (!func (impl_device, update, l->data, error)) return FALSE; } return TRUE; } static gboolean process_cursor_plane_assignment (MetaKmsImplDevice *impl_device, MetaKmsUpdate *update, MetaKmsPlaneAssignment *plane_assignment, GError **error) { uint32_t crtc_id; int fd; crtc_id = meta_kms_crtc_get_id (plane_assignment->crtc), 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; uint32_t handle_u32; width = plane_assignment->dst_rect.width; height = plane_assignment->dst_rect.height; if (plane_assignment->buffer) { MetaDrmBufferGbm *buffer_gbm = META_DRM_BUFFER_GBM (plane_assignment->buffer); struct gbm_bo *bo; union gbm_bo_handle handle; bo = meta_drm_buffer_gbm_get_bo (buffer_gbm); handle = gbm_bo_get_handle (bo); handle_u32 = handle.u32; } else { handle_u32 = 0; } if (plane_assignment->cursor_hotspot.is_valid) { ret = drmModeSetCursor2 (fd, crtc_id, handle_u32, width, height, plane_assignment->cursor_hotspot.x, plane_assignment->cursor_hotspot.y); } if (ret != 0) { ret = drmModeSetCursor (fd, crtc_id, handle_u32, 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, crtc_id, plane_assignment->dst_rect.x, plane_assignment->dst_rect.y); return TRUE; } static gboolean process_plane_assignment (MetaKmsImplDevice *impl_device, 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_device, 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 (MetaKmsImplDevice *impl_device, 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_device, 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_device_simple_process_update (MetaKmsImplDevice *impl_device, MetaKmsUpdate *update) { GError *error = NULL; GList *failed_planes; if (!process_entries (impl_device, update, meta_kms_update_get_connector_updates (update), process_connector_update, &error)) goto err_planes_not_assigned; if (!process_entries (impl_device, update, meta_kms_update_get_mode_sets (update), process_mode_set, &error)) goto err_planes_not_assigned; if (!process_entries (impl_device, update, meta_kms_update_get_crtc_gammas (update), process_crtc_gamma, &error)) goto err_planes_not_assigned; failed_planes = process_plane_assignments (impl_device, 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_device, 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: return meta_kms_feedback_new_failed (failed_planes, error); } static void flush_postponed_page_flip_datas (MetaKmsImplDeviceSimple *impl_device_simple) { invoke_page_flip_datas (impl_device_simple->postponed_page_flip_datas, meta_kms_page_flip_data_flipped_in_impl); clear_page_flip_datas (&impl_device_simple->postponed_page_flip_datas); invoke_page_flip_datas (impl_device_simple->postponed_mode_set_fallback_datas, meta_kms_page_flip_data_mode_set_fallback_in_impl); clear_page_flip_datas (&impl_device_simple->postponed_mode_set_fallback_datas); } static void meta_kms_impl_device_simple_handle_page_flip_callback (MetaKmsImplDevice *impl_device, MetaKmsPageFlipData *page_flip_data) { MetaKmsImplDeviceSimple *impl_device_simple = META_KMS_IMPL_DEVICE_SIMPLE (impl_device); if (impl_device_simple->pending_page_flip_retries) { impl_device_simple->postponed_page_flip_datas = g_list_append (impl_device_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_device_simple_discard_pending_page_flips (MetaKmsImplDevice *impl_device) { MetaKmsImplDeviceSimple *impl_device_simple = META_KMS_IMPL_DEVICE_SIMPLE (impl_device); GList *l; if (!impl_device_simple->pending_page_flip_retries) return; for (l = impl_device_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_device_simple->pending_page_flip_retries, g_list_free); g_clear_pointer (&impl_device_simple->retry_page_flips_source, g_source_destroy); } static void meta_kms_impl_device_simple_finalize (GObject *object) { MetaKmsImplDeviceSimple *impl_device_simple = META_KMS_IMPL_DEVICE_SIMPLE (object); g_list_free_full (impl_device_simple->pending_page_flip_retries, (GDestroyNotify) retry_page_flip_data_free); g_list_free_full (impl_device_simple->postponed_page_flip_datas, (GDestroyNotify) meta_kms_page_flip_data_unref); g_list_free_full (impl_device_simple->postponed_mode_set_fallback_datas, (GDestroyNotify) meta_kms_page_flip_data_unref); g_clear_pointer (&impl_device_simple->mode_set_fallback_feedback_source, g_source_destroy); g_hash_table_destroy (impl_device_simple->cached_mode_sets); G_OBJECT_CLASS (meta_kms_impl_device_simple_parent_class)->finalize (object); } static gboolean meta_kms_impl_device_simple_initable_init (GInitable *initable, GCancellable *cancellable, GError **error) { MetaKmsImplDeviceSimple *impl_device_simple = META_KMS_IMPL_DEVICE_SIMPLE (initable); MetaKmsImplDevice *impl_device = META_KMS_IMPL_DEVICE (impl_device_simple); MetaKmsDevice *device = meta_kms_impl_device_get_device (impl_device); GList *l; if (!initable_parent_iface->init (initable, cancellable, error)) return FALSE; impl_device_simple->cached_mode_sets = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) cached_mode_set_free); 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); } return TRUE; } static void meta_kms_impl_device_simple_init (MetaKmsImplDeviceSimple *impl_device_simple) { } static void initable_iface_init (GInitableIface *iface) { initable_parent_iface = g_type_interface_peek_parent (iface); iface->init = meta_kms_impl_device_simple_initable_init; } static void meta_kms_impl_device_simple_class_init (MetaKmsImplDeviceSimpleClass *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_simple_finalize; impl_device_class->process_update = meta_kms_impl_device_simple_process_update; impl_device_class->handle_page_flip_callback = meta_kms_impl_device_simple_handle_page_flip_callback; impl_device_class->discard_pending_page_flips = meta_kms_impl_device_simple_discard_pending_page_flips; }