/* * Copyright (C) 2018 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-private.h" #include "backends/native/meta-backend-native.h" #include "backends/native/meta-kms-device-private.h" #include "backends/native/meta-kms-impl.h" #include "backends/native/meta-kms-impl-simple.h" #include "backends/native/meta-kms-update-private.h" #include "backends/native/meta-udev.h" /** * SECTION:kms * @short description: KMS abstraction * @title: KMS abstraction * * The KMS abstraction consists of various building blocks for helping out with * interacting with the various drm API's, enabling users to use a * transactional API, aiming to hide all interaction with the underlying APIs. * * The subsystem defines two separate contexts, the "main" context, and the * "impl" context. The main context is the context of which mutter as a whole * runs in. It uses the main GLib main loop and main context and always runs in * the main thread. * * The impl context is where all underlying API is being executed. While in the * current state, it always runs in the main thread, the aim is to be able to * execute the impl context in a dedicated thread. * * The public facing MetaKms API is always assumed to be executed from the main * context. * * The KMS abstraction consists of the following public components: * * #MetaKms: * * Main entry point; used by the native backend to create devices, post updates * etc. * * #MetaKmsDevice: * * A device (usually /dev/dri/cardN, where N being a number). Used to get KMS * objects, such as connectors, CRTCs, planes, as well as basic meta data such * as device path etc. * * #MetaKmsCrtc: * * Represents a CRTC. It manages a representation of the current CRTC state, * including current mode, coordinates, possible clones. * * #MetaKmsConnector: * * Represents a connector, e.g. a display port connection. It also manages a * representation of the current state, including meta data such as physical * dimension of the connected, available modes, EDID, tile info etc. It also * contains helper functions for configuration, as well as methods for adding * configuration to a transaction (See #MetaKmsUpdate). * * #MetaKmsPlane: * * Represents a hardware plane. A plane is used to define the content of what * should be presented on a CRTC. Planes can either be primary planes, used as * a backdrop for CRTCs, overlay planes, and cursor planes. * * #MetaKmsUpdate: * * A KMS transaction object, meant to be processed potentially atomically when * posted. An update consists of plane assignments, mode sets and KMS object * property entries. The user adds updates to the object, and then posts it via * MetaKms. It will then be processed by the MetaKms backend (See * #MetaKmsImpl), potentially atomically. * * * There are also these private objects, without public facing API: * * #MetaKmsImpl: * * The KMS backend implementation, running in the impl context. #MetaKmsImpl * itself is an abstract object, with potentially multiple implementations. * Currently only #MetaKmsImplSimple exists. * * #MetaKmsImplSimple: * * A KMS backend implementation using the non-atomic drmMode* API. While it's * interacted with using the transactional API, the #MetaKmsUpdate is processed * non-atomically. * * #MetaKmsImplDevice: * * An object linked to a #MetaKmsDevice, but where it is executed in the impl * context. It takes care of the updating of the various KMS object (CRTC, * connector, ..) states. * * #MetaKmsPageFlip: * * A object representing a page flip. It's created when a page flip is queued, * and contains information necessary to provide feedback to the one requesting * the page flip. * */ typedef struct _MetaKmsCallbackData { MetaKmsCallback callback; gpointer user_data; GDestroyNotify user_data_destroy; } MetaKmsCallbackData; typedef struct _MetaKmsSimpleImplSource { GSource source; MetaKms *kms; } MetaKmsSimpleImplSource; typedef struct _MetaKmsFdImplSource { GSource source; gpointer fd_tag; MetaKms *kms; MetaKmsImplTaskFunc dispatch; gpointer user_data; } MetaKmsFdImplSource; struct _MetaKms { GObject parent; MetaBackend *backend; guint hotplug_handler_id; guint removed_handler_id; MetaKmsImpl *impl; gboolean in_impl_task; GList *devices; MetaKmsUpdate *pending_update; GList *pending_callbacks; guint callback_source_id; }; G_DEFINE_TYPE (MetaKms, meta_kms, G_TYPE_OBJECT) static void meta_kms_update_states_in_impl (MetaKms *kms); MetaKmsUpdate * meta_kms_ensure_pending_update (MetaKms *kms) { if (!kms->pending_update) kms->pending_update = meta_kms_update_new (); return meta_kms_get_pending_update (kms); } MetaKmsUpdate * meta_kms_get_pending_update (MetaKms *kms) { return kms->pending_update; } static gboolean meta_kms_update_process_in_impl (MetaKmsImpl *impl, gpointer user_data, GError **error) { g_autoptr (MetaKmsUpdate) update = user_data; gboolean ret; ret = meta_kms_impl_process_update (impl, update, error); if (meta_kms_update_has_mode_set (update)) meta_kms_update_states_in_impl (meta_kms_impl_get_kms (impl)); return ret; } static gboolean meta_kms_post_update_sync (MetaKms *kms, MetaKmsUpdate *update, GError **error) { meta_kms_update_seal (update); return meta_kms_run_impl_task_sync (kms, meta_kms_update_process_in_impl, update, error); } gboolean meta_kms_post_pending_update_sync (MetaKms *kms, GError **error) { return meta_kms_post_update_sync (kms, g_steal_pointer (&kms->pending_update), error); } static gboolean meta_kms_discard_pending_page_flips_in_impl (MetaKmsImpl *impl, gpointer user_data, GError **error) { meta_kms_impl_discard_pending_page_flips (impl); return TRUE; } void meta_kms_discard_pending_page_flips (MetaKms *kms) { meta_kms_run_impl_task_sync (kms, meta_kms_discard_pending_page_flips_in_impl, NULL, NULL); } static void meta_kms_callback_data_free (MetaKmsCallbackData *callback_data) { if (callback_data->user_data_destroy) callback_data->user_data_destroy (callback_data->user_data); g_slice_free (MetaKmsCallbackData, callback_data); } static int flush_callbacks (MetaKms *kms) { GList *l; int callback_count = 0; for (l = kms->pending_callbacks; l; l = l->next) { MetaKmsCallbackData *callback_data = l->data; callback_data->callback (kms, callback_data->user_data); meta_kms_callback_data_free (callback_data); callback_count++; } g_list_free (kms->pending_callbacks); kms->pending_callbacks = NULL; return callback_count; } static gboolean callback_idle (gpointer user_data) { MetaKms *kms = user_data; flush_callbacks (kms); kms->callback_source_id = 0; return G_SOURCE_REMOVE; } void meta_kms_queue_callback (MetaKms *kms, MetaKmsCallback callback, gpointer user_data, GDestroyNotify user_data_destroy) { MetaKmsCallbackData *callback_data; callback_data = g_slice_new0 (MetaKmsCallbackData); *callback_data = (MetaKmsCallbackData) { .callback = callback, .user_data = user_data, .user_data_destroy = user_data_destroy, }; kms->pending_callbacks = g_list_append (kms->pending_callbacks, callback_data); if (!kms->callback_source_id) kms->callback_source_id = g_idle_add (callback_idle, kms); } int meta_kms_flush_callbacks (MetaKms *kms) { int callback_count; callback_count = flush_callbacks (kms); g_clear_handle_id (&kms->callback_source_id, g_source_remove); return callback_count; } gboolean meta_kms_run_impl_task_sync (MetaKms *kms, MetaKmsImplTaskFunc func, gpointer user_data, GError **error) { gboolean ret; kms->in_impl_task = TRUE; ret = func (kms->impl, user_data, error); kms->in_impl_task = FALSE; return ret; } static gboolean simple_impl_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) { MetaKmsSimpleImplSource *simple_impl_source = (MetaKmsSimpleImplSource *) source; MetaKms *kms = simple_impl_source->kms; gboolean ret; kms->in_impl_task = TRUE; ret = callback (user_data); kms->in_impl_task = FALSE; return ret; } static GSourceFuncs simple_impl_source_funcs = { .dispatch = simple_impl_source_dispatch, }; GSource * meta_kms_add_source_in_impl (MetaKms *kms, GSourceFunc func, gpointer user_data, GDestroyNotify user_data_destroy) { GSource *source; MetaKmsSimpleImplSource *simple_impl_source; meta_assert_in_kms_impl (kms); source = g_source_new (&simple_impl_source_funcs, sizeof (MetaKmsSimpleImplSource)); simple_impl_source = (MetaKmsSimpleImplSource *) source; simple_impl_source->kms = kms; g_source_set_callback (source, func, user_data, user_data_destroy); g_source_attach (source, g_main_context_get_thread_default ()); return source; } static gboolean meta_kms_fd_impl_source_check (GSource *source) { MetaKmsFdImplSource *fd_impl_source = (MetaKmsFdImplSource *) source; return g_source_query_unix_fd (source, fd_impl_source->fd_tag) & G_IO_IN; } static gboolean meta_kms_fd_impl_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) { MetaKmsFdImplSource *fd_impl_source = (MetaKmsFdImplSource *) source; MetaKms *kms = fd_impl_source->kms; gboolean ret; GError *error = NULL; kms->in_impl_task = TRUE; ret = fd_impl_source->dispatch (kms->impl, fd_impl_source->user_data, &error); kms->in_impl_task = FALSE; if (!ret) { g_warning ("Failed to dispatch fd source: %s", error->message); g_error_free (error); } return G_SOURCE_CONTINUE; } static GSourceFuncs fd_impl_source_funcs = { NULL, meta_kms_fd_impl_source_check, meta_kms_fd_impl_source_dispatch }; GSource * meta_kms_register_fd_in_impl (MetaKms *kms, int fd, MetaKmsImplTaskFunc dispatch, gpointer user_data) { GSource *source; MetaKmsFdImplSource *fd_impl_source; meta_assert_in_kms_impl (kms); source = g_source_new (&fd_impl_source_funcs, sizeof (MetaKmsFdImplSource)); fd_impl_source = (MetaKmsFdImplSource *) source; fd_impl_source->dispatch = dispatch; fd_impl_source->user_data = user_data; fd_impl_source->kms = kms; fd_impl_source->fd_tag = g_source_add_unix_fd (source, fd, G_IO_IN | G_IO_ERR); g_source_attach (source, g_main_context_get_thread_default ()); return source; } gboolean meta_kms_in_impl_task (MetaKms *kms) { return kms->in_impl_task; } static void meta_kms_update_states_in_impl (MetaKms *kms) { GList *l; meta_assert_in_kms_impl (kms); for (l = kms->devices; l; l = l->next) { MetaKmsDevice *device = l->data; MetaKmsImplDevice *impl_device = meta_kms_device_get_impl_device (device); meta_kms_impl_device_update_states (impl_device); } } static gboolean update_states_in_impl (MetaKmsImpl *impl, gpointer user_data, GError **error) { MetaKms *kms = user_data; meta_kms_update_states_in_impl (kms); return TRUE; } static gboolean meta_kms_update_states_sync (MetaKms *kms, GError **error) { return meta_kms_run_impl_task_sync (kms, update_states_in_impl, kms, error); } static void handle_hotplug_event (MetaKms *kms) { g_autoptr (GError) error = NULL; if (!meta_kms_update_states_sync (kms, &error)) g_warning ("Updating KMS state failed: %s", error->message); } static void on_udev_hotplug (MetaUdev *udev, MetaKms *kms) { handle_hotplug_event (kms); } static void on_udev_device_removed (MetaUdev *udev, GUdevDevice *device, MetaKms *kms) { handle_hotplug_event (kms); } MetaBackend * meta_kms_get_backend (MetaKms *kms) { return kms->backend; } MetaKmsDevice * meta_kms_create_device (MetaKms *kms, const char *path, MetaKmsDeviceFlag flags, GError **error) { MetaKmsDevice *device; device = meta_kms_device_new (kms, path, flags, error); if (!device) return NULL; kms->devices = g_list_append (kms->devices, device); return device; } MetaKms * meta_kms_new (MetaBackend *backend, GError **error) { MetaBackendNative *backend_native = META_BACKEND_NATIVE (backend); MetaUdev *udev = meta_backend_native_get_udev (backend_native); MetaKms *kms; kms = g_object_new (META_TYPE_KMS, NULL); kms->backend = backend; kms->impl = META_KMS_IMPL (meta_kms_impl_simple_new (kms, error)); if (!kms->impl) { g_object_unref (kms); return NULL; } kms->hotplug_handler_id = g_signal_connect (udev, "hotplug", G_CALLBACK (on_udev_hotplug), kms); kms->removed_handler_id = g_signal_connect (udev, "device-removed", G_CALLBACK (on_udev_device_removed), kms); return kms; } static void meta_kms_finalize (GObject *object) { MetaKms *kms = META_KMS (object); MetaBackendNative *backend_native = META_BACKEND_NATIVE (kms->backend); MetaUdev *udev = meta_backend_native_get_udev (backend_native); GList *l; for (l = kms->pending_callbacks; l; l = l->next) meta_kms_callback_data_free (l->data); g_list_free (kms->pending_callbacks); g_clear_handle_id (&kms->callback_source_id, g_source_remove); g_list_free_full (kms->devices, g_object_unref); if (kms->hotplug_handler_id) g_signal_handler_disconnect (udev, kms->hotplug_handler_id); if (kms->removed_handler_id) g_signal_handler_disconnect (udev, kms->removed_handler_id); G_OBJECT_CLASS (meta_kms_parent_class)->finalize (object); } static void meta_kms_init (MetaKms *kms) { } static void meta_kms_class_init (MetaKmsClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = meta_kms_finalize; }