From ec4e20fa02a07c3467fbc9d39ee7aecd32d227b0 Mon Sep 17 00:00:00 2001 From: Christopher James Halse Rogers Date: Mon, 5 Aug 2024 13:04:54 +1000 Subject: [PATCH 01/27] platforms/common: Add mgk::get_cap_checked() --- .../server/kms-utils/drm_mode_resources.cpp | 17 +++++++++++++++++ .../server/kms-utils/drm_mode_resources.h | 3 +++ 2 files changed, 20 insertions(+) diff --git a/src/platforms/common/server/kms-utils/drm_mode_resources.cpp b/src/platforms/common/server/kms-utils/drm_mode_resources.cpp index 717673538da..d6c9e51825e 100644 --- a/src/platforms/common/server/kms-utils/drm_mode_resources.cpp +++ b/src/platforms/common/server/kms-utils/drm_mode_resources.cpp @@ -17,7 +17,10 @@ #include "drm_mode_resources.h" #include +#include #include +#include +#include namespace mgk = mir::graphics::kms; namespace mgkd = mgk::detail; @@ -460,3 +463,17 @@ template bool mgkd::ObjectCollection::iterator::operator!=(iterator const&) const; template bool mgkd::ObjectCollection::iterator::operator!=(iterator const&) const; template bool mgkd::ObjectCollection::iterator::operator!=(iterator const&) const; + +auto mgk::get_cap_checked(mir::Fd const& drm_node, uint64_t cap) -> uint64_t +{ + uint64_t result; + if (auto err = drmGetCap(drm_node, cap, &result)) + { + BOOST_THROW_EXCEPTION(( + std::system_error{ + -err, + std::system_category(), + std::format("Failed to query DRM capability {}", cap)})); + } + return result; +} diff --git a/src/platforms/common/server/kms-utils/drm_mode_resources.h b/src/platforms/common/server/kms-utils/drm_mode_resources.h index 54181753bf4..410f6b4b8d1 100644 --- a/src/platforms/common/server/kms-utils/drm_mode_resources.h +++ b/src/platforms/common/server/kms-utils/drm_mode_resources.h @@ -17,6 +17,7 @@ #ifndef MIR_GRAPHICS_COMMON_KMS_UTILS_DRM_MODE_RESOURCES_H_ #define MIR_GRAPHICS_COMMON_KMS_UTILS_DRM_MODE_RESOURCES_H_ +#include "mir/fd.h" #include #include @@ -164,6 +165,8 @@ class DRMModeResources DRMModeResUPtr const resources; }; +auto get_cap_checked(mir::Fd const& drm_node, uint64_t cap) -> uint64_t; + } } } From ba3b2f3f6f37b2e29e2e0f9b4f0d444d8991e280 Mon Sep 17 00:00:00 2001 From: Christopher James Halse Rogers Date: Mon, 5 Aug 2024 13:05:59 +1000 Subject: [PATCH 02/27] platforms/common: Add missing `mgk::ObjectProperties` implementations --- .../server/kms-utils/drm_mode_resources.cpp | 22 ++++++++++++++++++- .../server/kms-utils/drm_mode_resources.h | 5 +++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/platforms/common/server/kms-utils/drm_mode_resources.cpp b/src/platforms/common/server/kms-utils/drm_mode_resources.cpp index d6c9e51825e..fd179d84018 100644 --- a/src/platforms/common/server/kms-utils/drm_mode_resources.cpp +++ b/src/platforms/common/server/kms-utils/drm_mode_resources.cpp @@ -271,7 +271,15 @@ mgk::ObjectProperties::ObjectProperties( int drm_fd, uint32_t object_id, uint32_t object_type) - : properties_table{extract_properties(drm_fd, get_object_properties(drm_fd, object_id, object_type))} + : parent_id_{object_id}, + properties_table{extract_properties(drm_fd, get_object_properties(drm_fd, object_id, object_type))} +{ +} + +mgk::ObjectProperties::ObjectProperties( + int drm_fd, + DRMModeCrtcUPtr const& crtc) + : ObjectProperties(drm_fd, crtc->crtc_id, DRM_MODE_OBJECT_CRTC) { } @@ -282,6 +290,13 @@ mgk::ObjectProperties::ObjectProperties( { } +mgk::ObjectProperties::ObjectProperties( + int drm_fd, + DRMModeConnectorUPtr const& connector) + : ObjectProperties(drm_fd, connector->connector_id, DRM_MODE_OBJECT_CONNECTOR) +{ +} + uint64_t mgk::ObjectProperties::operator[](char const* name) const { return properties_table.at(name).value; @@ -297,6 +312,11 @@ bool mgk::ObjectProperties::has_property(char const* property_name) const return properties_table.count(property_name) > 0; } +uint32_t mgk::ObjectProperties::parent_id() const +{ + return parent_id_; +} + auto mgk::ObjectProperties::begin() const -> std::unordered_map::const_iterator { return properties_table.begin(); diff --git a/src/platforms/common/server/kms-utils/drm_mode_resources.h b/src/platforms/common/server/kms-utils/drm_mode_resources.h index 410f6b4b8d1..986fa370bef 100644 --- a/src/platforms/common/server/kms-utils/drm_mode_resources.h +++ b/src/platforms/common/server/kms-utils/drm_mode_resources.h @@ -120,7 +120,12 @@ class ObjectProperties std::unordered_map::const_iterator begin() const; std::unordered_map::const_iterator end() const; + /** + * The ID of the DRM object these properties relate to + */ + uint32_t parent_id() const; private: + uint32_t const parent_id_; std::unordered_map const properties_table; }; From 59dd7260ab2aceabaf60d96338aeaf94412a3041 Mon Sep 17 00:00:00 2001 From: Christopher James Halse Rogers Date: Mon, 5 Aug 2024 13:11:57 +1000 Subject: [PATCH 03/27] platforms: Add atomic-kms platform This is initially a copy of the display half of `gbm-kms`, quickly ported to use only the atomic KMS APIs. It shall be further developed to usefully use the atomic APIs to fix various TODOs, and provide support for extra performance features --- CMakeLists.txt | 13 +- src/platforms/CMakeLists.txt | 4 + src/platforms/atomic-kms/CMakeLists.txt | 1 + .../include/gbm_format_conversions.h | 34 + .../atomic-kms/server/CMakeLists.txt | 24 + .../atomic-kms/server/display_helpers.cpp | 241 ++++++ .../atomic-kms/server/display_helpers.h | 89 +++ .../server/gbm_display_allocator.cpp | 214 +++++ .../atomic-kms/server/gbm_display_allocator.h | 37 + .../atomic-kms/server/kms/CMakeLists.txt | 82 ++ .../server/kms/atomic_kms_output.cpp | 749 ++++++++++++++++++ .../atomic-kms/server/kms/atomic_kms_output.h | 103 +++ .../atomic-kms/server/kms/bypass.cpp | 46 ++ src/platforms/atomic-kms/server/kms/bypass.h | 44 + .../atomic-kms/server/kms/display.cpp | 498 ++++++++++++ src/platforms/atomic-kms/server/kms/display.h | 131 +++ .../atomic-kms/server/kms/display_buffer.cpp | 358 +++++++++ .../atomic-kms/server/kms/display_sink.h | 126 +++ .../atomic-kms/server/kms/egl_helper.cpp | 286 +++++++ .../atomic-kms/server/kms/egl_helper.h | 87 ++ .../server/kms/kms_display_configuration.h | 47 ++ .../atomic-kms/server/kms/kms_output.h | 109 +++ .../server/kms/kms_output_container.h | 53 ++ .../server/kms/kms_page_flipper.cpp | 188 +++++ .../atomic-kms/server/kms/kms_page_flipper.h | 76 ++ .../atomic-kms/server/kms/platform.cpp | 159 ++++ .../atomic-kms/server/kms/platform.h | 82 ++ .../server/kms/platform_symbols.cpp | 322 ++++++++ .../atomic-kms/server/kms/quirks.cpp | 204 +++++ src/platforms/atomic-kms/server/kms/quirks.h | 67 ++ .../kms/real_kms_display_configuration.cpp | 235 ++++++ .../kms/real_kms_display_configuration.h | 65 ++ .../server/kms/real_kms_output_container.cpp | 78 ++ .../server/kms/real_kms_output_container.h | 54 ++ .../atomic-kms/server/kms/symbols.map.in | 9 + .../atomic-kms/server/platform_common.h | 36 + .../kms-utils/threaded_drm_event_handler.cpp | 2 - tests/mir_test_doubles/CMakeLists.txt | 2 +- tests/unit-tests/CMakeLists.txt | 4 + .../graphics/test_platform_prober.cpp | 25 +- 40 files changed, 4964 insertions(+), 20 deletions(-) create mode 100644 src/platforms/atomic-kms/CMakeLists.txt create mode 100644 src/platforms/atomic-kms/include/gbm_format_conversions.h create mode 100644 src/platforms/atomic-kms/server/CMakeLists.txt create mode 100644 src/platforms/atomic-kms/server/display_helpers.cpp create mode 100644 src/platforms/atomic-kms/server/display_helpers.h create mode 100644 src/platforms/atomic-kms/server/gbm_display_allocator.cpp create mode 100644 src/platforms/atomic-kms/server/gbm_display_allocator.h create mode 100644 src/platforms/atomic-kms/server/kms/CMakeLists.txt create mode 100644 src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp create mode 100644 src/platforms/atomic-kms/server/kms/atomic_kms_output.h create mode 100644 src/platforms/atomic-kms/server/kms/bypass.cpp create mode 100644 src/platforms/atomic-kms/server/kms/bypass.h create mode 100644 src/platforms/atomic-kms/server/kms/display.cpp create mode 100644 src/platforms/atomic-kms/server/kms/display.h create mode 100644 src/platforms/atomic-kms/server/kms/display_buffer.cpp create mode 100644 src/platforms/atomic-kms/server/kms/display_sink.h create mode 100644 src/platforms/atomic-kms/server/kms/egl_helper.cpp create mode 100644 src/platforms/atomic-kms/server/kms/egl_helper.h create mode 100644 src/platforms/atomic-kms/server/kms/kms_display_configuration.h create mode 100644 src/platforms/atomic-kms/server/kms/kms_output.h create mode 100644 src/platforms/atomic-kms/server/kms/kms_output_container.h create mode 100644 src/platforms/atomic-kms/server/kms/kms_page_flipper.cpp create mode 100644 src/platforms/atomic-kms/server/kms/kms_page_flipper.h create mode 100644 src/platforms/atomic-kms/server/kms/platform.cpp create mode 100644 src/platforms/atomic-kms/server/kms/platform.h create mode 100644 src/platforms/atomic-kms/server/kms/platform_symbols.cpp create mode 100644 src/platforms/atomic-kms/server/kms/quirks.cpp create mode 100644 src/platforms/atomic-kms/server/kms/quirks.h create mode 100644 src/platforms/atomic-kms/server/kms/real_kms_display_configuration.cpp create mode 100644 src/platforms/atomic-kms/server/kms/real_kms_display_configuration.h create mode 100644 src/platforms/atomic-kms/server/kms/real_kms_output_container.cpp create mode 100644 src/platforms/atomic-kms/server/kms/real_kms_output_container.h create mode 100644 src/platforms/atomic-kms/server/kms/symbols.map.in create mode 100644 src/platforms/atomic-kms/server/platform_common.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e63bd11d5d..cc6562e416c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -161,18 +161,18 @@ pkg_check_modules(WAYLAND_EGLSTREAM wayland-eglstream IMPORTED_TARGET) if (WAYLAND_EGLSTREAM_FOUND) set( MIR_PLATFORM - gbm-kms;x11;eglstream-kms;wayland + gbm-kms;atomic-kms;x11;eglstream-kms;wayland CACHE STRING - "a list of graphics backends to build (options are 'gbm-kms', 'x11', 'eglstream-kms', or 'wayland')" + "a list of graphics backends to build (options are 'gbm-kms', 'atomic-kms', 'x11', 'eglstream-kms', or 'wayland')" ) else() set( MIR_PLATFORM - gbm-kms;x11;wayland + gbm-kms;atomic-kms;x11;wayland CACHE STRING - "a list of graphics backends to build (options are 'gbm-kms', 'x11', 'eglstream-kms', or 'wayland')" + "a list of graphics backends to build (options are 'gbm-kms', 'atomic-kms', 'x11', 'eglstream-kms', or 'wayland')" ) endif() @@ -209,6 +209,9 @@ foreach(platform IN LISTS MIR_PLATFORM) if (platform STREQUAL "gbm-kms") set(MIR_BUILD_PLATFORM_GBM_KMS TRUE) endif() + if (platform STREQUAL "atomic-kms") + set(MIR_BUILD_PLATFORM_ATOMIC_KMS TRUE) + endif() if (platform STREQUAL "x11") set(MIR_BUILD_PLATFORM_X11 TRUE) endif() @@ -263,7 +266,7 @@ if (HAVE_SYS_SDT_H) add_compile_definitions(LTTNG_UST_HAVE_SDT_INTEGRATION) endif() -if (MIR_BUILD_PLATFORM_GBM_KMS) +if (MIR_BUILD_PLATFORM_GBM_KMS OR MIR_BUILD_PLATFORM_ATOMIC_KMS) pkg_check_modules(GBM REQUIRED IMPORTED_TARGET gbm>=11.0.0) endif() diff --git a/src/platforms/CMakeLists.txt b/src/platforms/CMakeLists.txt index a1130e9e478..912f937abdb 100644 --- a/src/platforms/CMakeLists.txt +++ b/src/platforms/CMakeLists.txt @@ -49,6 +49,10 @@ if (MIR_BUILD_PLATFORM_GBM_KMS) add_subdirectory(gbm-kms/) endif() +if (MIR_BUILD_PLATFORM_ATOMIC_KMS) + add_subdirectory(atomic-kms/) +endif() + if (MIR_BUILD_PLATFORM_X11) add_subdirectory(x11/) endif() diff --git a/src/platforms/atomic-kms/CMakeLists.txt b/src/platforms/atomic-kms/CMakeLists.txt new file mode 100644 index 00000000000..f510a73aec3 --- /dev/null +++ b/src/platforms/atomic-kms/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(server/) diff --git a/src/platforms/atomic-kms/include/gbm_format_conversions.h b/src/platforms/atomic-kms/include/gbm_format_conversions.h new file mode 100644 index 00000000000..42f78875ad2 --- /dev/null +++ b/src/platforms/atomic-kms/include/gbm_format_conversions.h @@ -0,0 +1,34 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + +#ifndef MIR_GRAPHICS_GBM_GBM_FORMAT_CONVERSIONS_H_ +#define MIR_GRAPHICS_GBM_GBM_FORMAT_CONVERSIONS_H_ + +#include +#include +#include + +namespace mir +{ +namespace graphics +{ +namespace atomic +{ +enum : uint32_t { invalid_atomic_format = std::numeric_limits::max() }; + +} +} +} +#endif /* MIR_GRAPHICS_GBM_GBM_FORMAT_CONVERSIONS_H_ */ diff --git a/src/platforms/atomic-kms/server/CMakeLists.txt b/src/platforms/atomic-kms/server/CMakeLists.txt new file mode 100644 index 00000000000..188a8d46f4c --- /dev/null +++ b/src/platforms/atomic-kms/server/CMakeLists.txt @@ -0,0 +1,24 @@ +add_subdirectory(kms/) + +add_library( + mirsharedatomickmscommon-static STATIC + + display_helpers.cpp + gbm_display_allocator.h + gbm_display_allocator.cpp +) + +target_include_directories( + mirsharedatomickmscommon-static + PUBLIC + ${server_common_include_dirs} + ${CMAKE_CURRENT_BINARY_DIR} +) + +target_link_libraries( + mirsharedatomickmscommon-static + + server_platform_common + kms_utils + mirplatform +) diff --git a/src/platforms/atomic-kms/server/display_helpers.cpp b/src/platforms/atomic-kms/server/display_helpers.cpp new file mode 100644 index 00000000000..ea4d29655a3 --- /dev/null +++ b/src/platforms/atomic-kms/server/display_helpers.cpp @@ -0,0 +1,241 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "display_helpers.h" +#include "one_shot_device_observer.h" + +#include "kms-utils/drm_mode_resources.h" +#include "mir/graphics/egl_error.h" +#include "kms/quirks.h" + +#include "mir/udev/wrapper.h" +#include "mir/console_services.h" + +#include + +#define MIR_LOG_COMPONENT "atomic-kms" +#include "mir/log.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace mg = mir::graphics; +namespace mga = mir::graphics::atomic; +namespace mgc = mir::graphics::common; +namespace mgmh = mir::graphics::atomic::helpers; + +/************* + * DRMHelper * + *************/ + +std::vector> +mgmh::DRMHelper::open_all_devices( + std::shared_ptr const& udev, + mir::ConsoleServices& console, + mga::Quirks const& quirks) +{ + int error = ENODEV; //Default error is "there are no DRM devices" + + mir::udev::Enumerator devices(udev); + devices.match_subsystem("drm"); + devices.match_sysname("card[0-9]"); + + devices.scan_devices(); + + std::vector> opened_devices; + + for(auto& device : devices) + { + if (quirks.should_skip(device)) + { + mir::log_info("Ignoring device %s due to specified quirk", device.devnode()); + continue; + } + + mir::Fd tmp_fd; + std::unique_ptr device_handle; + try + { + device_handle = console.acquire_device( + major(device.devnum()), minor(device.devnum()), + std::make_unique(tmp_fd)) + .get(); + } + catch (std::exception const& error) + { + mir::log_warning( + "Failed to open DRM device node %s: %s", + device.devnode(), + boost::diagnostic_information(error).c_str()); + continue; + } + + // Paranoia is always justified when dealing with hardware interfaces… + if (tmp_fd == mir::Fd::invalid) + { + mir::log_critical( + "Opening the DRM device %s succeeded, but provided an invalid descriptor!", + device.devnode()); + mir::log_critical( + "This is probably a logic error in Mir, please report to https://github.com/MirServer/mir/issues"); + continue; + } + + // Check that the drm device is usable by setting the interface version we use (1.4) + drmSetVersion sv; + sv.drm_di_major = 1; + sv.drm_di_minor = 4; + sv.drm_dd_major = -1; /* Don't care */ + sv.drm_dd_minor = -1; /* Don't care */ + + if ((error = -drmSetInterfaceVersion(tmp_fd, &sv))) + { + mir::log_warning( + "Failed to set DRM interface version on device %s: %i (%s)", + device.devnode(), + error, + strerror(error)); + continue; + } + + auto busid = std::unique_ptr{ + drmGetBusid(tmp_fd), &drmFreeBusid + }; + + if (!busid) + { + mir::log_warning( + "Failed to query BusID for device %s; cannot check if KMS is available", + device.devnode()); + } + else + { + switch (auto err = -drmCheckModesettingSupported(busid.get())) + { + case 0: break; + + case ENOSYS: + if (quirks.require_modesetting_support(device)) + { + mir::log_info("Ignoring non-KMS DRM device %s", device.devnode()); + error = ENOSYS; + continue; + } + + [[fallthrough]]; + case EINVAL: + mir::log_warning( + "Failed to detect whether device %s supports KMS, but continuing anyway", + device.devnode()); + break; + + default: + mir::log_warning("Unexpected error from drmCheckModesettingSupported(): %s (%i), but continuing anyway", + strerror(err), err); + mir::log_warning("Please file a bug at https://github.com/MirServer/mir/issues containing this message"); + } + } + + // Can't use make_shared with the private constructor. + opened_devices.push_back( + std::shared_ptr{ + new DRMHelper{ + std::move(tmp_fd), + std::move(device_handle)}}); + mir::log_info("Using DRM device %s", device.devnode()); + } + + if (opened_devices.size() == 0) + { + BOOST_THROW_EXCEPTION(( + std::system_error{error, std::system_category(), "Error opening DRM device"})); + } + + return opened_devices; +} + +std::unique_ptr mgmh::DRMHelper::open_any_render_node( + std::shared_ptr const& udev) +{ + mir::Fd tmp_fd; + int error = ENODEV; //Default error is "there are no DRM devices" + + mir::udev::Enumerator devices(udev); + devices.match_subsystem("drm"); + devices.match_sysname("renderD[0-9]*"); + + devices.scan_devices(); + + for(auto& device : devices) + { + // If directly opening the DRM device is good enough for X it's good enough for us! + tmp_fd = mir::Fd{open(device.devnode(), O_RDWR | O_CLOEXEC)}; + if (tmp_fd < 0) + { + error = errno; + continue; + } + + break; + } + + if (tmp_fd < 0) + { + BOOST_THROW_EXCEPTION(( + std::system_error{ + error, + std::system_category(), + "Error opening DRM device"})); + } + + return std::unique_ptr{ + new mgmh::DRMHelper{std::move(tmp_fd), nullptr}}; +} + +mgmh::DRMHelper::DRMHelper(mir::Fd&& fd, std::unique_ptr device) + : fd{std::move(fd)}, + device_handle{std::move(device)} +{ +} + +mgmh::DRMHelper::~DRMHelper() +{ +} + +/************* + * GBMHelper * + *************/ + +mgmh::GBMHelper::GBMHelper(mir::Fd const& drm_fd) + : device{gbm_create_device(drm_fd)} +{ + if (!device) + BOOST_THROW_EXCEPTION( + std::runtime_error("Failed to create GBM device")); +} + +mgmh::GBMHelper::~GBMHelper() +{ + if (device) + gbm_device_destroy(device); +} diff --git a/src/platforms/atomic-kms/server/display_helpers.h b/src/platforms/atomic-kms/server/display_helpers.h new file mode 100644 index 00000000000..6429da51da5 --- /dev/null +++ b/src/platforms/atomic-kms/server/display_helpers.h @@ -0,0 +1,89 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_GRAPHICS_ATOMIC_DISPLAY_HELPERS_H_ +#define MIR_GRAPHICS_ATOMIC_DISPLAY_HELPERS_H_ + +#include "mir/udev/wrapper.h" +#include "mir/fd.h" + +#include +#include +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic warning "-Wall" +#include +#pragma GCC diagnostic pop + +#include + +namespace mir +{ +class ConsoleServices; +class Device; + +namespace graphics +{ +namespace atomic +{ +class Quirks; + +typedef std::unique_ptr> GBMSurfaceUPtr; + +namespace helpers +{ + +class DRMHelper +{ +public: + ~DRMHelper(); + + DRMHelper(const DRMHelper &) = delete; + DRMHelper& operator=(const DRMHelper&) = delete; + + static std::vector> open_all_devices( + std::shared_ptr const& udev, + mir::ConsoleServices& console, + Quirks const& quirks); + + static std::unique_ptr open_any_render_node( + std::shared_ptr const& udev); + + mir::Fd fd; +private: + std::unique_ptr const device_handle; + + explicit DRMHelper(mir::Fd&& fd, std::unique_ptr device); +}; + +class GBMHelper +{ +public: + GBMHelper(mir::Fd const& drm_fd); + ~GBMHelper(); + + GBMHelper(const GBMHelper&) = delete; + GBMHelper& operator=(const GBMHelper&) = delete; + + gbm_device* const device; +}; + +} +} +} +} +#endif /* MIR_GRAPHICS_ATOMIC_DISPLAY_HELPERS_H_ */ diff --git a/src/platforms/atomic-kms/server/gbm_display_allocator.cpp b/src/platforms/atomic-kms/server/gbm_display_allocator.cpp new file mode 100644 index 00000000000..4e515b798ad --- /dev/null +++ b/src/platforms/atomic-kms/server/gbm_display_allocator.cpp @@ -0,0 +1,214 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "gbm_display_allocator.h" +#include "kms_framebuffer.h" + +#include +#include +#include + +namespace mg = mir::graphics; +namespace mga = mir::graphics::atomic; +namespace geom = mir::geometry; + +mga::GBMDisplayAllocator::GBMDisplayAllocator(mir::Fd drm_fd, std::shared_ptr gbm, geom::Size size) + : fd{std::move(drm_fd)}, + gbm{std::move(gbm)}, + size{size} +{ +} + +auto mga::GBMDisplayAllocator::supported_formats() const -> std::vector +{ + // TODO: Pull out of KMS plane info + return { DRMFormat{DRM_FORMAT_XRGB8888}, DRMFormat{DRM_FORMAT_ARGB8888}}; +} + +auto mga::GBMDisplayAllocator::modifiers_for_format(DRMFormat /*format*/) const -> std::vector +{ + // TODO: Pull out off KMS plane info + return {}; +} + +namespace +{ +using LockedFrontBuffer = std::unique_ptr>; + +class GBMBoFramebuffer : public mg::FBHandle +{ +public: + static auto framebuffer_for_frontbuffer(mir::Fd const& drm_fd, LockedFrontBuffer bo) -> std::unique_ptr + { + if (auto cached_fb = static_cast*>(gbm_bo_get_user_data(bo.get()))) + { + return std::unique_ptr{new GBMBoFramebuffer{std::move(bo), *cached_fb}}; + } + + auto fb_id = new std::shared_ptr{ + new uint32_t{0}, + [drm_fd](uint32_t* fb_id) + { + if (*fb_id) + { + drmModeRmFB(drm_fd, *fb_id); + } + delete fb_id; + }}; + uint32_t handles[4] = {gbm_bo_get_handle(bo.get()).u32, 0, 0, 0}; + uint32_t strides[4] = {gbm_bo_get_stride(bo.get()), 0, 0, 0}; + uint32_t offsets[4] = {gbm_bo_get_offset(bo.get(), 0), 0, 0, 0}; + + auto format = gbm_bo_get_format(bo.get()); + + auto const width = gbm_bo_get_width(bo.get()); + auto const height = gbm_bo_get_height(bo.get()); + + /* Create a KMS FB object with the gbm_bo attached to it. */ + auto ret = drmModeAddFB2(drm_fd, width, height, format, + handles, strides, offsets, fb_id->get(), 0); + if (ret) + return nullptr; + + gbm_bo_set_user_data(bo.get(), fb_id, [](gbm_bo*, void* fb_ptr) { delete static_cast*>(fb_ptr); }); + + return std::unique_ptr{new GBMBoFramebuffer{std::move(bo), *fb_id}}; + } + + operator uint32_t() const override + { + return *fb_id; + } + + auto size() const -> geom::Size override + { + return + geom::Size{ + gbm_bo_get_width(bo.get()), + gbm_bo_get_height(bo.get())}; + } +private: + GBMBoFramebuffer(LockedFrontBuffer bo, std::shared_ptr fb) + : bo{std::move(bo)}, + fb_id{std::move(fb)} + { + } + + LockedFrontBuffer const bo; + std::shared_ptr const fb_id; +}; + +namespace +{ +auto create_gbm_surface(gbm_device* gbm, geom::Size size, mg::DRMFormat format, std::span modifiers) + -> std::shared_ptr +{ + auto const surface = + [&]() + { + if (modifiers.empty()) + { + // If we have no no modifiers don't use the with-modifiers creation path. + auto foo = GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT; + return gbm_surface_create( + gbm, + size.width.as_uint32_t(), size.height.as_uint32_t(), + format, + foo); + } + else + { + return gbm_surface_create_with_modifiers2( + gbm, + size.width.as_uint32_t(), size.height.as_uint32_t(), + format, + modifiers.data(), + modifiers.size(), + GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT); + } + }(); + + if (!surface) + { + BOOST_THROW_EXCEPTION(( + std::system_error{ + errno, + std::system_category(), + "Failed to create GBM surface"})); + + } + return std::shared_ptr{ + surface, + [](auto surface) { gbm_surface_destroy(surface); }}; +} +} + +class GBMSurfaceImpl : public mga::GBMDisplayAllocator::GBMSurface +{ +public: + GBMSurfaceImpl(mir::Fd drm_fd, gbm_device* gbm, geom::Size size, mg::DRMFormat const format, std::span modifiers) + : drm_fd{std::move(drm_fd)}, + surface{create_gbm_surface(gbm, size, format, modifiers)} + { + } + + GBMSurfaceImpl(GBMSurfaceImpl const&) = delete; + auto operator=(GBMSurfaceImpl const&) -> GBMSurfaceImpl const& = delete; + + operator gbm_surface*() const override + { + return surface.get(); + } + + auto claim_framebuffer() -> std::unique_ptr override + { + if (!gbm_surface_has_free_buffers(surface.get())) + { + BOOST_THROW_EXCEPTION(( + std::system_error{ + EBUSY, + std::system_category(), + "Too many buffers consumed from GBM surface"})); + } + + LockedFrontBuffer bo{ + gbm_surface_lock_front_buffer(surface.get()), + [shared_surface = surface](gbm_bo* bo) { gbm_surface_release_buffer(shared_surface.get(), bo); }}; + + if (!bo) + { + BOOST_THROW_EXCEPTION((std::runtime_error{"Failed to acquire GBM front buffer"})); + } + + auto fb = GBMBoFramebuffer::framebuffer_for_frontbuffer(drm_fd, std::move(bo)); + if (!fb) + { + BOOST_THROW_EXCEPTION((std::system_error{errno, std::system_category(), "Failed to make DRM FB"})); + } + return fb; + } +private: + mir::Fd const drm_fd; + std::shared_ptr const surface; +}; +} + +auto mga::GBMDisplayAllocator::make_surface(DRMFormat format, std::span modifiers) + -> std::unique_ptr +{ + return std::make_unique(fd, gbm.get(), size, format, modifiers); +} + diff --git a/src/platforms/atomic-kms/server/gbm_display_allocator.h b/src/platforms/atomic-kms/server/gbm_display_allocator.h new file mode 100644 index 00000000000..e9a08b41385 --- /dev/null +++ b/src/platforms/atomic-kms/server/gbm_display_allocator.h @@ -0,0 +1,37 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "mir/graphics/platform.h" +#include "mir/fd.h" + +namespace mir::graphics::atomic +{ +class GBMDisplayAllocator : public graphics::GBMDisplayAllocator +{ +public: + GBMDisplayAllocator(mir::Fd drm_fd, std::shared_ptr atomic, geometry::Size size); + + auto supported_formats() const -> std::vector override; + + auto modifiers_for_format(DRMFormat format) const -> std::vector override; + + auto make_surface(DRMFormat format, std::span modifier) -> std::unique_ptr override; +private: + mir::Fd const fd; + std::shared_ptr const gbm; + geometry::Size const size; +}; +} diff --git a/src/platforms/atomic-kms/server/kms/CMakeLists.txt b/src/platforms/atomic-kms/server/kms/CMakeLists.txt new file mode 100644 index 00000000000..537fe92599a --- /dev/null +++ b/src/platforms/atomic-kms/server/kms/CMakeLists.txt @@ -0,0 +1,82 @@ +include_directories( + ${server_common_include_dirs} + .. +) + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR}/../ +) + +# gbm-kms.h and drm.h have trailing commas at the end of enum definitions +# This is valid C99, but g++ 4.4 flags it as an error with -pedantic +string(REPLACE "-pedantic" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) + +add_compile_definitions( + __GBM__ + MIR_LOG_COMPONENT_FALLBACK="atomic-kms" +) + +add_library( + mirplatformgraphicsatomickmsobjects OBJECT + + atomic_kms_output.cpp + atomic_kms_output.h + bypass.cpp + display.cpp + display_buffer.cpp + platform.cpp + kms_display_configuration.h + real_kms_display_configuration.cpp + kms_output.h + kms_output_container.h + real_kms_output_container.cpp + egl_helper.h + egl_helper.cpp + quirks.cpp + quirks.h +) + +target_link_libraries( + mirplatformgraphicsatomickmsobjects + + PRIVATE + mirsharedatomickmscommon-static + ${GBM_LDFLAGS} ${GBM_LIBRARIES} +) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/symbols.map.in + ${CMAKE_CURRENT_BINARY_DIR}/symbols.map + ) +set(symbol_map ${CMAKE_CURRENT_BINARY_DIR}/symbols.map) + +add_library(mirplatformgraphicsatomickms MODULE + platform_symbols.cpp +) + +target_link_libraries( + mirplatformgraphicsatomickms + + PRIVATE + mirplatform + mirplatformgraphicsatomickmsobjects + mirsharedatomickmscommon-static + Boost::program_options + Boost::iostreams + PkgConfig::DRM + PkgConfig::GBM + PkgConfig::EGL + PkgConfig::GLESv2 + PkgConfig::WAYLAND_SERVER +) + +set_target_properties( + mirplatformgraphicsatomickms PROPERTIES + OUTPUT_NAME graphics-atomic-kms + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/server-modules + PREFIX "" + SUFFIX ".so.${MIR_SERVER_GRAPHICS_PLATFORM_ABI}" + LINK_FLAGS "-Wl,--exclude-libs=ALL -Wl,--version-script,${symbol_map}" + LINK_DEPENDS ${symbol_map} +) + +install(TARGETS mirplatformgraphicsatomickms LIBRARY DESTINATION ${MIR_SERVER_PLATFORM_PATH}) diff --git a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp new file mode 100644 index 00000000000..2553e8d2110 --- /dev/null +++ b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp @@ -0,0 +1,749 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "atomic_kms_output.h" +#include "kms-utils/drm_event_handler.h" +#include "kms-utils/drm_mode_resources.h" +#include "kms_framebuffer.h" +#include "mir/graphics/display_configuration.h" +#include "mir/graphics/gamma_curves.h" +#include "mir_toolkit/common.h" +#include "kms-utils/kms_connector.h" +#include "mir/fatal.h" +#include "mir/log.h" +#include +#include +#include // strcmp + +#include +#include +#include +#include + +namespace mg = mir::graphics; +namespace mga = mg::atomic; +namespace mgk = mg::kms; +namespace geom = mir::geometry; + + +namespace +{ +bool kms_modes_are_equal(drmModeModeInfo const* info1, drmModeModeInfo const* info2) +{ + return (info1 && info2) && + info1->clock == info2->clock && + info1->hdisplay == info2->hdisplay && + info1->hsync_start == info2->hsync_start && + info1->hsync_end == info2->hsync_end && + info1->htotal == info2->htotal && + info1->hskew == info2->hskew && + info1->vdisplay == info2->vdisplay && + info1->vsync_start == info2->vsync_start && + info1->vsync_end == info2->vsync_end && + info1->vtotal == info2->vtotal; +} + +uint32_t create_blob_returning_handle(mir::Fd const& drm_fd, void const* data, size_t len) +{ + uint32_t handle; + if (auto err = drmModeCreatePropertyBlob(drm_fd, data, len, &handle)) + { + BOOST_THROW_EXCEPTION(( + std::system_error{ + -err, + std::system_category(), + "Failed to create DRM property blob"})); + } + return handle; +} + +class PropertyBlobData +{ +public: + PropertyBlobData(mir::Fd const& drm_fd, uint32_t handle) + : ptr{drmModeGetPropertyBlob(drm_fd, handle)} + { + if (!ptr) + { + // drmModeGetPropertyBlob sets errno on failure, except on allocation failure + auto const err = errno ? errno : ENOMEM; + BOOST_THROW_EXCEPTION(( + std::system_error{ + err, + std::system_category(), + "Failed to read DRM property blob"})); + } + } + + ~PropertyBlobData() + { + drmModeFreePropertyBlob(ptr); + } + + template + auto data() const -> std::span + { + /* This is a precondition check, so technically unnecessary. + * That said, there are a bunch of moving parts here, so + * let's be nice and check what little we can. + */ + if (ptr->length % sizeof(T) != 0) + { + BOOST_THROW_EXCEPTION(( + std::runtime_error{ + std::format("DRM property size {} is not a multiple of expected object size {}", + ptr->length, + sizeof(T))})); + } + + /* We don't have to care about alignment, at least; libdrm will + * have copied the data into a suitably-aligned allocation + */ + return std::span{static_cast(ptr->data), ptr->length / sizeof(T)}; + } +private: + drmModePropertyBlobPtr const ptr; +}; + +class AtomicUpdate +{ +public: + AtomicUpdate() + : req{drmModeAtomicAlloc()} + { + if (!req) + { + BOOST_THROW_EXCEPTION(( + std::runtime_error{"Failed to allocate Atomic DRM update request"})); + } + } + + ~AtomicUpdate() + { + drmModeAtomicFree(req); + } + + operator drmModeAtomicReqPtr() const + { + return req; + } + + void add_property(mgk::ObjectProperties const& properties, char const* prop_name, uint64_t value) + { + drmModeAtomicAddProperty(req, properties.parent_id(), properties.id_for(prop_name), value); + } +private: + drmModeAtomicReqPtr const req; +}; +} + +class mga::AtomicKMSOutput::PropertyBlob +{ +public: + PropertyBlob(mir::Fd drm_fd, void const* data, size_t size) + : handle_{create_blob_returning_handle(drm_fd, data, size)}, + drm_fd{std::move(drm_fd)} + { + } + + ~PropertyBlob() + { + if (auto err = drmModeDestroyPropertyBlob(drm_fd, handle_)) + { + mir::log_warning( + "Failed to free DRM property blob: %s (%i)", + strerror(-err), + -err); + } + } + + uint32_t handle() const + { + return handle_; + } +private: + uint32_t const handle_; + mir::Fd const drm_fd; +}; + +mga::AtomicKMSOutput::AtomicKMSOutput( + mir::Fd drm_master, + kms::DRMModeConnectorUPtr connector, + std::shared_ptr event_handler) + : drm_fd_{drm_master}, + event_handler{std::move(event_handler)}, + connector{std::move(connector)}, + mode_index{0}, + current_crtc(), + saved_crtc(), + using_saved_crtc{true} +{ + reset(); + + kms::DRMModeResources resources{drm_fd_}; + + if (this->connector->encoder_id) + { + auto encoder = resources.encoder(this->connector->encoder_id); + if (encoder->crtc_id) + { + saved_crtc = *resources.crtc(encoder->crtc_id); + } + } +} + +mga::AtomicKMSOutput::~AtomicKMSOutput() +{ + restore_saved_crtc(); +} + +uint32_t mga::AtomicKMSOutput::id() const +{ + return connector->connector_id; +} + +void mga::AtomicKMSOutput::reset() +{ + kms::DRMModeResources resources{drm_fd_}; + + /* Update the connector to ensure we have the latest information */ + try + { + connector = resources.connector(connector->connector_id); + connector_props = std::make_unique(drm_fd_, connector); + } + catch (std::exception const& e) + { + fatal_error(e.what()); + } + + /* Discard previously current crtc */ + current_crtc = nullptr; +} + +geom::Size mga::AtomicKMSOutput::size() const +{ + // Disconnected hardware has no modes: invent a size + if (connector->connection == DRM_MODE_DISCONNECTED) + return {0, 0}; + + drmModeModeInfo const& mode(connector->modes[mode_index]); + return {mode.hdisplay, mode.vdisplay}; +} + +int mga::AtomicKMSOutput::max_refresh_rate() const +{ + if (connector->connection == DRM_MODE_DISCONNECTED) + return 1; + + drmModeModeInfo const& current_mode = connector->modes[mode_index]; + return current_mode.vrefresh; +} + +void mga::AtomicKMSOutput::configure(geom::Displacement offset, size_t kms_mode_index) +{ + fb_offset = offset; + mode_index = kms_mode_index; + mode = std::make_unique(drm_fd_, &connector->modes[mode_index], sizeof(connector->modes[mode_index])); + ensure_crtc(); +} + +bool mga::AtomicKMSOutput::set_crtc(FBHandle const& fb) +{ + if (!ensure_crtc()) + { + mir::log_error("Output %s has no associated CRTC to set a framebuffer on", + mgk::connector_name(connector).c_str()); + return false; + } + + auto const width = current_crtc->width; + auto const height = current_crtc->height; + + AtomicUpdate update; + update.add_property(*crtc_props, "MODE_ID", mode->handle()); + update.add_property(*connector_props, "CRTC_ID", current_crtc->crtc_id); + + /* Source viewport. Coordinates are 16.16 fixed point format */ + update.add_property(*plane_props, "SRC_X", fb_offset.dx.as_uint32_t() << 16); + update.add_property(*plane_props, "SRC_Y", fb_offset.dy.as_uint32_t() << 16); + update.add_property(*plane_props, "SRC_W", width << 16); + update.add_property(*plane_props, "SRC_H", height << 16); + + /* Destination viewport. Coordinates are *not* 16.16 */ + update.add_property(*plane_props, "CRTC_X", 0); + update.add_property(*plane_props, "CRTC_Y", 0); + update.add_property(*plane_props, "CRTC_W", width); + update.add_property(*plane_props, "CRTC_H", height); + + /* Set a surface for the plane */ + update.add_property(*plane_props, "CRTC_ID", current_crtc->crtc_id); + update.add_property(*plane_props, "FB_ID", fb); + + auto ret = drmModeAtomicCommit(drm_fd_, update, DRM_MODE_ATOMIC_ALLOW_MODESET, nullptr); + if (ret) + { + mir::log_error("Failed to set CRTC: %s (%i)", strerror(-ret), -ret); + current_crtc = nullptr; + return false; + } + + using_saved_crtc = false; + return true; +} + +bool mga::AtomicKMSOutput::has_crtc_mismatch() +{ + if (!ensure_crtc()) + { + mir::log_error("Output %s has no associated CRTC to get ", mgk::connector_name(connector).c_str()); + return true; + } + + return !kms_modes_are_equal(¤t_crtc->mode, &connector->modes[mode_index]); +} + +void mga::AtomicKMSOutput::clear_crtc() +{ + try + { + ensure_crtc(); + } + catch (...) + { + /* + * In order to actually clear the output, we need to have a crtc + * connected to the output/connector so that we can disconnect + * it. However, not being able to get a crtc is OK, since it means + * that the output cannot be displaying anything anyway. + */ + return; + } + + AtomicUpdate update; + update.add_property(*plane_props, "FB_ID", 0); + + auto result = drmModeAtomicCommit(drm_fd_, update, DRM_MODE_ATOMIC_ALLOW_MODESET, nullptr); + if (result) + { + if (result == -EACCES || result == -EPERM) + { + /* We don't have modesetting rights. + * + * This can happen during session switching if (eg) logind has already + * revoked device access before notifying us. + * + * Whatever we're switching to can handle the CRTCs; this should not be fatal. + */ + mir::log_info("Couldn't clear output %s (drmModeSetCrtc: %s (%i))", + mgk::connector_name(connector).c_str(), + strerror(-result), + -result); + } + else + { + fatal_error("Couldn't clear output %s (drmModeSetCrtc = %d)", + mgk::connector_name(connector).c_str(), result); + } + } + + current_crtc = nullptr; +} + +bool mga::AtomicKMSOutput::schedule_page_flip(FBHandle const& fb) +{ + if (!ensure_crtc()) + { + mir::log_error("Output %s has no associated CRTC to set a framebuffer on", + mgk::connector_name(connector).c_str()); + return false; + } + + auto const width = current_crtc->width; + auto const height = current_crtc->height; + + AtomicUpdate update; + update.add_property(*crtc_props, "MODE_ID", mode->handle()); + update.add_property(*connector_props, "CRTC_ID", current_crtc->crtc_id); + + /* Source viewport. Coordinates are 16.16 fixed point format */ + update.add_property(*plane_props, "SRC_X", fb_offset.dx.as_uint32_t() << 16); + update.add_property(*plane_props, "SRC_Y", fb_offset.dy.as_uint32_t() << 16); + update.add_property(*plane_props, "SRC_W", width << 16); + update.add_property(*plane_props, "SRC_H", height << 16); + + /* Destination viewport. Coordinates are *not* 16.16 */ + update.add_property(*plane_props, "CRTC_X", 0); + update.add_property(*plane_props, "CRTC_Y", 0); + update.add_property(*plane_props, "CRTC_W", width); + update.add_property(*plane_props, "CRTC_H", height); + + /* Set a surface for the plane */ + update.add_property(*plane_props, "CRTC_ID", current_crtc->crtc_id); + update.add_property(*plane_props, "FB_ID", fb); + + pending_page_flip = event_handler->expect_flip_event(current_crtc->crtc_id, [](auto, auto){}); + auto ret = drmModeAtomicCommit( + drm_fd_, + update, + DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT, + const_cast(event_handler->drm_event_data())); + if (ret) + { + mir::log_error("Failed to set CRTC: %s (%i)", strerror(-ret), -ret); + current_crtc = nullptr; + event_handler->cancel_flip_events(current_crtc->crtc_id); + return false; + } + + using_saved_crtc = false; + return true; +} + +void mga::AtomicKMSOutput::wait_for_page_flip() +{ + pending_page_flip.get(); +} + +bool mga::AtomicKMSOutput::set_cursor(gbm_bo*) +{ + return false; +} + +void mga::AtomicKMSOutput::move_cursor(geometry::Point) +{ +} + +bool mga::AtomicKMSOutput::clear_cursor() +{ + return false; +} + +bool mga::AtomicKMSOutput::has_cursor() const +{ + return false; +} + +bool mga::AtomicKMSOutput::ensure_crtc() +{ + /* Nothing to do if we already have a crtc */ + if (current_crtc) + return true; + + /* If the output is not connected there is nothing to do */ + if (connector->connection != DRM_MODE_CONNECTED) + return false; + + // Update the connector as we may unexpectedly fail in find_crtc_and_index_for_connector() + // https://github.com/MirServer/mir/issues/2661 + connector = kms::get_connector(drm_fd_, connector->connector_id); + std::tie(current_crtc, current_plane) = mgk::find_crtc_with_primary_plane(drm_fd_, connector); + crtc_props = std::make_unique(drm_fd_, current_crtc); + plane_props = std::make_unique(drm_fd_, current_plane); + + return (current_crtc != nullptr); +} + +void mga::AtomicKMSOutput::restore_saved_crtc() +{ + if (!using_saved_crtc) + { + drmModeSetCrtc(drm_fd_, saved_crtc.crtc_id, saved_crtc.buffer_id, + saved_crtc.x, saved_crtc.y, + &connector->connector_id, 1, &saved_crtc.mode); + + using_saved_crtc = true; + } +} + +void mga::AtomicKMSOutput::set_power_mode(MirPowerMode mode) +{ + bool should_be_active = mode == mir_power_mode_on; + std::unique_ptr + request{drmModeAtomicAlloc(), &drmModeAtomicFree}; + + drmModeAtomicAddProperty(request.get(), current_crtc->crtc_id, crtc_props->id_for("ACTIVE"), should_be_active); + drmModeAtomicCommit(drm_fd(), request.get(), DRM_MODE_ATOMIC_ALLOW_MODESET, nullptr); +} + + +void mga::AtomicKMSOutput::set_gamma(mg::GammaCurves const& gamma) +{ + if (!gamma.red.size()) + { + mir::log_warning("Ignoring attempt to set zero length gamma"); + return; + } + + if (!ensure_crtc()) + { + mir::log_warning("Output %s has no associated CRTC to set gamma on", + mgk::connector_name(connector).c_str()); + return; + } + + if (gamma.red.size() != gamma.green.size() || + gamma.green.size() != gamma.blue.size()) + { + BOOST_THROW_EXCEPTION( + std::invalid_argument("set_gamma: mismatch gamma LUT sizes")); + } + + auto drm_lut = std::make_unique(gamma.red.size()); + for (size_t i = 0; i < gamma.red.size(); ++i) + { + drm_lut[i].red = gamma.red[i]; + drm_lut[i].green = gamma.green[i]; + drm_lut[i].blue = gamma.blue[i]; + } + PropertyBlob lut{drm_fd_, drm_lut.get(), sizeof(struct drm_color_lut) * gamma.red.size()}; + AtomicUpdate update; + + update.add_property(*crtc_props, "GAMMA_LUT", lut.handle()); + drmModeAtomicCommit(drm_fd(), update, DRM_MODE_ATOMIC_ALLOW_MODESET, nullptr); +} + +void mga::AtomicKMSOutput::refresh_hardware_state() +{ + connector = kms::get_connector(drm_fd_, connector->connector_id); + current_crtc = nullptr; + + if (connector->encoder_id) + { + auto encoder = kms::get_encoder(drm_fd_, connector->encoder_id); + + if (encoder->crtc_id) + { + current_crtc = kms::get_crtc(drm_fd_, encoder->crtc_id); + } + } +} + +namespace +{ +double calculate_vrefresh_hz(drmModeModeInfo const& mode) +{ + if (mode.htotal == 0 || mode.vtotal == 0) + return 0.0; + + /* mode.clock is in KHz */ + double hz = (mode.clock * 100000LL / + ((long)mode.htotal * (long)mode.vtotal) + ) / 100.0; + + // Actually we don't need floating point at all for this... + // TODO: Consider converting our structs to fixed-point ints + return hz; +} + +mg::DisplayConfigurationOutputType +kms_connector_type_to_output_type(uint32_t connector_type) +{ + return static_cast(connector_type); +} + +MirSubpixelArrangement kms_subpixel_to_mir_subpixel(uint32_t subpixel) +{ + switch (subpixel) + { + case DRM_MODE_SUBPIXEL_UNKNOWN: + return mir_subpixel_arrangement_unknown; + case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB: + return mir_subpixel_arrangement_horizontal_rgb; + case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR: + return mir_subpixel_arrangement_horizontal_bgr; + case DRM_MODE_SUBPIXEL_VERTICAL_RGB: + return mir_subpixel_arrangement_vertical_rgb; + case DRM_MODE_SUBPIXEL_VERTICAL_BGR: + return mir_subpixel_arrangement_vertical_bgr; + case DRM_MODE_SUBPIXEL_NONE: + return mir_subpixel_arrangement_none; + default: + return mir_subpixel_arrangement_unknown; + } +} + +std::vector edid_for_connector(mir::Fd const& drm_fd, uint32_t connector_id) +{ + std::vector edid; + + mgk::ObjectProperties connector_props{ + drm_fd, connector_id, DRM_MODE_OBJECT_CONNECTOR}; + + if (connector_props.has_property("EDID")) + { + /* + * We don't technically need the property information here, but query it + * anyway so we can detect if our assumptions about DRM behaviour + * become invalid. + */ + auto property = mgk::DRMModePropertyUPtr{ + drmModeGetProperty(drm_fd, connector_props.id_for("EDID")), + &drmModeFreeProperty}; + + if (!property) + { + mir::log_warning( + "Failed to get EDID property for connector %u: %i (%s)", + connector_id, + errno, + ::strerror(errno)); + return edid; + } + + if (!drm_property_type_is(property.get(), DRM_MODE_PROP_BLOB)) + { + mir::log_warning( + "EDID property on connector %u has unexpected type %u", + connector_id, + property->flags); + return edid; + } + + // A property value of 0 means invalid. + if (connector_props["EDID"] == 0) + { + /* + * Log a debug message only. This will trigger for broken monitors which + * don't provide an EDID, which is not as unusual as you might think... + */ + mir::log_debug("No EDID data available on connector %u", connector_id); + return edid; + } + + try + { + PropertyBlobData edid_blob{drm_fd, static_cast(connector_props["EDID"])}; + edid.reserve(edid_blob.data().size_bytes()); + edid.assign(edid_blob.data().begin(), edid_blob.data().end()); + + edid.shrink_to_fit(); + } + catch (std::system_error const& err) + { + // Failing to read the EDID property is weird, but shouldn't be fatal + mir::log_warning( + "Failed to get EDID property blob for connector %u: %i (%s)", + connector_id, + err.code().value(), + err.what()); + + return edid; + } + } + + return edid; +} +} + +void mga::AtomicKMSOutput::update_from_hardware_state( + DisplayConfigurationOutput& output) const +{ + DisplayConfigurationOutputType const type{ + kms_connector_type_to_output_type(connector->connector_type)}; + geom::Size physical_size{connector->mmWidth, connector->mmHeight}; + bool connected{connector->connection == DRM_MODE_CONNECTED}; + uint32_t const invalid_mode_index = std::numeric_limits::max(); + uint32_t current_mode_index{invalid_mode_index}; + uint32_t preferred_mode_index{invalid_mode_index}; + std::vector modes; + std::vector formats{mir_pixel_format_argb_8888, // PULL THESE OUT OF THE PROPERTIES + mir_pixel_format_xrgb_8888}; + + std::vector edid; + if (connected) + { + /* Only ask for the EDID on connected outputs. There's obviously no monitor EDID + * when there is no monitor connected! + */ + edid = edid_for_connector(drm_fd_, connector->connector_id); + } + + auto const current_mode_info = + [&]() -> drmModeModeInfo const* + { + if (current_crtc) + { + return ¤t_crtc->mode; + } + return nullptr; + }(); + + GammaCurves gamma; + if (current_crtc && crtc_props->has_property("GAMMA_LUT") && crtc_props->has_property("GAMMA_LUT_SIZE")) + { + PropertyBlobData gamma_lut{drm_fd_, static_cast((*crtc_props)["GAMMA_LUT"])}; + auto const gamma_size = gamma_lut.data().size(); + gamma.red.reserve(gamma_size); + gamma.green.reserve(gamma_size); + gamma.blue.reserve(gamma_size); + for (auto const& entry : gamma_lut.data()) + { + gamma.red.push_back(entry.red); + gamma.green.push_back(entry.green); + gamma.blue.push_back(entry.blue); + } + } + + /* Add all the available modes and find the current and preferred one */ + for (int m = 0; m < connector->count_modes; m++) { + drmModeModeInfo const& mode_info = connector->modes[m]; + + geom::Size size{mode_info.hdisplay, mode_info.vdisplay}; + + double vrefresh_hz = calculate_vrefresh_hz(mode_info); + + modes.push_back({size, vrefresh_hz}); + + if (kms_modes_are_equal(&mode_info, current_mode_info)) + current_mode_index = m; + + if ((mode_info.type & DRM_MODE_TYPE_PREFERRED) == DRM_MODE_TYPE_PREFERRED) + preferred_mode_index = m; + } + + /* Fallback for VMWare which fails to specify a matching current mode (bug:1661295) */ + if (current_mode_index == invalid_mode_index) { + for (int m = 0; m != connector->count_modes; ++m) { + drmModeModeInfo &mode_info = connector->modes[m]; + + if (strcmp(mode_info.name, "preferred") == 0) + current_mode_index = m; + } + } + + // There's no need to warn about failing to find a current display mode on a disconnected display. + if (connected && (current_mode_index == invalid_mode_index)) { + mir::log_warning( + "Unable to determine the current display mode."); + } + + output.type = type; + output.modes = modes; + output.preferred_mode_index = preferred_mode_index; + output.physical_size_mm = physical_size; + output.connected = connected; + output.current_format = mir_pixel_format_xrgb_8888; + output.current_mode_index = current_mode_index; + output.subpixel_arrangement = kms_subpixel_to_mir_subpixel(connector->subpixel); + output.gamma = gamma; + output.edid = edid; +} + +int mga::AtomicKMSOutput::drm_fd() const +{ + return drm_fd_; +} diff --git a/src/platforms/atomic-kms/server/kms/atomic_kms_output.h b/src/platforms/atomic-kms/server/kms/atomic_kms_output.h new file mode 100644 index 00000000000..c10a406479c --- /dev/null +++ b/src/platforms/atomic-kms/server/kms/atomic_kms_output.h @@ -0,0 +1,103 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_GRAPHICS_GBM_ATOMIC_KMS_OUTPUT_H_ +#define MIR_GRAPHICS_GBM_ATOMIC_KMS_OUTPUT_H_ + +#include "kms_output.h" +#include "kms-utils/drm_mode_resources.h" +#include "mir/fd.h" + +#include +#include + +namespace mir +{ +namespace graphics +{ +namespace kms +{ +class DRMEventHandler; +} +namespace atomic +{ + +class PageFlipper; + +class AtomicKMSOutput : public KMSOutput +{ +public: + AtomicKMSOutput( + mir::Fd drm_master, + kms::DRMModeConnectorUPtr connector, + std::shared_ptr event_handler); + ~AtomicKMSOutput(); + + uint32_t id() const override; + + void reset() override; + void configure(geometry::Displacement fb_offset, size_t kms_mode_index) override; + geometry::Size size() const override; + int max_refresh_rate() const override; + + bool set_crtc(FBHandle const& fb) override; + bool has_crtc_mismatch() override; + void clear_crtc() override; + bool schedule_page_flip(FBHandle const& fb) override; + void wait_for_page_flip() override; + + bool set_cursor(gbm_bo* buffer) override; + void move_cursor(geometry::Point destination) override; + bool clear_cursor() override; + bool has_cursor() const override; + + void set_power_mode(MirPowerMode mode) override; + void set_gamma(GammaCurves const& gamma) override; + + void refresh_hardware_state() override; + void update_from_hardware_state(DisplayConfigurationOutput& output) const override; + + int drm_fd() const override; + +private: + bool ensure_crtc(); + void restore_saved_crtc(); + + mir::Fd const drm_fd_; + std::shared_ptr const event_handler; + + std::future pending_page_flip; + + class PropertyBlob; + + kms::DRMModeConnectorUPtr connector; + size_t mode_index; + geometry::Displacement fb_offset; + kms::DRMModeCrtcUPtr current_crtc; + kms::DRMModePlaneUPtr current_plane; + std::unique_ptr mode; + std::unique_ptr crtc_props; + std::unique_ptr plane_props; + std::unique_ptr connector_props; + drmModeCrtc saved_crtc; + bool using_saved_crtc; +}; + +} +} +} + +#endif /* MIR_GRAPHICS_GBM_ATOMIC_KMS_OUTPUT_H_ */ diff --git a/src/platforms/atomic-kms/server/kms/bypass.cpp b/src/platforms/atomic-kms/server/kms/bypass.cpp new file mode 100644 index 00000000000..69bb8745b78 --- /dev/null +++ b/src/platforms/atomic-kms/server/kms/bypass.cpp @@ -0,0 +1,46 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "bypass.h" + +#include "mir/graphics/renderable.h" + +using namespace mir; +namespace mga = mir::graphics::atomic; + +mga::BypassMatch::BypassMatch(geometry::Rectangle const& rect) + : view_area(rect), + bypass_is_feasible(true), + identity(1) +{ +} + +bool mga::BypassMatch::operator()(std::shared_ptr const& renderable) +{ + //we've already eliminated bypass as a possibility + if (!bypass_is_feasible) + return false; + + //offscreen surfaces don't affect if bypass is possible + if (!view_area.overlaps(renderable->screen_position())) + return false; + + auto const is_opaque = !((renderable->alpha() != 1.0f) || renderable->shaped()); + auto const fits = (renderable->screen_position() == view_area); + auto const is_orthogonal = (renderable->transformation() == identity); + bypass_is_feasible = (is_opaque && fits && is_orthogonal); + return bypass_is_feasible; +} diff --git a/src/platforms/atomic-kms/server/kms/bypass.h b/src/platforms/atomic-kms/server/kms/bypass.h new file mode 100644 index 00000000000..0637ff0c429 --- /dev/null +++ b/src/platforms/atomic-kms/server/kms/bypass.h @@ -0,0 +1,44 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_GRAPHICS_GBM_BYPASS_H_ +#define MIR_GRAPHICS_GBM_BYPASS_H_ + +#include "mir/graphics/renderable.h" + +namespace mir +{ +namespace graphics +{ +namespace atomic +{ + +class BypassMatch +{ +public: + BypassMatch(geometry::Rectangle const& rect); + bool operator()(std::shared_ptr const&); +private: + geometry::Rectangle const view_area; + bool bypass_is_feasible; + glm::mat4 const identity; +}; + +} // namespace atomic-kms +} // namespace graphics +} // namespace mir + +#endif // MIR_GRAPHICS_GBM_BYPASS_H_ diff --git a/src/platforms/atomic-kms/server/kms/display.cpp b/src/platforms/atomic-kms/server/kms/display.cpp new file mode 100644 index 00000000000..dc0aa42734b --- /dev/null +++ b/src/platforms/atomic-kms/server/kms/display.cpp @@ -0,0 +1,498 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "display.h" +#include "kms-utils/threaded_drm_event_handler.h" +#include "kms/egl_helper.h" +#include "mir/graphics/platform.h" +#include "platform.h" +#include "display_sink.h" +#include "kms_display_configuration.h" +#include "kms_output.h" +#include "mir/console_services.h" +#include "mir/graphics/overlapping_output_grouping.h" +#include "mir/graphics/event_handler_register.h" + +#include "kms_framebuffer.h" +#include "mir/graphics/display_report.h" +#include "mir/graphics/display_configuration_policy.h" +#include "mir/graphics/transformation.h" +#include "mir/geometry/rectangle.h" +#include "mir/renderer/gl/context.h" +#include "mir/graphics/drm_formats.h" +#include "mir/graphics/egl_error.h" + +#include +#include + +#include +#include +#include +#include +#define MIR_LOG_COMPONENT "atomic-kms" +#include "mir/log.h" +#include "kms-utils/drm_mode_resources.h" +#include "kms-utils/kms_connector.h" + +#include +#include +#include +#include + +#include +#include +#include + +namespace mga = mir::graphics::atomic; +namespace mg = mir::graphics; +namespace geom = mir::geometry; + +namespace +{ +double calculate_vrefresh_hz(drmModeModeInfo const& mode) +{ + if (mode.htotal == 0 || mode.vtotal == 0) + return 0.0; + + /* mode.clock is in KHz */ + double hz = (mode.clock * 100000LL / + ((long)mode.htotal * (long)mode.vtotal) + ) / 100.0; + + return hz; +} + +char const* describe_connection_status(drmModeConnector const& connection) +{ + switch (connection.connection) + { + case DRM_MODE_CONNECTED: + return "connected"; + case DRM_MODE_DISCONNECTED: + return "disconnected"; + case DRM_MODE_UNKNOWNCONNECTION: + return "UNKNOWN"; + default: + return ""; + } +} + +void log_drm_details(mir::Fd const& drm_fd) +{ + mir::log_info("DRM device details:"); + auto version = std::unique_ptr{ + drmGetVersion(drm_fd), + &drmFreeVersion}; + + auto device_name = std::unique_ptr{ + drmGetDeviceNameFromFd(drm_fd), + &free + }; + + mir::log_info( + "%s: using driver %s [%s] (version: %i.%i.%i driver date: %s)", + device_name.get(), + version->name, + version->desc, + version->version_major, + version->version_minor, + version->version_patchlevel, + version->date); + + try + { + mg::kms::DRMModeResources resources{drm_fd}; + for (auto const& connector : resources.connectors()) + { + mir::log_info( + "\tOutput: %s (%s)", + mg::kms::connector_name(connector).c_str(), + describe_connection_status(*connector)); + for (auto i = 0; i < connector->count_modes; ++i) + { + mir::log_info( + "\t\tMode: %i×%i@%.2f", + connector->modes[i].hdisplay, + connector->modes[i].vdisplay, + calculate_vrefresh_hz(connector->modes[i])); + } + } + } + catch (std::exception const& error) + { + mir::log_info( + "\tKMS not supported (%s)", + error.what()); + } +} + +} + +mga::Display::Display( + mir::Fd drm_fd, + std::shared_ptr gbm, + mga::BypassOption bypass_option, + std::shared_ptr const& initial_conf_policy, + std::shared_ptr const& listener) + : drm_fd{std::move(drm_fd)}, + event_handler{std::make_shared(this->drm_fd)}, + gbm{std::move(gbm)}, + listener(listener), + monitor(mir::udev::Context()), + output_container{ + std::make_shared( + this->drm_fd, + event_handler)}, + current_display_configuration{output_container}, + dirty_configuration{false}, + bypass_option(bypass_option) +{ + monitor.filter_by_subsystem_and_type("drm", "drm_minor"); + monitor.enable(); + + log_drm_details(this->drm_fd); + + initial_conf_policy->apply_to(current_display_configuration); + + configure(current_display_configuration); +} + +// please don't remove this empty destructor, it's here for the +// unique ptr!! if you accidentally remove it you will get a not +// so relevant linker error about some missing headers +mga::Display::~Display() = default; + +void mga::Display::for_each_display_sync_group( + std::function const& f) +{ + std::lock_guard lg{configuration_mutex}; + + for (auto& db_ptr : display_sinks) + f(*db_ptr); +} + +std::unique_ptr mga::Display::configuration() const +{ + std::lock_guard lg{configuration_mutex}; + + if (dirty_configuration) + { + /* Give back a copy of the latest configuration information */ + current_display_configuration.update(); + dirty_configuration = false; + } + + return std::make_unique(current_display_configuration); +} + +void mga::Display::configure(mg::DisplayConfiguration const& conf) +{ + if (!conf.valid()) + { + BOOST_THROW_EXCEPTION( + std::logic_error("Invalid or inconsistent display configuration")); + } + + { + std::lock_guard lock{configuration_mutex}; + configure_locked(dynamic_cast(conf), lock); + } +} + +void mga::Display::register_configuration_change_handler( + EventHandlerRegister& handlers, + DisplayConfigurationChangeHandler const& conf_change_handler) +{ + handlers.register_fd_handler( + {monitor.fd()}, + this, + make_module_ptr>( + [conf_change_handler, this](int) + { + mir::log_debug("Handling UDEV events"); + monitor.process_events([conf_change_handler, this] + (mir::udev::Monitor::EventType type, mir::udev::Device const& device) + { + mir::log_debug("Processing UDEV event for %s: %i", device.syspath(), type); + dirty_configuration = true; + conf_change_handler(); + }); + })); +} + +void mga::Display::pause() +{ +} + +void mga::Display::resume() +{ + { + std::lock_guard lg{configuration_mutex}; + + /* + * After resuming (e.g. because we switched back to the display server VT) + * we need to reset the CRTCs. For active displays we schedule a CRTC reset + * on the next swap. For connected but unused outputs we clear the CRTC. + */ + for (auto& db_ptr : display_sinks) + db_ptr->schedule_set_crtc(); + + clear_connected_unused_outputs(); + } +} + +auto mga::Display::create_hardware_cursor() -> std::shared_ptr +{ + return {}; +} + +void mga::Display::clear_connected_unused_outputs() +{ + current_display_configuration.for_each_output([&](DisplayConfigurationOutput const& conf_output) + { + /* + * An output may be unused either because it's explicitly not used + * (DisplayConfigurationOutput::used) or because its power mode is + * not mir_power_mode_on. + */ + if (conf_output.connected && + (!conf_output.used || (conf_output.power_mode != mir_power_mode_on))) + { + auto kms_output = current_display_configuration.get_output_for(conf_output.id); + + kms_output->clear_crtc(); + kms_output->set_power_mode(conf_output.power_mode); + kms_output->set_gamma(conf_output.gamma); + } + }); +} + +bool mga::Display::apply_if_configuration_preserves_display_buffers( + mg::DisplayConfiguration const& conf) +{ + bool result = false; + auto const& new_kms_conf = dynamic_cast(conf); + + { + std::lock_guard lock{configuration_mutex}; + if (compatible(current_display_configuration, new_kms_conf)) + { + configure_locked(new_kms_conf, lock); + result = true; + } + } + + return result; +} + +void mga::Display::configure_locked( + mga::RealKMSDisplayConfiguration const& kms_conf, + std::lock_guard const&) +{ + // Treat the current_display_configuration as incompatible with itself, + // before it's fully constructed, to force proper initialization. + bool const comp{ + (&kms_conf != ¤t_display_configuration) && + compatible(kms_conf, current_display_configuration)}; + std::vector> display_buffers_new; + + if (!comp) + { + /* + * Notice for a little while here we will have duplicate + * DisplayBuffers attached to each output, and the display_buffers_new + * will take over the outputs before the old display_sinks are + * destroyed. So to avoid page flipping confusion in-between, make + * sure we wait for all pending page flips to finish before the + * display_buffers_new are created and take control of the outputs. + */ + for (auto& db : display_sinks) + db->wait_for_page_flip(); + + /* Reset the state of all outputs */ + kms_conf.for_each_output( + [&](DisplayConfigurationOutput const& conf_output) + { + auto kms_output = current_display_configuration.get_output_for(conf_output.id); + kms_output->clear_cursor(); + kms_output->reset(); + }); + } + + /* Set up used outputs */ + OverlappingOutputGrouping grouping{kms_conf}; + auto group_idx = 0; + + grouping.for_each_group( + [&](OverlappingOutputGroup const& group) + { + auto bounding_rect = group.bounding_rectangle(); + std::vector> kms_outputs; + glm::mat2 transformation; + geom::Size current_mode_resolution; + + group.for_each_output( + [&](DisplayConfigurationOutput const& conf_output) + { + auto kms_output = current_display_configuration.get_output_for(conf_output.id); + + auto const mode_index = kms_conf.get_kms_mode_index(conf_output.id, + conf_output.current_mode_index); + kms_output->configure(conf_output.top_left - bounding_rect.top_left, mode_index); + if (!comp) + { + kms_output->set_power_mode(conf_output.power_mode); + kms_output->set_gamma(conf_output.gamma); + kms_outputs.push_back(std::move(kms_output)); + } + + /* + * Presently OverlappingOutputGroup guarantees all grouped + * outputs have the same transformation. + */ + transformation = conf_output.transformation(); + if (conf_output.current_mode_index < conf_output.modes.size()) + current_mode_resolution = conf_output.modes[conf_output.current_mode_index].size; + }); + + if (comp) + { + display_sinks[group_idx++]->set_transformation(transformation, + bounding_rect); + } + else + { + auto db = std::make_unique( + drm_fd, + gbm, + event_handler, + bypass_option, + listener, + kms_outputs, + bounding_rect, + transformation); + + display_buffers_new.push_back(std::move(db)); + } + }); + + if (!comp) + display_sinks = std::move(display_buffers_new); + + /* Store applied configuration */ + current_display_configuration = kms_conf; + + if (!comp) + /* Clear connected but unused outputs */ + clear_connected_unused_outputs(); +} + +namespace +{ +auto gbm_create_device_checked(mir::Fd fd) -> std::shared_ptr +{ + errno = 0; + auto device = gbm_create_device(fd); + if (!device) + { + BOOST_THROW_EXCEPTION(( + std::system_error{ + errno, + std::system_category(), + "Failed to create GBM device"})); + } + return { + device, + [fd](struct gbm_device* device) // Capture shared ownership of fd to keep gdm_device functional + { + if (device) + { + gbm_device_destroy(device); + } + } + }; +} +} + +mga::GBMDisplayProvider::GBMDisplayProvider( + mir::Fd drm_fd) + : fd{std::move(drm_fd)}, + gbm{gbm_create_device_checked(fd)} +{ +} + +auto mga::GBMDisplayProvider::on_this_sink(mg::DisplaySink& sink) const -> bool +{ + if (auto gbm_display_sink = dynamic_cast(&sink)) + { + return gbm_display_sink->gbm_device() == gbm; + } + return false; +} + +auto mga::GBMDisplayProvider::gbm_device() const -> std::shared_ptr +{ + return gbm; +} + +auto mga::GBMDisplayProvider::is_same_device(mir::udev::Device const& render_device) const -> bool +{ +#ifndef MIR_DRM_HAS_GET_DEVICE_FROM_DEVID + class CStrFree + { + public: + void operator()(char* str) + { + if (str) + { + free(str); + } + } + }; + + std::unique_ptr primary_node{drmGetPrimaryDeviceNameFromFd(fd)}; + std::unique_ptr render_node{drmGetRenderDeviceNameFromFd(fd)}; + + if (primary_node) + { + if (strcmp(primary_node.get(), render_device.devnode()) == 0) + { + return true; + } + } + if (render_node) + { + if (strcmp(render_node.get(), render_device.devnode()) == 0) + { + return true; + } + } + + return false; +#else + drmDevicePtr us{nullptr}, them{nullptr}; + + drmGetDeviceFromDevId(render_device.devno(), 0, &them); + drmGetDevice2(fd, 0, &us); + + bool result = drmDevicesEqual(us, them); + + drmDeviceFree(us); + drmDeviceFree(them); + + return result; +#endif +} diff --git a/src/platforms/atomic-kms/server/kms/display.h b/src/platforms/atomic-kms/server/kms/display.h new file mode 100644 index 00000000000..ba0961a0606 --- /dev/null +++ b/src/platforms/atomic-kms/server/kms/display.h @@ -0,0 +1,131 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_GRAPHICS_GBM_DISPLAY_H_ +#define MIR_GRAPHICS_GBM_DISPLAY_H_ + +#include "kms-utils/drm_event_handler.h" +#include "mir/graphics/display.h" +#include "mir/udev/wrapper.h" +#include "real_kms_output_container.h" +#include "real_kms_display_configuration.h" +#include "platform_common.h" +#include "mir/graphics/platform.h" + +#include +#include +#include + +namespace mir +{ +namespace graphics +{ + +class DisplayInterfaceProvider; +class DisplayReport; +class DisplayConfigurationPolicy; +class EventHandlerRegister; +class GLConfig; + +namespace kms +{ +class DRMEventHandler; +} + +namespace atomic +{ + +namespace helpers +{ +class DRMHelper; +class GBMHelper; +} + +class DisplaySink; +class KMSOutput; +class Cursor; + +class Display : public graphics::Display +{ +public: + Display( + mir::Fd drm_fd, + std::shared_ptr gbm, + BypassOption bypass_option, + std::shared_ptr const& initial_conf_policy, + std::shared_ptr const& listener); + ~Display(); + + geometry::Rectangle view_area() const; + void for_each_display_sync_group( + std::function const& f) override; + + std::unique_ptr configuration() const override; + bool apply_if_configuration_preserves_display_buffers(DisplayConfiguration const& conf) override; + void configure(DisplayConfiguration const& conf) override; + + void register_configuration_change_handler( + EventHandlerRegister& handlers, + DisplayConfigurationChangeHandler const& conf_change_handler) override; + + void pause() override; + void resume() override; + + std::shared_ptr create_hardware_cursor() override; + +private: + void clear_connected_unused_outputs(); + + mutable std::mutex configuration_mutex; + mir::Fd const drm_fd; + std::shared_ptr const event_handler; + std::shared_ptr const gbm; + std::shared_ptr const listener; + mir::udev::Monitor monitor; + std::shared_ptr const output_container; + std::vector> display_sinks; + mutable RealKMSDisplayConfiguration current_display_configuration; + mutable std::atomic dirty_configuration; + + void configure_locked( + RealKMSDisplayConfiguration const& conf, + std::lock_guard const&); + + BypassOption bypass_option; + std::weak_ptr cursor; +}; + +class GBMDisplayProvider : public graphics::GBMDisplayProvider +{ +public: + explicit GBMDisplayProvider(mir::Fd drm_fd); + + auto is_same_device(mir::udev::Device const& render_device) const -> bool override; + + auto on_this_sink(graphics::DisplaySink& sink) const -> bool override; + + auto gbm_device() const -> std::shared_ptr override; + +private: + mir::Fd const fd; + std::shared_ptr const gbm; +}; + +} +} +} + +#endif /* MIR_GRAPHICS_GBM_DISPLAY_H_ */ diff --git a/src/platforms/atomic-kms/server/kms/display_buffer.cpp b/src/platforms/atomic-kms/server/kms/display_buffer.cpp new file mode 100644 index 00000000000..4c5c17777df --- /dev/null +++ b/src/platforms/atomic-kms/server/kms/display_buffer.cpp @@ -0,0 +1,358 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "display_sink.h" +#include "kms_cpu_addressable_display_provider.h" +#include "kms_output.h" +#include "cpu_addressable_fb.h" +#include "gbm_display_allocator.h" +#include "mir/fd.h" +#include "mir/graphics/display_report.h" +#include "mir/graphics/platform.h" +#include "mir/graphics/transformation.h" +#include "bypass.h" +#include "mir/fatal.h" +#include "mir/log.h" +#include "display_helpers.h" +#include "egl_helper.h" +#include "mir/graphics/egl_error.h" +#include "mir/graphics/gl_config.h" +#include "mir/graphics/dmabuf_buffer.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace mg = mir::graphics; +namespace mga = mir::graphics::atomic; +namespace geom = mir::geometry; +namespace mgk = mir::graphics::kms; + +mga::DisplaySink::DisplaySink( + mir::Fd drm_fd, + std::shared_ptr gbm, + std::shared_ptr event_handler, + mga::BypassOption, + std::shared_ptr const& listener, + std::vector> const& outputs, + geom::Rectangle const& area, + glm::mat2 const& transformation) + : gbm{std::move(gbm)}, + listener(listener), + outputs(outputs), + event_handler{std::move(event_handler)}, + area(area), + transform{transformation}, + needs_set_crtc{false} +{ + listener->report_successful_setup_of_native_resources(); + + // If any of the outputs have a CRTC mismatch, we will want to set all of them + // so that they're all showing the same buffer. + bool has_crtc_mismatch = false; + for (auto& output : outputs) + { + has_crtc_mismatch = output->has_crtc_mismatch(); + if (has_crtc_mismatch) + break; + } + + if (has_crtc_mismatch) + { + mir::log_info("Clearing screen due to differing encountered and target modes"); + // TODO: Pull a supported format out of KMS rather than assuming XRGB8888 + auto initial_fb = std::make_shared( + std::move(drm_fd), + false, + DRMFormat{DRM_FORMAT_XRGB8888}, + area.size); + + auto mapping = initial_fb->map_writeable(); + ::memset(mapping->data(), 24, mapping->len()); + + visible_fb = std::move(initial_fb); + for (auto &output: outputs) { + output->set_crtc(*visible_fb); + } + listener->report_successful_drm_mode_set_crtc_on_construction(); + } + listener->report_successful_display_construction(); +} + +mga::DisplaySink::~DisplaySink() = default; + +geom::Rectangle mga::DisplaySink::view_area() const +{ + return area; +} + +glm::mat2 mga::DisplaySink::transformation() const +{ + return transform; +} + +void mga::DisplaySink::set_transformation(glm::mat2 const& t, geometry::Rectangle const& a) +{ + transform = t; + area = a; +} + +bool mga::DisplaySink::overlay(std::vector const& renderable_list) +{ + // TODO: implement more than the most basic case. + if (renderable_list.size() != 1) + { + return false; + } + + if (renderable_list[0].screen_positon != view_area()) + { + return false; + } + + if (renderable_list[0].source_position.top_left != geom::PointF {0,0} || + renderable_list[0].source_position.size.width.as_value() != view_area().size.width.as_int() || + renderable_list[0].source_position.size.height.as_value() != view_area().size.height.as_int()) + { + return false; + } + + if (auto fb = std::dynamic_pointer_cast(renderable_list[0].buffer)) + { + next_swap = std::move(fb); + return true; + } + return false; +} + +void mga::DisplaySink::for_each_display_sink(std::function const& f) +{ + f(*this); +} + +void mga::DisplaySink::set_crtc(FBHandle const& forced_frame) +{ + for (auto& output : outputs) + { + /* + * Note that failure to set the CRTC is not a fatal error. This can + * happen under normal conditions when resizing VirtualBox (which + * actually removes and replaces the virtual output each time so + * sometimes it's really not there). Xorg often reports similar + * errors, and it's not fatal. + */ + if (!output->set_crtc(forced_frame)) + mir::log_error("Failed to set DRM CRTC. " + "Screen contents may be incomplete. " + "Try plugging the monitor in again."); + } +} + +void mga::DisplaySink::post() +{ + /* + * We might not have waited for the previous frame to page flip yet. + * This is good because it maximizes the time available to spend rendering + * each frame. Just remember wait_for_page_flip() must be called at some + * point before the next schedule_page_flip(). + */ + wait_for_page_flip(); + + if (!next_swap) + { + // Hey! No one has given us a next frame yet, so we don't have to change what's onscreen. + // Sweet! We can just bail. + return; + } + /* + * Otherwise, pull the next frame into the pending slot + */ + scheduled_fb = std::move(next_swap); + next_swap = nullptr; + + /* + * Try to schedule a page flip as first preference to avoid tearing. + * [will complete in a background thread] + */ + if (!needs_set_crtc && !schedule_page_flip(*scheduled_fb)) + needs_set_crtc = true; + + /* + * Fallback blitting: Not pretty, since it may tear. VirtualBox seems + * to need to do this on every frame. [will complete in this thread] + */ + if (needs_set_crtc) + { + set_crtc(*scheduled_fb); + // SetCrtc is immediate, so the FB is now visible and we have nothing pending + visible_fb = std::move(scheduled_fb); + scheduled_fb = nullptr; + + needs_set_crtc = false; + } + + using namespace std::chrono_literals; // For operator""ms() + + // Predicted worst case render time for the next frame... + auto predicted_render_time = 50ms; + + if (holding_client_buffers) + { + /* + * For composited frames we defer wait_for_page_flip till just before + * the next frame, but not for bypass frames. Deferring the flip of + * bypass frames would increase the time we held + * visible_bypass_frame unacceptably, resulting in client stuttering + * unless we allocate more buffers (which I'm trying to avoid). + * Also, bypass does not need the deferred page flip because it has + * no compositing/rendering step for which to save time for. + */ + wait_for_page_flip(); + + // It's very likely the next frame will be bypassed like this one so + // we only need time for kernel page flip scheduling... + predicted_render_time = 5ms; + } + else + { + /* + * Not in clone mode? We can afford to wait for the page flip then, + * making us double-buffered (noticeably less laggy than the triple + * buffering that clone mode requires). + */ + if (outputs.size() == 1) + wait_for_page_flip(); + + /* + * TODO: If you're optimistic about your GPU performance and/or + * measure it carefully you may wish to set predicted_render_time + * to a lower value here for lower latency. + * + *predicted_render_time = 9ms; // e.g. about the same as Weston + */ + } + + recommend_sleep = 0ms; + if (outputs.size() == 1) + { + auto const& output = outputs.front(); + auto const min_frame_interval = 1000ms / output->max_refresh_rate(); + if (predicted_render_time < min_frame_interval) + recommend_sleep = min_frame_interval - predicted_render_time; + } +} + +std::chrono::milliseconds mga::DisplaySink::recommended_sleep() const +{ + return recommend_sleep; +} + +bool mga::DisplaySink::schedule_page_flip(FBHandle const& bufobj) +{ + /* + * Schedule the current front buffer object for display. Note that + * the page flip is asynchronous and synchronized with vertical refresh. + */ + /* TODO: This works badly if *some* outputs successfully flipped and + * others did not. We should instead have exactly one KMSOutput per DisplaySink + */ + for (auto& output : outputs) + { + if (output->schedule_page_flip(bufobj)) + { + pending_flips.push_back(output.get()); + } + } + + return !pending_flips.empty(); +} + +void mga::DisplaySink::wait_for_page_flip() +{ + if (!pending_flips.empty()) + { + for (auto pending_flip : pending_flips) + { + pending_flip->wait_for_page_flip(); + } + pending_flips.clear(); + + // The previously-scheduled FB has been page-flipped, and is now visible + visible_fb = std::move(scheduled_fb); + scheduled_fb = nullptr; + } +} + +void mga::DisplaySink::schedule_set_crtc() +{ + needs_set_crtc = true; +} + +auto mga::DisplaySink::drm_fd() const -> mir::Fd +{ + return mir::Fd{mir::IntOwnedFd{outputs.front()->drm_fd()}}; +} + +auto mga::DisplaySink::gbm_device() const -> std::shared_ptr +{ + return gbm; +} + +void mga::DisplaySink::set_next_image(std::unique_ptr content) +{ + std::vector const single_buffer = { + DisplayElement { + view_area(), + geom::RectangleF{ + {0, 0}, + {view_area().size.width.as_value(), view_area().size.height.as_value()}}, + std::move(content) + } + }; + if (!overlay(single_buffer)) + { + // Oh, oh! We should be *guaranteed* to “overlay” a single Framebuffer; this is likely a programming error + BOOST_THROW_EXCEPTION((std::runtime_error{"Failed to post buffer to display"})); + } +} + +auto mga::DisplaySink::maybe_create_allocator(DisplayAllocator::Tag const& type_tag) + -> DisplayAllocator* +{ + if (dynamic_cast(&type_tag)) + { + if (!kms_allocator) + { + kms_allocator = kms::CPUAddressableDisplayAllocator::create_if_supported(drm_fd(), outputs.front()->size()); + } + return kms_allocator.get(); + } + if (dynamic_cast(&type_tag)) + { + if (!gbm_allocator) + { + gbm_allocator = std::make_unique(drm_fd(), gbm, outputs.front()->size()); + } + return gbm_allocator.get(); + } + return nullptr; +} diff --git a/src/platforms/atomic-kms/server/kms/display_sink.h b/src/platforms/atomic-kms/server/kms/display_sink.h new file mode 100644 index 00000000000..7257ce5263b --- /dev/null +++ b/src/platforms/atomic-kms/server/kms/display_sink.h @@ -0,0 +1,126 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_GRAPHICS_ATOMIC_DISPLAY_SINK_H_ +#define MIR_GRAPHICS_ATOMIC_DISPLAY_SINK_H_ + +#include "kms-utils/drm_event_handler.h" +#include "mir/graphics/display_sink.h" +#include "mir/graphics/display.h" +#include "display_helpers.h" +#include "egl_helper.h" +#include "mir/graphics/platform.h" +#include "platform_common.h" +#include "kms_framebuffer.h" + +#include +#include +#include +#include + +namespace mir +{ +namespace graphics +{ + +class DisplayReport; +class GLConfig; + +namespace kms +{ +class DRMEventHandler; +} + +namespace atomic +{ + +class Platform; +class KMSOutput; + +class DisplaySink : public graphics::DisplaySink, + public graphics::DisplaySyncGroup +{ +public: + DisplaySink( + mir::Fd drm_fd, + std::shared_ptr gbm, + std::shared_ptr event_handler, + BypassOption bypass_options, + std::shared_ptr const& listener, + std::vector> const& outputs, + geometry::Rectangle const& area, + glm::mat2 const& transformation); + ~DisplaySink(); + + geometry::Rectangle view_area() const override; + + void set_next_image(std::unique_ptr content) override; + + bool overlay(std::vector const& renderlist) override; + + void for_each_display_sink( + std::function const& f) override; + void post() override; + std::chrono::milliseconds recommended_sleep() const override; + + glm::mat2 transformation() const override; + + void set_transformation(glm::mat2 const& t, geometry::Rectangle const& a); + void schedule_set_crtc(); + void wait_for_page_flip(); + + auto drm_fd() const -> mir::Fd; + + auto gbm_device() const -> std::shared_ptr; + +protected: + auto maybe_create_allocator(DisplayAllocator::Tag const& type_tag) -> DisplayAllocator* override; + +private: + bool schedule_page_flip(FBHandle const& bufobj); + void set_crtc(FBHandle const&); + + std::shared_ptr const gbm; + bool holding_client_buffers{false}; + std::shared_ptr bypass_bufobj{nullptr}; + std::shared_ptr const listener; + + std::vector> const outputs; + std::vector pending_flips; + + std::shared_ptr const event_handler; + + std::shared_ptr kms_allocator; + std::unique_ptr gbm_allocator; + + // Framebuffer handling + // KMS does not take a reference to submitted framebuffers; if you destroy a framebuffer while + // it's in use, KMS treat that as submitting a null framebuffer and turn off the display. + std::shared_ptr next_swap{nullptr}; //< Next frame to submit to the hardware + std::shared_ptr scheduled_fb{nullptr}; //< Frame currently submitted to the hardware, not yet on-screen + std::shared_ptr visible_fb{nullptr}; //< Frame currently onscreen + + geometry::Rectangle area; + glm::mat2 transform; + std::atomic needs_set_crtc; + std::chrono::milliseconds recommend_sleep{0}; +}; + +} +} +} + +#endif /* MIR_GRAPHICS_ATOMIC_DISPLAY_SINK_H_ */ diff --git a/src/platforms/atomic-kms/server/kms/egl_helper.cpp b/src/platforms/atomic-kms/server/kms/egl_helper.cpp new file mode 100644 index 00000000000..b8442eae022 --- /dev/null +++ b/src/platforms/atomic-kms/server/kms/egl_helper.cpp @@ -0,0 +1,286 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "egl_helper.h" +#include "mir/graphics/gl_config.h" +#include "mir/graphics/egl_error.h" +#include +#include +#include + +#define MIR_LOG_COMPONENT "EGL" +#include "mir/log.h" + +namespace mg = mir::graphics; +namespace mgmh = mir::graphics::atomic::helpers; + +mgmh::EGLHelper::EGLHelper(GLConfig const& gl_config) + : depth_buffer_bits{gl_config.depth_buffer_bits()}, + stencil_buffer_bits{gl_config.stencil_buffer_bits()}, + egl_display{EGL_NO_DISPLAY}, egl_config{0}, + egl_context{EGL_NO_CONTEXT}, egl_surface{EGL_NO_SURFACE}, + should_terminate_egl{false} +{ +} + +mgmh::EGLHelper::EGLHelper( + GLConfig const& gl_config, + GBMHelper const& gbm, + gbm_surface* surface, + uint32_t gbm_format, + EGLContext shared_context) + : EGLHelper(gl_config) +{ + setup(gbm.device, surface, gbm_format, shared_context, false); +} + +mgmh::EGLHelper::EGLHelper(EGLHelper&& from) + : depth_buffer_bits{from.depth_buffer_bits}, + stencil_buffer_bits{from.stencil_buffer_bits}, + egl_display{from.egl_display}, + egl_config{from.egl_config}, + egl_context{from.egl_context}, + egl_surface{from.egl_surface}, + should_terminate_egl{from.should_terminate_egl} +{ + from.should_terminate_egl = false; + from.egl_display = EGL_NO_DISPLAY; + from.egl_context = EGL_NO_CONTEXT; + from.egl_surface = EGL_NO_SURFACE; +} + +namespace +{ +void initialise_egl(EGLDisplay dpy, int minimum_major_version, int minimum_minor_version) +{ + EGLint major, minor; + + if (eglInitialize(dpy, &major, &minor) == EGL_FALSE) + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to initialize EGL display")); + + if ((major < minimum_major_version) || + (major == minimum_major_version && minor < minimum_minor_version)) + { + BOOST_THROW_EXCEPTION( + boost::enable_error_info(std::runtime_error("Incompatible EGL version"))); + // TODO: Insert egl version major and minor into exception + } +} + +std::vector get_matching_configs(EGLDisplay dpy, EGLint const attr[]) +{ + EGLint num_egl_configs; + + // First query the number of matching configs… + if ((eglChooseConfig(dpy, attr, nullptr, 0, &num_egl_configs) == EGL_FALSE) || + (num_egl_configs == 0)) + { + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to enumerate any matching EGL configs")); + } + + std::vector matching_configs(static_cast(num_egl_configs)); + if ((eglChooseConfig(dpy, attr, matching_configs.data(), static_cast(matching_configs.size()), &num_egl_configs) == EGL_FALSE) || + (num_egl_configs == 0)) + { + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to acquire matching EGL configs")); + } + + matching_configs.resize(static_cast(num_egl_configs)); + return matching_configs; +} + +} + +void mgmh::EGLHelper::setup(GBMHelper const& gbm) +{ + eglBindAPI(EGL_OPENGL_ES_API); + + static const EGLint context_attr[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + egl_display = egl_display_for_gbm_device(gbm.device); + initialise_egl(egl_display, 1, 4); + should_terminate_egl = true; + + // This is a context solely used for sharing GL object IDs; we will not do any rendering + // with it, so we do not care what EGLconfig is used *at all*. + EGLint const no_attribs[] = {EGL_NONE}; + egl_config = get_matching_configs(egl_display, no_attribs)[0]; + + egl_context = eglCreateContext(egl_display, egl_config, EGL_NO_CONTEXT, context_attr); + if (egl_context == EGL_NO_CONTEXT) + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to create EGL context")); +} + +void mgmh::EGLHelper::setup(GBMHelper const& gbm, EGLContext shared_context) +{ + eglBindAPI(EGL_OPENGL_ES_API); + + static const EGLint context_attr[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + egl_display = egl_display_for_gbm_device(gbm.device); + + // Might as well copy the EGLConfig from shared_context + EGLint config_id; + if (eglQueryContext(egl_display, shared_context, EGL_CONFIG_ID, &config_id) != EGL_TRUE) + { + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to query EGLConfig of shared EGLContext")); + } + EGLint const context_attribs[] = { + EGL_CONFIG_ID, config_id, + EGL_NONE + }; + egl_config = get_matching_configs(egl_display, context_attribs)[0]; + + egl_context = eglCreateContext(egl_display, egl_config, shared_context, context_attr); + if (egl_context == EGL_NO_CONTEXT) + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to create EGL context")); +} + +void mgmh::EGLHelper::setup( + gbm_device* const device, + gbm_surface* surface_gbm, + uint32_t gbm_format, + EGLContext shared_context, + bool owns_egl) +{ + eglBindAPI(EGL_OPENGL_ES_API); + + static const EGLint context_attr[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + egl_display = egl_display_for_gbm_device(device); + if (owns_egl) + { + initialise_egl(egl_display, 1, 4); + should_terminate_egl = owns_egl; + } + + egl_config = egl_config_for_format(static_cast(gbm_format)); + + egl_surface = platform_base.eglCreatePlatformWindowSurface( + egl_display, + egl_config, + surface_gbm, + nullptr); + if(egl_surface == EGL_NO_SURFACE) + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to create EGL window surface")); + + egl_context = eglCreateContext(egl_display, egl_config, shared_context, context_attr); + if (egl_context == EGL_NO_CONTEXT) + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to create EGL context")); +} + +mgmh::EGLHelper::~EGLHelper() noexcept +{ + if (egl_display != EGL_NO_DISPLAY) { + if (egl_context != EGL_NO_CONTEXT) + { + eglBindAPI(EGL_OPENGL_ES_API); + if (eglGetCurrentContext() == egl_context) + eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroyContext(egl_display, egl_context); + } + if (egl_surface != EGL_NO_SURFACE) + eglDestroySurface(egl_display, egl_surface); + if (should_terminate_egl) + eglTerminate(egl_display); + } +} + +bool mgmh::EGLHelper::swap_buffers() +{ + auto ret = eglSwapBuffers(egl_display, egl_surface); + return (ret == EGL_TRUE); +} + +bool mgmh::EGLHelper::make_current() const +{ + auto ret = eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context); + eglBindAPI(EGL_OPENGL_ES_API); + return (ret == EGL_TRUE); +} + +bool mgmh::EGLHelper::release_current() const +{ + auto ret = eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + return (ret == EGL_TRUE); +} + +auto mgmh::EGLHelper::egl_display_for_gbm_device(struct gbm_device* const device) -> EGLDisplay +{ + auto const egl_display = platform_base.eglGetPlatformDisplay( + EGL_PLATFORM_GBM_KHR, // EGL_PLATFORM_GBM_MESA has the same value. + static_cast(device), + nullptr); + if (egl_display == EGL_NO_DISPLAY) + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to get EGL display")); + + return egl_display; +} + +auto mgmh::EGLHelper::egl_config_for_format(EGLint gbm_format) -> EGLConfig +{ + // TODO: Get the required EGL_{RED,GREEN,BLUE}_SIZE values out of gbm_format + EGLint const config_attr[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 0, + EGL_DEPTH_SIZE, depth_buffer_bits, + EGL_STENCIL_SIZE, stencil_buffer_bits, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + + for (auto const& config : get_matching_configs(egl_display, config_attr)) + { + EGLint id; + if (eglGetConfigAttrib(egl_display, config, EGL_NATIVE_VISUAL_ID, &id) == EGL_FALSE) + { + mir::log_warning( + "Failed to query GBM format of EGLConfig: %s", + mg::egl_category().message(eglGetError()).c_str()); + continue; + } + + if (id == gbm_format) + { + // We've found our matching format, so we're done here. + return config; + } + } + BOOST_THROW_EXCEPTION((NoMatchingEGLConfig{static_cast(gbm_format)})); +} + +void mgmh::EGLHelper::report_egl_configuration(std::function f) +{ + f(egl_display, egl_config); +} + +mgmh::EGLHelper::NoMatchingEGLConfig::NoMatchingEGLConfig(uint32_t /*format*/) + : std::runtime_error("Failed to find matching EGL config") +{ + // TODO: Include the format string; need to extract from linux_dmabuf.cpp +} diff --git a/src/platforms/atomic-kms/server/kms/egl_helper.h b/src/platforms/atomic-kms/server/kms/egl_helper.h new file mode 100644 index 00000000000..ea4468fbd80 --- /dev/null +++ b/src/platforms/atomic-kms/server/kms/egl_helper.h @@ -0,0 +1,87 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_GRAPHICS_GBM_EGL_HELPER_H_ +#define MIR_GRAPHICS_GBM_EGL_HELPER_H_ + +#include "../display_helpers.h" +#include "mir/graphics/egl_extensions.h" +#include +#include + +namespace mir +{ +namespace graphics +{ +class GLConfig; +namespace atomic +{ +namespace helpers +{ +class EGLHelper +{ +public: + EGLHelper(GLConfig const& gl_config); + EGLHelper( + GLConfig const& gl_config, + GBMHelper const& gbm, + gbm_surface* surface, + uint32_t gbm_format, + EGLContext shared_context); + ~EGLHelper() noexcept; + EGLHelper(EGLHelper&& from); + + EGLHelper(const EGLHelper&) = delete; + EGLHelper& operator=(const EGLHelper&) = delete; + + void setup(GBMHelper const& gbm); + void setup(GBMHelper const& gbm, EGLContext shared_context); + void setup(gbm_device* const device, gbm_surface* surface_gbm, uint32_t gbm_format, EGLContext shared_context, bool owns_egl); + + bool swap_buffers(); + bool make_current() const; + bool release_current() const; + + EGLContext context() const { return egl_context; } + auto display() const -> EGLDisplay { return egl_display; } + + void report_egl_configuration(std::function); + + class NoMatchingEGLConfig : public std::runtime_error + { + public: + NoMatchingEGLConfig(uint32_t format); + }; +private: + auto egl_config_for_format(EGLint gbm_format) -> EGLConfig; + + auto egl_display_for_gbm_device(struct gbm_device* const device) -> EGLDisplay; + + EGLint const depth_buffer_bits; + EGLint const stencil_buffer_bits; + EGLDisplay egl_display; + EGLConfig egl_config; + EGLContext egl_context; + EGLSurface egl_surface; + bool should_terminate_egl; + EGLExtensions::PlatformBaseEXT platform_base; +}; +} +} +} +} + +#endif /* MIR_GRAPHICS_GBM_EGL_HELPER_H_ */ diff --git a/src/platforms/atomic-kms/server/kms/kms_display_configuration.h b/src/platforms/atomic-kms/server/kms/kms_display_configuration.h new file mode 100644 index 00000000000..03ead845579 --- /dev/null +++ b/src/platforms/atomic-kms/server/kms/kms_display_configuration.h @@ -0,0 +1,47 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_GRAPHICS_GBM_KMS_DISPLAY_CONFIGURATION_H_ +#define MIR_GRAPHICS_GBM_KMS_DISPLAY_CONFIGURATION_H_ + +#include "mir/graphics/display_configuration.h" +#include + +namespace mir +{ +namespace graphics +{ +namespace atomic +{ +class KMSOutput; + +class DRMModeResources; + +class KMSDisplayConfiguration : public DisplayConfiguration +{ +public: + virtual std::shared_ptr get_output_for(DisplayConfigurationOutputId id) const = 0; + virtual size_t get_kms_mode_index( + DisplayConfigurationOutputId id, + size_t conf_mode_index) const = 0; + virtual void update() = 0; +}; + +} +} +} + +#endif /* MIR_GRAPHICS_GBM_KMS_DISPLAY_CONFIGURATION_H_ */ diff --git a/src/platforms/atomic-kms/server/kms/kms_output.h b/src/platforms/atomic-kms/server/kms/kms_output.h new file mode 100644 index 00000000000..6772556d240 --- /dev/null +++ b/src/platforms/atomic-kms/server/kms/kms_output.h @@ -0,0 +1,109 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_GRAPHICS_GBM_KMS_OUTPUT_H_ +#define MIR_GRAPHICS_GBM_KMS_OUTPUT_H_ + +#include "mir/geometry/size.h" +#include "mir/geometry/point.h" +#include "mir/geometry/displacement.h" +#include "mir/graphics/display_configuration.h" +#include "mir/graphics/frame.h" +#include "mir/graphics/dmabuf_buffer.h" +#include "mir_toolkit/common.h" +#include "kms-utils/drm_mode_resources.h" + +#include + +namespace mir +{ +namespace graphics +{ +class DisplayConfigurationOutput; +class FBHandle; + +namespace atomic +{ + +class KMSOutput +{ +public: + virtual ~KMSOutput() = default; + + /* + * I'm not sure that DRM guarantees ID uniqueness in the presence of hotplug/unplug; + * this may want to be an opaque class Id + operator== in future. + */ + virtual uint32_t id() const = 0; + + virtual void reset() = 0; + virtual void configure(geometry::Displacement fb_offset, size_t kms_mode_index) = 0; + virtual geometry::Size size() const = 0; + + /** + * Approximate maximum refresh rate of this output to within 1Hz. + * Typically the rate is fixed (e.g. 60Hz) but it may also be variable as + * in Nvidia G-Sync/AMD FreeSync/VESA Adaptive Sync. So this function + * returns the maximum rate to expect. + */ + virtual int max_refresh_rate() const = 0; + + virtual bool set_crtc(FBHandle const& fb) = 0; + + /** + * Check if the pending call to set_crtc is compatible with the current state of the CRTC. + * @returns true if a set_crtc is required, otherwise false + */ + virtual bool has_crtc_mismatch() = 0; + virtual void clear_crtc() = 0; + + virtual bool schedule_page_flip(FBHandle const& fb) = 0; + virtual void wait_for_page_flip() = 0; + + virtual bool set_cursor(gbm_bo* buffer) = 0; + virtual void move_cursor(geometry::Point destination) = 0; + virtual bool clear_cursor() = 0; + virtual bool has_cursor() const = 0; + + virtual void set_power_mode(MirPowerMode mode) = 0; + virtual void set_gamma(GammaCurves const& gamma) = 0; + + /** + * Re-probe the hardware state of this connector. + * + * \throws std::system_error if the underlying DRM connector has disappeared. + */ + virtual void refresh_hardware_state() = 0; + /** + * Translate and copy the cached hardware state into a Mir display configuration object. + * + * \param [out] to_update The Mir display configuration object to update with new + * hardware state. Only hardware state (modes, dimensions, etc) + * is touched. + */ + virtual void update_from_hardware_state(DisplayConfigurationOutput& to_update) const = 0; + + virtual int drm_fd() const = 0; +protected: + KMSOutput() = default; + KMSOutput(const KMSOutput&) = delete; + KMSOutput& operator=(const KMSOutput&) = delete; +}; +} +} +} + +#endif /* MIR_GRAPHICS_GBM_KMS_OUTPUT_H_ */ diff --git a/src/platforms/atomic-kms/server/kms/kms_output_container.h b/src/platforms/atomic-kms/server/kms/kms_output_container.h new file mode 100644 index 00000000000..2d951cb56ab --- /dev/null +++ b/src/platforms/atomic-kms/server/kms/kms_output_container.h @@ -0,0 +1,53 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_GRAPHICS_GBM_KMS_OUTPUT_CONTAINER_H_ +#define MIR_GRAPHICS_GBM_KMS_OUTPUT_CONTAINER_H_ + +#include +#include + +namespace mir +{ +namespace graphics +{ +namespace atomic +{ + +class KMSOutput; + +class KMSOutputContainer +{ +public: + virtual ~KMSOutputContainer() = default; + + virtual void for_each_output(std::function const&)> functor) const = 0; + + /** + * Re-probe hardware state and update output list. + */ + virtual void update_from_hardware_state() = 0; +protected: + KMSOutputContainer() = default; + KMSOutputContainer(KMSOutputContainer const&) = delete; + KMSOutputContainer& operator=(KMSOutputContainer const&) = delete; +}; + +} +} +} + +#endif /* MIR_GRAPHICS_GBM_KMS_OUTPUT_CONTAINER_H_ */ diff --git a/src/platforms/atomic-kms/server/kms/kms_page_flipper.cpp b/src/platforms/atomic-kms/server/kms/kms_page_flipper.cpp new file mode 100644 index 00000000000..a6720541ee3 --- /dev/null +++ b/src/platforms/atomic-kms/server/kms/kms_page_flipper.cpp @@ -0,0 +1,188 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "kms_page_flipper.h" +#include "mir/graphics/display_report.h" + +#include +#include +#include + +#include +#include +#include +#include + +namespace mg = mir::graphics; +namespace mgg = mir::graphics::atomic; + +namespace +{ + +void page_flip_handler(int /*fd*/, unsigned int seq, + unsigned int sec, unsigned int usec, + void* data) +{ + auto page_flip_data = static_cast(data); + std::chrono::nanoseconds ns{sec*1000000000LL + usec*1000LL}; + page_flip_data->flipper->notify_page_flip(page_flip_data->crtc_id, + seq, ns); +} + +} + +mgg::KMSPageFlipper::KMSPageFlipper( + int drm_fd, + std::shared_ptr const& report) : + drm_fd{drm_fd}, + report{report}, + pending_page_flips(), + worker_tid() +{ + uint64_t mono = 0; + if (drmGetCap(drm_fd, DRM_CAP_TIMESTAMP_MONOTONIC, &mono) || !mono) + clock_id = CLOCK_REALTIME; + else + clock_id = CLOCK_MONOTONIC; +} + +bool mgg::KMSPageFlipper::schedule_flip(uint32_t crtc_id, + uint32_t fb_id, + uint32_t connector_id) +{ + std::unique_lock lock{pf_mutex}; + + if (pending_page_flips.find(crtc_id) != pending_page_flips.end()) + BOOST_THROW_EXCEPTION(std::logic_error("Page flip for crtc_id is already scheduled")); + + pending_page_flips[crtc_id] = PageFlipEventData{crtc_id, connector_id, this}; + + /* + * It appears we can't tell the difference between flipping being + * unsupported or failing for other reasons. On VirtualBox this always + * fails with -22 (Invalid argument) despite the arguments being + * apparently valid. + */ + auto ret = drmModePageFlip(drm_fd, crtc_id, fb_id, + DRM_MODE_PAGE_FLIP_EVENT, + &pending_page_flips[crtc_id]); + + if (ret) + pending_page_flips.erase(crtc_id); + + return (ret == 0); +} + +mg::Frame mgg::KMSPageFlipper::wait_for_flip(uint32_t crtc_id) +{ + drmEventContext evctx; + memset(&evctx, 0, sizeof evctx); + evctx.version = 2; // We only support the old v2 page_flip_handler + evctx.page_flip_handler = &page_flip_handler; + + static std::thread::id const invalid_tid; + + { + std::unique_lock lock{pf_mutex}; + + /* + * While another thread is the worker (it is controlling the + * page flip event loop) and our event has not arrived, wait. + */ + while (worker_tid != invalid_tid && !page_flip_is_done(crtc_id)) + pf_cv.wait(lock); + + /* If the page flip we are waiting for has arrived we are done. */ + if (page_flip_is_done(crtc_id)) + return completed_page_flips[crtc_id]; + + /* ...otherwise we become the worker */ + worker_tid = std::this_thread::get_id(); + } + + /* Only the worker thread reaches this point */ + bool done{false}; + + while (!done) + { + fd_set fds; + FD_ZERO(&fds); + FD_SET(drm_fd, &fds); + + /* + * Wait for a page flip event. When we get a page flip event, + * page_flip_handler(), called through drmHandleEvent(), will update + * the pending_page_flips map. + */ + auto ret = select(drm_fd + 1, &fds, nullptr, nullptr, nullptr); + + { + std::unique_lock lock{pf_mutex}; + + if (ret > 0) + { + drmHandleEvent(drm_fd, &evctx); + } + else if (ret < 0 && errno != EINTR) + { + std::string const msg("Error while waiting for page-flip event"); + BOOST_THROW_EXCEPTION( + boost::enable_error_info( + std::runtime_error(msg)) << boost::errinfo_errno(errno)); + } + + done = page_flip_is_done(crtc_id); + /* Give up loop control if we are done */ + if (done) + worker_tid = invalid_tid; + } + + /* + * Wake up other (non-worker) threads, so they can check whether + * their page-flip events have arrived, or whether they can become + * the worker (see pf_cv.wait(lock) above). + */ + pf_cv.notify_all(); + } + return completed_page_flips[crtc_id]; +} + +std::thread::id mgg::KMSPageFlipper::debug_get_worker_tid() +{ + std::unique_lock lock{pf_mutex}; + + return worker_tid; +} + +/* This method should be called with the 'pf_mutex' locked */ +bool mgg::KMSPageFlipper::page_flip_is_done(uint32_t crtc_id) +{ + return pending_page_flips.find(crtc_id) == pending_page_flips.end(); +} + +void mgg::KMSPageFlipper::notify_page_flip(uint32_t crtc_id, int64_t msc, + std::chrono::nanoseconds ust) +{ + auto pending = pending_page_flips.find(crtc_id); + if (pending != pending_page_flips.end()) + { + auto& frame = completed_page_flips[crtc_id]; + frame.msc = msc; + frame.ust = {clock_id, ust}; + report->report_vsync(pending->second.connector_id, frame); + pending_page_flips.erase(pending); + } +} diff --git a/src/platforms/atomic-kms/server/kms/kms_page_flipper.h b/src/platforms/atomic-kms/server/kms/kms_page_flipper.h new file mode 100644 index 00000000000..4cd287ab6f3 --- /dev/null +++ b/src/platforms/atomic-kms/server/kms/kms_page_flipper.h @@ -0,0 +1,76 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_GRAPHICS_GBM_KMS_PAGE_FLIPPER_H_ +#define MIR_GRAPHICS_GBM_KMS_PAGE_FLIPPER_H_ + +#include "page_flipper.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace mir +{ +namespace graphics +{ + +class DisplayReport; + +namespace atomic +{ + +class KMSPageFlipper; +struct PageFlipEventData +{ + uint32_t crtc_id; + uint32_t connector_id; + KMSPageFlipper* flipper; +}; + +class KMSPageFlipper : public PageFlipper +{ +public: + KMSPageFlipper(int drm_fd, std::shared_ptr const& report); + + bool schedule_flip(uint32_t crtc_id, uint32_t fb_id, uint32_t connector_id) override; + Frame wait_for_flip(uint32_t crtc_id) override; + + std::thread::id debug_get_worker_tid(); + + void notify_page_flip(uint32_t crtc_id, int64_t msc, std::chrono::nanoseconds ust); +private: + bool page_flip_is_done(uint32_t crtc_id); + + int const drm_fd; + std::shared_ptr const report; + std::unordered_map pending_page_flips; + std::unordered_map completed_page_flips; + std::mutex pf_mutex; + std::condition_variable pf_cv; + std::thread::id worker_tid; + clockid_t clock_id; +}; + +} +} +} + +#endif /* MIR_GRAPHICS_GBM_KMS_PAGE_FLIPPER_H_ */ diff --git a/src/platforms/atomic-kms/server/kms/platform.cpp b/src/platforms/atomic-kms/server/kms/platform.cpp new file mode 100644 index 00000000000..4b4578fd045 --- /dev/null +++ b/src/platforms/atomic-kms/server/kms/platform.cpp @@ -0,0 +1,159 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "platform.h" +#include "display.h" +#include "mir/console_services.h" +#include "mir/emergency_cleanup_registry.h" +#include "mir/graphics/platform.h" +#include "mir/udev/wrapper.h" +#include "one_shot_device_observer.h" +#include +#include +#include + +#define MIR_LOG_COMPONENT "atomic-kms" +#include "mir/log.h" + +namespace mg = mir::graphics; +namespace mga = mg::atomic; + +namespace +{ +auto master_fd_for_device(mir::udev::Device const& device, mir::ConsoleServices& vt) -> std::tuple, mir::Fd> +{ + mir::Fd drm_fd; + auto device_handle = vt.acquire_device( + major(device.devnum()), minor(device.devnum()), + std::make_unique(drm_fd)) + .get(); + + if (drm_fd == mir::Fd::invalid) + { + BOOST_THROW_EXCEPTION((std::runtime_error{"Failed to acquire DRM fd"})); + } + + if (auto err = drmSetClientCap(drm_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) + { + BOOST_THROW_EXCEPTION(( + std::system_error{ + -err, + std::system_category(), + "Failed to enable DRM Universal Planes support"})); + } + + if (auto err = drmSetClientCap(drm_fd, DRM_CLIENT_CAP_ATOMIC, 1)) + { + BOOST_THROW_EXCEPTION(( + std::system_error{ + -err, + std::system_category(), + "Failed to enable Atomic KMS support"})); + } + + return std::make_tuple(std::move(device_handle), std::move(drm_fd)); +} + +auto maybe_make_gbm_provider(mir::Fd drm_fd) -> std::shared_ptr +{ + try + { + return std::make_shared(std::move(drm_fd)); + } + catch (std::exception const& err) + { + mir::log_info("Failed to create GBM device for direct buffer submission"); + mir::log_info("Output will use CPU buffer copies"); + return {}; + } +} +} + +mga::Platform::Platform( + udev::Device const& device, + std::shared_ptr const& listener, + ConsoleServices& vt, + EmergencyCleanupRegistry& registry, + BypassOption bypass_option) + : Platform(master_fd_for_device(device, vt), listener, registry, bypass_option) +{ +} + +mga::Platform::Platform( + std::tuple, mir::Fd> drm, + std::shared_ptr const& listener, + EmergencyCleanupRegistry&, + BypassOption bypass_option) + : udev{std::make_shared()}, + listener{listener}, + device_handle{std::move(std::get<0>(drm))}, + drm_fd{std::move(std::get<1>(drm))}, + gbm_display_provider{maybe_make_gbm_provider(drm_fd)}, + bypass_option_{bypass_option} +{ + if (drm_fd == mir::Fd::invalid) + { + BOOST_THROW_EXCEPTION((std::logic_error{"Invalid DRM device FD"})); + } +} + +mga::Platform::~Platform() = default; + +namespace +{ +auto gbm_device_from_provider(std::shared_ptr const& provider) + -> std::shared_ptr +{ + if (provider) + { + return provider->gbm_device(); + } + return nullptr; +} +} + +mir::UniqueModulePtr mga::Platform::create_display( + std::shared_ptr const& initial_conf_policy, std::shared_ptr const&) +{ + return make_module_ptr( + drm_fd, + gbm_device_from_provider(gbm_display_provider), + bypass_option_, + initial_conf_policy, + listener); +} + +auto mga::Platform::maybe_create_provider(DisplayProvider::Tag const& type_tag) + -> std::shared_ptr +{ + if (dynamic_cast(&type_tag)) + { + return gbm_display_provider; + } + if (dynamic_cast(&type_tag)) + { + /* There's no implementation behind it, but we want to know during probe time + * that the DisplayBuffers will support it. + */ + return std::make_shared(); + } + return {}; +} + +mga::BypassOption mga::Platform::bypass_option() const +{ + return bypass_option_; +} diff --git a/src/platforms/atomic-kms/server/kms/platform.h b/src/platforms/atomic-kms/server/kms/platform.h new file mode 100644 index 00000000000..348294d411d --- /dev/null +++ b/src/platforms/atomic-kms/server/kms/platform.h @@ -0,0 +1,82 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_GRAPHICS_GBM_PLATFORM_H_ +#define MIR_GRAPHICS_GBM_PLATFORM_H_ + +#include "mir/graphics/platform.h" +#include "platform_common.h" +#include "display_helpers.h" + +#include + +namespace mir +{ +class EmergencyCleanupRegistry; +class ConsoleServices; + +namespace graphics +{ +namespace atomic +{ + +class Quirks; + +class Platform : public graphics::DisplayPlatform +{ +public: + Platform( + udev::Device const& device, + std::shared_ptr const& reporter, + ConsoleServices& vt, + EmergencyCleanupRegistry& emergency_cleanup_registry, + BypassOption bypass_option); + + ~Platform() override; + + /* From Platform */ + UniqueModulePtr create_display( + std::shared_ptr const& initial_conf_policy, + std::shared_ptr const& gl_config) override; + + std::shared_ptr udev; + + std::shared_ptr const listener; + +protected: + auto maybe_create_provider(DisplayProvider::Tag const& type_tag) + -> std::shared_ptr override; + +public: + BypassOption bypass_option() const; +private: + Platform( + std::tuple, mir::Fd> drm, + std::shared_ptr const& reporter, + EmergencyCleanupRegistry& emergency_cleanup_registry, + BypassOption bypass_option); + + std::unique_ptr const device_handle; + mir::Fd const drm_fd; + + std::shared_ptr gbm_display_provider; + + BypassOption const bypass_option_; +}; +} +} +} +#endif /* MIR_GRAPHICS_GBM_PLATFORM_H_ */ diff --git a/src/platforms/atomic-kms/server/kms/platform_symbols.cpp b/src/platforms/atomic-kms/server/kms/platform_symbols.cpp new file mode 100644 index 00000000000..fbc4c07513b --- /dev/null +++ b/src/platforms/atomic-kms/server/kms/platform_symbols.cpp @@ -0,0 +1,322 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "mir/graphics/platform.h" +#include +#define MIR_LOG_COMPONENT "atomic-kms" +#include "mir/log.h" + +#include "platform.h" +#include "display_helpers.h" +#include "quirks.h" +#include "kms-utils/drm_mode_resources.h" +#include "mir/options/program_option.h" +#include "mir/options/option.h" +#include "mir/options/configuration.h" +#include "mir/udev/wrapper.h" +#include "mir/module_deleter.h" +#include "mir/assert_module_entry_point.h" +#include "mir/libname.h" +#include "mir/console_services.h" +#include "one_shot_device_observer.h" +#include "mir/graphics/egl_error.h" +#include "mir/graphics/gl_config.h" +#include "mir/graphics/egl_logger.h" + +#include +#include +#include "egl_helper.h" +#include +#include + +namespace mg = mir::graphics; +namespace mga = mg::atomic; +namespace mgc = mir::graphics::common; +namespace mo = mir::options; +namespace mgk = mir::graphics::kms; + +namespace +{ +char const* bypass_option_name{"bypass"}; + +} + +mir::UniqueModulePtr create_display_platform( + mg::SupportedDevice const& device, + std::shared_ptr const& options, + std::shared_ptr const& emergency_cleanup_registry, + std::shared_ptr const& console, + std::shared_ptr const& report) +{ + mir::assert_entry_point_signature(&create_display_platform); + // ensure atomic-kms finds the atomic-kms mir-platform symbols + + if (options->is_set(mir::options::debug_opt)) + { + mg::initialise_egl_logger(); + } + + auto bypass_option = mga::BypassOption::allowed; + if (!options->get(bypass_option_name)) + bypass_option = mga::BypassOption::prohibited; + + return mir::make_module_ptr( + *device.device, report, *console, *emergency_cleanup_registry, bypass_option); +} + +void add_graphics_platform_options(boost::program_options::options_description& config) +{ + mir::assert_entry_point_signature(&add_graphics_platform_options); + config.add_options() + (bypass_option_name, + boost::program_options::value()->default_value(false), + "[platform-specific] utilize the bypass optimization for fullscreen surfaces."); + mga::Quirks::add_quirks_option(config); +} + +namespace +{ +class MinimalGLConfig : public mir::graphics::GLConfig +{ +public: + int depth_buffer_bits() const override + { + return 0; + } + + int stencil_buffer_bits() const override + { + return 0; + } +}; +} + +auto probe_display_platform( + std::shared_ptr const& console, + std::shared_ptr const& udev, + mir::options::Option const& options) -> std::vector +{ + mir::assert_entry_point_signature(&probe_display_platform); + + mga::Quirks quirks{options}; + + mir::udev::Enumerator drm_devices{udev}; + drm_devices.match_subsystem("drm"); + drm_devices.match_sysname("card[0-9]*"); + drm_devices.scan_devices(); + + if (drm_devices.begin() == drm_devices.end()) + { + mir::log_info("Unsupported: No DRM devices detected"); + return {}; + } + + // We also require GBM EGL platform + auto const* client_extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + if (!client_extensions) + { + // Doesn't support EGL client extensions; Mesa does, so this is unlikely to be atomic-kms. + mir::log_info("Unsupported: EGL platform does not support client extensions."); + return {}; + } + if (strstr(client_extensions, "EGL_KHR_platform_gbm") == nullptr) + { + // Doesn't support the Khronos-standardised GBM platform… + mir::log_info("EGL platform does not support EGL_KHR_platform_gbm extension"); + // …maybe we support the old pre-standardised Mesa GBM platform? + if (strstr(client_extensions, "EGL_MESA_platform_gbm") == nullptr) + { + mir::log_info( + "Unsupported: EGL platform supports neither EGL_KHR_platform_gbm nor EGL_MESA_platform_gbm"); + return {}; + } + } + + + // Check for suitability + std::vector supported_devices; + mir::Fd tmp_fd; + for (auto& device : drm_devices) + { + if (quirks.should_skip(device)) + { + mir::log_info("Not probing device %s due to specified quirk", device.devnode()); + continue; + } + + auto const devnum = device.devnum(); + if (devnum == makedev(0, 0)) + { + /* The display connectors attached to the card appear as subdevices + * of the card[0-9] node. + * These won't have a device node, so pass on anything that doesn't have + * a /dev/dri/card* node + */ + continue; + } + + try + { + // Rely on the console handing us a DRM master... + auto const device_cleanup = console->acquire_device( + major(devnum), minor(devnum), + std::make_unique(tmp_fd)).get(); + + // We have a device. We don't know if it works yet, but it's a device we *could* support + supported_devices.emplace_back( + mg::SupportedDevice{ + device.clone(), + mg::probe::unsupported, + nullptr + }); + + if (tmp_fd != mir::Fd::invalid) + { + // Check that the drm device is usable by setting the interface version we use (1.4) + drmSetVersion sv; + sv.drm_di_major = 1; + sv.drm_di_minor = 4; + sv.drm_dd_major = -1; /* Don't care */ + sv.drm_dd_minor = -1; /* Don't care */ + + if (auto error = -drmSetInterfaceVersion(tmp_fd, &sv)) + { + BOOST_THROW_EXCEPTION((std::system_error{ + error, + std::system_category(), + std::string{"Failed to set DRM interface version on device "} + device.devnode()})); + } + + if (!mgk::get_cap_checked(tmp_fd, DRM_CLIENT_CAP_ATOMIC)) + { + mir::log_info("KMS device %s does not support Atomic KMS", device.devnode()); + continue; + } + + // For now, we *also* require our DisplayPlatform to support creating a HW EGL context + mga::helpers::GBMHelper atomic_device{tmp_fd}; + mga::helpers::EGLHelper egl{MinimalGLConfig()}; + + egl.setup(atomic_device); + + egl.make_current(); + + auto const renderer_string = reinterpret_cast(glGetString(GL_RENDERER)); + if (!renderer_string) + { + throw mg::gl_error( + "Probe failed to query GL renderer"); + } + + using namespace std::literals::string_literals; + if ("llvmpipe"s == renderer_string) + { + mir::log_info("KMS device only has associated software renderer: %s, device unsuitable", renderer_string); + supported_devices.back().support_level = mg::probe::unsupported; + continue; + } + + /* Check if modesetting is supported on this DRM node + * This must be done after drmSetInterfaceVersion() as, for Hysterical Raisins, + * drmGetBusid() will return nullptr unless drmSetInterfaceVersion() has already been called + */ + auto const busid = std::unique_ptr{ + drmGetBusid(tmp_fd), + &drmFreeBusid + }; + + if (!busid) + { + mir::log_warning( + "Failed to query BusID for device %s; cannot check if KMS is available", + device.devnode()); + supported_devices.back().support_level = mg::probe::supported; + } + else + { + mg::kms::DRMModeResources kms_resources{tmp_fd}; + switch (auto err = -drmCheckModesettingSupported(busid.get())) + { + case 0: + // We've got a DRM device that supports KMS. Let's see if it's got any output hardware! + if ((kms_resources.num_connectors() > 0) && + (kms_resources.num_crtcs() > 0) && + (kms_resources.num_encoders() > 0)) + { + // It supports KMS *and* can drive at least one physical output! Top hole! + supported_devices.back().support_level = mg::probe::best; + } + else + { + mir::log_info("KMS support found, but device has no output hardware."); + mir::log_info("This is probably a render-only hybrid graphics device"); + } + break; + + case ENOSYS: + if (quirks.require_modesetting_support(device)) + { + throw std::runtime_error{std::string{"Device "}+device.devnode()+" does not support KMS"}; + } + + [[fallthrough]]; + case EINVAL: + mir::log_warning( + "Failed to detect whether device %s supports KMS, continuing with lower confidence", + device.devnode()); + supported_devices.back().support_level = mg::probe::supported; + break; + + default: + mir::log_warning("Unexpected error from drmCheckModesettingSupported(): %s (%i), " + "but continuing anyway", strerror(err), err); + mir::log_warning("Please file a bug at " + "https://github.com/MirServer/mir/issues containing this message"); + supported_devices.back().support_level = mg::probe::supported; + } + } + } + } + catch (std::exception const& e) + { + mir::log( + mir::logging::Severity::informational, + MIR_LOG_COMPONENT, + std::current_exception(), + "Failed to probe DRM device"); + } + } + + return supported_devices; +} + +namespace +{ +mir::ModuleProperties const description = { + "mir:atomic-kms", + MIR_VERSION_MAJOR, + MIR_VERSION_MINOR, + MIR_VERSION_MICRO, + mir::libname() +}; +} + +mir::ModuleProperties const* describe_graphics_module() +{ + mir::assert_entry_point_signature(&describe_graphics_module); + return &description; +} + diff --git a/src/platforms/atomic-kms/server/kms/quirks.cpp b/src/platforms/atomic-kms/server/kms/quirks.cpp new file mode 100644 index 00000000000..cf4740d203e --- /dev/null +++ b/src/platforms/atomic-kms/server/kms/quirks.cpp @@ -0,0 +1,204 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "quirks.h" + +#include "mir/log.h" +#include "mir/options/option.h" +#include "mir/udev/wrapper.h" + +#include +#include + +namespace mga = mir::graphics::atomic; +namespace mo = mir::options; + +namespace +{ +char const* quirks_option_name = "driver-quirks"; +} + +namespace +{ +auto value_or(char const* maybe_null_string, char const* value_if_null) -> char const* +{ + if (maybe_null_string) + { + return maybe_null_string; + } + else + { + return value_if_null; + } +} +} + +class mga::Quirks::Impl +{ +public: + explicit Impl(mo::Option const& options) + { + if (!options.is_set(quirks_option_name)) + { + return; + } + + for (auto const& quirk : options.get>(quirks_option_name)) + { + auto const disable_kms_probe = "disable-kms-probe:"; + auto const skip_devnode = "skip:devnode:"; + auto const skip_driver = "skip:driver:"; + auto const allow_devnode = "allow:devnode:"; + auto const allow_driver = "allow:driver:"; + + if (quirk.starts_with(skip_devnode)) + { + devnodes_to_skip.insert(quirk.substr(strlen(skip_devnode))); + continue; + } + else if (quirk.starts_with(skip_driver)) + { + drivers_to_skip.insert(quirk.substr(strlen(skip_driver))); + continue; + } + else if (quirk.starts_with(allow_devnode)) + { + devnodes_to_skip.erase(quirk.substr(strlen(allow_devnode))); + continue; + } + else if (quirk.starts_with(allow_driver)) + { + drivers_to_skip.erase(quirk.substr(strlen(allow_driver))); + continue; + } + else if (quirk.starts_with(disable_kms_probe)) + { + // Quirk format is disable-kms-probe:value + skip_modesetting_support.emplace(quirk.substr(strlen(disable_kms_probe))); + continue; + } + + // If we didn't `continue` above, we're ignoring... + mir::log_warning( + "Ignoring unexpected value for %s option: %s " + "(expects value of the form “skip::”, “allow::” or ”disable-kms-probe:”)", + quirks_option_name, + quirk.c_str()); + } + } + + auto should_skip(udev::Device const& device) const -> bool + { + auto const devnode = value_or(device.devnode(), ""); + auto const parent_device = device.parent(); + auto const driver = + [&]() + { + if (parent_device) + { + return value_or(parent_device->driver(), ""); + } + mir::log_warning("udev device has no parent! Unable to determine driver for quirks."); + return ""; + }(); + mir::log_debug("Quirks: checking device with devnode: %s, driver %s", device.devnode(), driver); + bool const should_skip_driver = drivers_to_skip.count(driver); + bool const should_skip_devnode = devnodes_to_skip.count(devnode); + if (should_skip_driver) + { + mir::log_info("Quirks: skipping device %s (matches driver quirk %s)", devnode, driver); + } + if (should_skip_devnode) + { + mir::log_info("Quirks: skipping device %s (matches devnode quirk %s)", devnode, devnode); + } + return should_skip_driver || should_skip_devnode; + } + + auto require_modesetting_support(mir::udev::Device const& device) const -> bool + { + auto const devnode = value_or(device.devnode(), ""); + auto const parent_device = device.parent(); + auto const driver = + [&]() + { + if (parent_device) + { + return value_or(parent_device->driver(), ""); + } + mir::log_warning("udev device has no parent! Unable to determine driver for quirks."); + return ""; + }(); + mir::log_debug("Quirks: checking device with devnode: %s, driver %s", device.devnode(), driver); + + bool const should_skip_modesetting_support = skip_modesetting_support.count(driver); + if (should_skip_modesetting_support) + { + mir::log_info("Quirks: skipping modesetting check %s (matches driver quirk %s)", devnode, driver); + } + return !should_skip_modesetting_support; + } + +private: + /* AST is a simple 2D output device, built into some motherboards. + * They do not have any 3D engine associated, so were quirked off to avoid https://github.com/canonical/mir/issues/2678 + * + * At least as of drivers ≤ version 550, the NVIDIA atomic implementation is buggy in a way that prevents + * Mir from working. Quirk off atomic-kms on NVIDIA. + */ + std::unordered_set drivers_to_skip = { "nvidia", "ast" }; + std::unordered_set devnodes_to_skip; + // We know this is currently useful for virtio_gpu, vc4-drm and v3d + std::unordered_set skip_modesetting_support = { "virtio_gpu", "vc4-drm", "v3d" }; +}; + +mga::Quirks::Quirks(const options::Option& options) + : impl{std::make_unique(options)} +{ +} + +mga::Quirks::~Quirks() = default; + +auto mga::Quirks::should_skip(udev::Device const& device) const -> bool +{ + return impl->should_skip(device); +} + +void mga::Quirks::add_quirks_option(boost::program_options::options_description& config) +{ + config.add_options() + (quirks_option_name, + boost::program_options::value>(), + "[platform-specific] Driver quirks to apply (may be specified multiple times; multiple quirks are combined)"); +} + +auto mir::graphics::atomic::Quirks::require_modesetting_support(mir::udev::Device const& device) const -> bool +{ + if (getenv("MIR_MESA_KMS_DISABLE_MODESET_PROBE") != nullptr) + { + mir::log_debug("MIR_MESA_KMS_DISABLE_MODESET_PROBE is set"); + return false; + } + else if (getenv("MIR_GBM_KMS_DISABLE_MODESET_PROBE") != nullptr) + { + mir::log_debug("MIR_GBM_KMS_DISABLE_MODESET_PROBE is set"); + return false; + } + else + { + return impl->require_modesetting_support(device); + } +} diff --git a/src/platforms/atomic-kms/server/kms/quirks.h b/src/platforms/atomic-kms/server/kms/quirks.h new file mode 100644 index 00000000000..0e3427c4d72 --- /dev/null +++ b/src/platforms/atomic-kms/server/kms/quirks.h @@ -0,0 +1,67 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_GRAPHICS_GBM_KMS_QUIRKS_H_ +#define MIR_GRAPHICS_GBM_KMS_QUIRKS_H_ + +#include +#include + +namespace mir +{ +namespace options +{ +class Option; +} +namespace udev +{ +class Device; +} + +namespace graphics::atomic +{ +/** + * Interface for querying device-specific quirks + */ +class Quirks +{ +public: + explicit Quirks(options::Option const& options); + ~Quirks(); + + /** + * Should this device be skipped entirely from use and probing? + */ + [[nodiscard]] + auto should_skip(udev::Device const& device) const -> bool; + + /** + * Should we require this device to have modesetting support? + */ + [[nodiscard]] + auto require_modesetting_support(udev::Device const& device) const -> bool; + + static void add_quirks_option(boost::program_options::options_description& config); + +private: + class Impl; + std::unique_ptr const impl; +}; +} +} + + +#endif //MIR_GRAPHICS_GBM_KMS_QUIRKS_H_ diff --git a/src/platforms/atomic-kms/server/kms/real_kms_display_configuration.cpp b/src/platforms/atomic-kms/server/kms/real_kms_display_configuration.cpp new file mode 100644 index 00000000000..1e02c883f3e --- /dev/null +++ b/src/platforms/atomic-kms/server/kms/real_kms_display_configuration.cpp @@ -0,0 +1,235 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "real_kms_display_configuration.h" +#include "kms_output.h" +#include "kms_output_container.h" +#include "mir/graphics/pixel_format_utils.h" +#include "mir/log.h" +#include "mir/output_type_names.h" + +#include + +#include +#include +#include +#include +#include +#include + +namespace mg = mir::graphics; +namespace mga = mir::graphics::atomic; +namespace mgk = mir::graphics::kms; +namespace geom = mir::geometry; + +mga::RealKMSDisplayConfiguration::RealKMSDisplayConfiguration( + std::shared_ptr const& displays) + : displays{displays}, + card{mg::DisplayConfigurationCardId{0}, 0} +{ + update(); +} + +mga::RealKMSDisplayConfiguration::RealKMSDisplayConfiguration( + RealKMSDisplayConfiguration const& conf) + : KMSDisplayConfiguration(), + displays{conf.displays}, + card{conf.card}, + outputs{conf.outputs} +{ +} + +mga::RealKMSDisplayConfiguration& mga::RealKMSDisplayConfiguration::operator=( + RealKMSDisplayConfiguration const& conf) +{ + if (&conf != this) + { + displays = conf.displays; + card = conf.card; + outputs = conf.outputs; + } + + return *this; +} + +void mga::RealKMSDisplayConfiguration::for_each_output( + std::function f) const +{ + for (auto const& output_pair : outputs) + f(output_pair.first); +} + +void mga::RealKMSDisplayConfiguration::for_each_output( + std::function f) +{ + for (auto& output_pair : outputs) + { + UserDisplayConfigurationOutput user(output_pair.first); + f(user); + } +} + +std::unique_ptr mga::RealKMSDisplayConfiguration::clone() const +{ + return std::make_unique(*this); +} + +mga::RealKMSDisplayConfiguration::Output const& +mga::RealKMSDisplayConfiguration::output(DisplayConfigurationOutputId id) const +{ + return outputs.at(id.as_value() - 1); +} + +std::shared_ptr mga::RealKMSDisplayConfiguration::get_output_for( + DisplayConfigurationOutputId id) const +{ + return output(id).second; +} + +size_t mga::RealKMSDisplayConfiguration::get_kms_mode_index( + DisplayConfigurationOutputId id, + size_t conf_mode_index) const +{ + if (static_cast(id.as_value()) > outputs.size()) + { + BOOST_THROW_EXCEPTION(std::invalid_argument("Request for KMS mode index of invalid output ID")); + } + if (conf_mode_index > output(id).first.modes.size()) + { + BOOST_THROW_EXCEPTION(std::invalid_argument("Request for out-of-bounds KMS mode index")); + } + + return conf_mode_index; +} + +namespace +{ +void populate_default_mir_config(mg::DisplayConfigurationOutput& to_populate) +{ + to_populate.card_id = mg::DisplayConfigurationCardId{0}; + to_populate.gamma_supported = mir_output_gamma_supported; + to_populate.orientation = mir_orientation_normal; + to_populate.form_factor = mir_form_factor_monitor; + to_populate.scale = 1.0f; + to_populate.top_left = geom::Point{}; + to_populate.used = false; + to_populate.pixel_formats = {mir_pixel_format_xrgb_8888, mir_pixel_format_argb_8888}; + to_populate.current_format = mir_pixel_format_xrgb_8888; + to_populate.current_mode_index = std::numeric_limits::max(); +} + +void name_outputs(std::vector>>& outputs) +{ + std::map> card_map; + + for (auto& output_pair : outputs) + { + auto& conf_output = output_pair.first; + auto const type = conf_output.type; + auto const index_by_type = ++card_map[conf_output.card_id][type]; + + std::ostringstream out; + + out << mir::output_type_name(static_cast(type)); + if (conf_output.card_id.as_value() > 0) + out << '-' << conf_output.card_id.as_value(); + out << '-' << index_by_type; + + conf_output.name = out.str(); + } +} +} + +void mga::RealKMSDisplayConfiguration::update() +{ + decltype(outputs) new_outputs; + + displays->update_from_hardware_state(); + displays->for_each_output( + [this, &new_outputs](auto const& output) mutable + { + DisplayConfigurationOutput mir_config; + + auto const existing_output = std::find_if( + outputs.begin(), + outputs.end(), + [&output](auto const& candidate) + { + // Pointer comparison; is this KMSOutput object already present? + return candidate.second == output; + }); + if (existing_output == outputs.end()) + { + populate_default_mir_config(mir_config); + } + else + { + mir_config = existing_output->first; + } + + output->update_from_hardware_state(mir_config); + mir_config.id = DisplayConfigurationOutputId{int(new_outputs.size() + 1)}; + + new_outputs.emplace_back(mir_config, output); + }); + + name_outputs(new_outputs); + outputs = new_outputs; + + /* + * This is not the true max simultaneous outputs, but it's unclear whether it's possible + * to provide a max_simultaneous_outputs value that is useful to clients. + */ + card.max_simultaneous_outputs = outputs.size(); +} + +// Compatibility means conf1 can be attained from conf2 (and vice versa) +// without recreating the display buffers (e.g. conf1 and conf2 are identical +// except one of the outputs of conf1 is rotated w.r.t. that of conf2). If +// the two outputs differ in their power state, the display buffers would need +// to be allocated/destroyed, and hence should not be considered compatible. +bool mga::compatible(mga::RealKMSDisplayConfiguration const& conf1, mga::RealKMSDisplayConfiguration const& conf2) +{ + bool compatible{ + (conf1.card == conf2.card) && + (conf1.outputs.size() == conf2.outputs.size())}; + + if (compatible) + { + unsigned int const count = conf1.outputs.size(); + + for (unsigned int i = 0; i < count; ++i) + { + compatible &= (conf1.outputs[i].first.power_mode == conf2.outputs[i].first.power_mode); + if (compatible) + { + auto clone = conf2.outputs[i].first; + + // ignore difference in orientation, scale factor, form factor, subpixel arrangement + clone.orientation = conf1.outputs[i].first.orientation; + clone.subpixel_arrangement = conf1.outputs[i].first.subpixel_arrangement; + clone.scale = conf1.outputs[i].first.scale; + clone.form_factor = conf1.outputs[i].first.form_factor; + clone.custom_logical_size = conf1.outputs[i].first.custom_logical_size; + compatible &= (conf1.outputs[i].first == clone); + } + else + break; + } + } + + return compatible; +} diff --git a/src/platforms/atomic-kms/server/kms/real_kms_display_configuration.h b/src/platforms/atomic-kms/server/kms/real_kms_display_configuration.h new file mode 100644 index 00000000000..b58c37d5c26 --- /dev/null +++ b/src/platforms/atomic-kms/server/kms/real_kms_display_configuration.h @@ -0,0 +1,65 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_GRAPHICS_ATOMIC_REAL_KMS_DISPLAY_CONFIGURATION_H_ +#define MIR_GRAPHICS_ATOMIC_REAL_KMS_DISPLAY_CONFIGURATION_H_ + +#include "kms_display_configuration.h" + +#include + +namespace mir +{ +namespace graphics +{ +namespace atomic +{ +class KMSOutput; +class KMSOutputContainer; + +class RealKMSDisplayConfiguration : public KMSDisplayConfiguration +{ +friend bool compatible(RealKMSDisplayConfiguration const& conf1, RealKMSDisplayConfiguration const& conf2); + +public: + RealKMSDisplayConfiguration(std::shared_ptr const& displays); + RealKMSDisplayConfiguration(RealKMSDisplayConfiguration const& conf); + RealKMSDisplayConfiguration& operator=(RealKMSDisplayConfiguration const& conf); + + void for_each_output(std::function f) const override; + void for_each_output(std::function f) override; + std::unique_ptr clone() const override; + + std::shared_ptr get_output_for(DisplayConfigurationOutputId id) const override; + size_t get_kms_mode_index(DisplayConfigurationOutputId id, size_t conf_mode_index) const override; + void update() override; + +private: + + std::shared_ptr displays; + DisplayConfigurationCard card; + typedef std::pair> Output; + Output const& output(DisplayConfigurationOutputId id) const; + std::vector outputs; +}; + +bool compatible(RealKMSDisplayConfiguration const& conf1, RealKMSDisplayConfiguration const& conf2); + +} +} +} + +#endif /* MIR_GRAPHICS_ATOMIC_REAL_KMS_DISPLAY_CONFIGURATION_H_ */ diff --git a/src/platforms/atomic-kms/server/kms/real_kms_output_container.cpp b/src/platforms/atomic-kms/server/kms/real_kms_output_container.cpp new file mode 100644 index 00000000000..7e355f13f5a --- /dev/null +++ b/src/platforms/atomic-kms/server/kms/real_kms_output_container.cpp @@ -0,0 +1,78 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include +#include "real_kms_output_container.h" +#include "atomic_kms_output.h" +#include "kms-utils/drm_event_handler.h" +#include "kms-utils/drm_mode_resources.h" + +namespace mga = mir::graphics::atomic; + +mga::RealKMSOutputContainer::RealKMSOutputContainer( + mir::Fd drm_fd, + std::shared_ptr event_handler) + : drm_fd{std::move(drm_fd)}, + event_handler{std::move(event_handler)} +{ +} + +void mga::RealKMSOutputContainer::for_each_output(std::function const&)> functor) const +{ + for(auto& output: outputs) + functor(output); +} + +void mga::RealKMSOutputContainer::update_from_hardware_state() +{ + decltype(outputs) new_outputs; + + auto const resources = std::make_unique(drm_fd); + + for (auto &&connector : resources->connectors()) + { + // Caution: O(n²) here, but n is the number of outputs, so should + // conservatively be << 100. + auto existing_output = std::find_if( + outputs.begin(), + outputs.end(), + [&connector, this](auto const &candidate) + { + return + connector->connector_id == candidate->id() && + drm_fd == candidate->drm_fd(); + }); + + if (existing_output != outputs.end()) + { + // We could drop this down to O(n) by being smarter about moving out + // of the outputs vector. + // + // That's a bit of a faff, so just do the simple thing for now. + new_outputs.push_back(*existing_output); + new_outputs.back()->refresh_hardware_state(); + } + else + { + new_outputs.push_back(std::make_shared( + drm_fd, + std::move(connector), + event_handler)); + } + } + + outputs = new_outputs; +} diff --git a/src/platforms/atomic-kms/server/kms/real_kms_output_container.h b/src/platforms/atomic-kms/server/kms/real_kms_output_container.h new file mode 100644 index 00000000000..97641bd876e --- /dev/null +++ b/src/platforms/atomic-kms/server/kms/real_kms_output_container.h @@ -0,0 +1,54 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_GRAPHICS_GBM_REAL_KMS_OUTPUT_CONTAINER_H_ +#define MIR_GRAPHICS_GBM_REAL_KMS_OUTPUT_CONTAINER_H_ + +#include "kms_output_container.h" +#include "mir/fd.h" +#include + +namespace mir +{ +namespace graphics +{ +namespace kms +{ +class DRMEventHandler; +} + +namespace atomic +{ + +class RealKMSOutputContainer : public KMSOutputContainer +{ +public: + RealKMSOutputContainer(mir::Fd drm_fd, std::shared_ptr event_handler); + + void for_each_output(std::function const&)> functor) const override; + + void update_from_hardware_state() override; +private: + mir::Fd const drm_fd; + std::shared_ptr const event_handler; + std::vector> outputs; +}; + +} +} +} + +#endif /* MIR_GRAPHICS_GBM_REAL_KMS_OUTPUT_CONTAINER_H_ */ diff --git a/src/platforms/atomic-kms/server/kms/symbols.map.in b/src/platforms/atomic-kms/server/kms/symbols.map.in new file mode 100644 index 00000000000..30dad63f1b7 --- /dev/null +++ b/src/platforms/atomic-kms/server/kms/symbols.map.in @@ -0,0 +1,9 @@ +@MIR_SERVER_GRAPHICS_PLATFORM_VERSION@ { + global: + add_graphics_platform_options; + probe_display_platform; + describe_graphics_module; + create_display_platform; + local: + *; +}; diff --git a/src/platforms/atomic-kms/server/platform_common.h b/src/platforms/atomic-kms/server/platform_common.h new file mode 100644 index 00000000000..cd7488fca09 --- /dev/null +++ b/src/platforms/atomic-kms/server/platform_common.h @@ -0,0 +1,36 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_GRAPHICS_GBM_PLATFORM_COMMON_H_ +#define MIR_GRAPHICS_GBM_PLATFORM_COMMON_H_ + +namespace mir +{ +namespace graphics +{ +namespace atomic +{ + +enum class BypassOption +{ + allowed, + prohibited +}; + +} +} +} +#endif /* MIR_GRAPHICS_GBM_PLATFORM_COMMON_H_ */ diff --git a/src/platforms/common/server/kms-utils/threaded_drm_event_handler.cpp b/src/platforms/common/server/kms-utils/threaded_drm_event_handler.cpp index fc68ab7cb68..4de5556d0f0 100644 --- a/src/platforms/common/server/kms-utils/threaded_drm_event_handler.cpp +++ b/src/platforms/common/server/kms-utils/threaded_drm_event_handler.cpp @@ -15,8 +15,6 @@ */ #include "threaded_drm_event_handler.h" -#include "mir/log.h" -#include "mir/logging/logger.h" #include #include diff --git a/tests/mir_test_doubles/CMakeLists.txt b/tests/mir_test_doubles/CMakeLists.txt index e24a66c5941..2943c401277 100644 --- a/tests/mir_test_doubles/CMakeLists.txt +++ b/tests/mir_test_doubles/CMakeLists.txt @@ -75,7 +75,7 @@ if (MIR_BUILD_PLATFORM_X11) ) endif() -if (MIR_BUILD_PLATFORM_GBM_KMS) +if (MIR_BUILD_PLATFORM_GBM_KMS OR MIR_BUILD_PLATFORM_ATOMIC_KMS) include_directories( ${PROJECT_SOURCE_DIR}/src/platforms/gbm-kms/server ${CMAKE_SOURCE_DIR} diff --git a/tests/unit-tests/CMakeLists.txt b/tests/unit-tests/CMakeLists.txt index f297e1fb503..e3e474a9cfd 100644 --- a/tests/unit-tests/CMakeLists.txt +++ b/tests/unit-tests/CMakeLists.txt @@ -8,6 +8,10 @@ if (MIR_BUILD_PLATFORM_GBM_KMS) add_compile_definitions(MIR_BUILD_PLATFORM_GBM_KMS) endif() +if (MIR_BUILD_PLATFORM_ATOMIC_KMS) + add_compile_definitions(MIR_BUILD_PLATFORM_ATOMIC_KMS) +endif() + if (MIR_BUILD_PLATFORM_X11) add_compile_definitions(MIR_BUILD_PLATFORM_X11) endif() diff --git a/tests/unit-tests/graphics/test_platform_prober.cpp b/tests/unit-tests/graphics/test_platform_prober.cpp index cd3609f3a98..a54450ec222 100644 --- a/tests/unit-tests/graphics/test_platform_prober.cpp +++ b/tests/unit-tests/graphics/test_platform_prober.cpp @@ -44,7 +44,7 @@ #include "mir/test/doubles/null_logger.h" #include "mir/test/doubles/mock_egl.h" -#if defined(MIR_BUILD_PLATFORM_GBM_KMS) +#if defined(MIR_BUILD_PLATFORM_GBM_KMS) || defined(MIR_BUILD_PLATFORM_ATOMIC_KMS) #include "mir/test/doubles/mock_drm.h" #include "mir/test/doubles/mock_gbm.h" #include "mir/test/doubles/mock_gl.h" @@ -70,6 +70,9 @@ std::vector> available_platforms() #ifdef MIR_BUILD_PLATFORM_GBM_KMS modules.push_back(std::make_shared(mtf::server_platform("graphics-gbm-kms"))); +#endif +#ifdef MIR_BUILD_PLATFORM_ATOMIC_KMS + modules.push_back(std::make_shared(mtf::server_platform("graphics-atomic-kms"))); #endif return modules; } @@ -97,12 +100,12 @@ std::shared_ptr ensure_mesa_probing_succeeds() struct MockEnvironment { mtf::UdevEnvironment udev; testing::NiceMock egl; -#ifdef MIR_BUILD_PLATFORM_GBM_KMS +#if defined(MIR_BUILD_PLATFORM_GBM_KMS) || defined(MIR_BUILD_PLATFORM_ATOMIC_KMS) testing::NiceMock gbm; testing::NiceMock gl; #endif }; -#ifdef MIR_BUILD_PLATFORM_GBM_KMS +#if defined (MIR_BUILD_PLATFORM_GBM_KMS) || defined(MIR_BUILD_PLATFORM_ATOMIC_KMS) static auto const fake_gbm_device = reinterpret_cast(0xa1b2c3d4); #endif static auto const fake_egl_display = reinterpret_cast(0xeda); @@ -111,7 +114,7 @@ std::shared_ptr ensure_mesa_probing_succeeds() env->udev.add_standard_device("standard-drm-devices"); ON_CALL(env->egl, eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS)) .WillByDefault(Return("EGL_MESA_platform_gbm EGL_EXT_platform_base")); -#ifdef MIR_BUILD_PLATFORM_GBM_KMS +#if defined(MIR_BUILD_PLATFORM_GBM_KMS) || defined(MIR_BUILD_PLATFORM_ATOMIC_KMS) ON_CALL(env->gbm, gbm_create_device(_)) .WillByDefault(Return(fake_gbm_device)); ON_CALL(env->egl, eglGetDisplay(fake_gbm_device)) @@ -123,7 +126,7 @@ std::shared_ptr ensure_mesa_probing_succeeds() SetArgPointee<1>(1), SetArgPointee<2>(4), Return(EGL_TRUE))); -#ifdef MIR_BUILD_PLATFORM_GBM_KMS +#if defined(MIR_BUILD_PLATFORM_GBM_KMS) || defined(MIR_BUILD_PLATFORM_ATOMIC_KMS) ON_CALL(env->egl, eglGetConfigAttrib(_, env->egl.fake_configs[0], EGL_NATIVE_VISUAL_ID, _)) .WillByDefault( DoAll( @@ -176,7 +179,7 @@ class StubConsoleServices : public mir::ConsoleServices class ServerPlatformProbeMockDRM : public ::testing::Test { -#if defined(MIR_BUILD_PLATFORM_GBM_KMS) +#if defined(MIR_BUILD_PLATFORM_GBM_KMS) || defined(MIR_BUILD_PLATFORM_ATOMIC_KMS) public: ::testing::NiceMock mock_drm; #endif @@ -210,7 +213,7 @@ TEST(ServerPlatformProbe, ConstructingWithNoModulesIsAnError) std::runtime_error); } -#ifdef MIR_BUILD_PLATFORM_GBM_KMS +#if defined(MIR_BUILD_PLATFORM_GBM_KMS) || defined(MIR_BUILD_PLATFORM_ATOMIC_KMS) TEST_F(ServerPlatformProbeMockDRM, LoadsMesaPlatformWhenDrmMasterCanBeAcquired) { using namespace testing; @@ -540,11 +543,11 @@ class FullProbeStack : public testing::Test .WillByDefault([this](auto, auto) { return egl_client_extensions.c_str(); }); } - auto add_kms_device(std::string const& driver_name = "i915") -> std::unique_ptr + auto add_kms_device(std::string const& driver_name [[maybe_unused]] = "i915") -> std::unique_ptr { using namespace std::string_literals; using namespace testing; -#ifndef MIR_BUILD_PLATFORM_GBM_KMS +#if !defined(MIR_BUILD_PLATFORM_GBM_KMS) && !defined(MIR_BUILD_PLATFORM_ATOMIC_KMS) return nullptr; #else @@ -693,10 +696,8 @@ class FullProbeStack : public testing::Test testing::NiceMock x11; testing::NiceMock egl; testing::NiceMock gl; -#ifdef MIR_BUILD_PLATFORM_GBM_KMS +#if defined(MIR_BUILD_PLATFORM_GBM_KMS) || defined(MIR_BUILD_PLATFORM_ATOMIC_KMS) testing::NiceMock gbm; -#endif -#if defined(MIR_BUILD_PLATFORM_GBM_KMS) int drm_device_count{0}; testing::NiceMock drm; #endif From e9a505f1e3c8d6e1a7dd692e02585ba9e211066f Mon Sep 17 00:00:00 2001 From: Christopher James Halse Rogers Date: Tue, 1 Oct 2024 19:34:50 +1000 Subject: [PATCH 04/27] platforms/atomic-kms: Populate supported pixel formats correctly (#3596) `DisplayConfigurationOutput.pixel_formats` now contains the list of accepted pixel formats (or, at least, those pixel formats that are representable in the `MirPixelFormats` enum; many aren't). --------- Co-authored-by: tarek-y-ismail Co-authored-by: Alan Griffiths --- .../server/kms/atomic_kms_output.cpp | 56 ++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp index 2553e8d2110..8502c361332 100644 --- a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp +++ b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp @@ -19,11 +19,13 @@ #include "kms-utils/drm_mode_resources.h" #include "kms_framebuffer.h" #include "mir/graphics/display_configuration.h" +#include "mir/graphics/drm_formats.h" #include "mir/graphics/gamma_curves.h" #include "mir_toolkit/common.h" #include "kms-utils/kms_connector.h" #include "mir/fatal.h" #include "mir/log.h" +#include #include #include #include // strcmp @@ -114,6 +116,11 @@ class PropertyBlobData */ return std::span{static_cast(ptr->data), ptr->length / sizeof(T)}; } + + auto raw() const -> drmModePropertyBlobRes const* + { + return ptr; + } private: drmModePropertyBlobPtr const ptr; }; @@ -647,6 +654,42 @@ std::vector edid_for_connector(mir::Fd const& drm_fd, uint32_t connecto return edid; } + +auto formats_for_output(mir::Fd const& drm_fd, mgk::DRMModeConnectorUPtr const& connector) -> std::vector +{ + auto [_, plane] = mgk::find_crtc_with_primary_plane(drm_fd, connector); + + mgk::ObjectProperties plane_props{drm_fd, plane->plane_id, DRM_MODE_OBJECT_PLANE}; + + if (!plane_props.has_property("IN_FORMATS")) + { + return {mg::DRMFormat{DRM_FORMAT_ARGB8888}, mg::DRMFormat{DRM_FORMAT_XRGB8888} }; + } + + PropertyBlobData format_blob{drm_fd, static_cast(plane_props["IN_FORMATS"])}; + drmModeFormatModifierIterator iter{}; + + std::vector supported_formats; + while (drmModeFormatModifierBlobIterNext(format_blob.raw(), &iter)) + { + /* This will iterate over {format, modifier} pairs, with all the modifiers for a single + * format in a block. For example: + * {fmt1, mod1} + * {fmt1, mod2} + * {fmt1, mod3} + * {fmt2, mod2} + * {fmt2, mod4} + * ... + * + * We only care about the format, so we only add when we see a new format + */ + if (supported_formats.empty() || supported_formats.back() != iter.fmt) + { + supported_formats.emplace_back(iter.fmt); + } + } + return supported_formats; +} } void mga::AtomicKMSOutput::update_from_hardware_state( @@ -660,8 +703,17 @@ void mga::AtomicKMSOutput::update_from_hardware_state( uint32_t current_mode_index{invalid_mode_index}; uint32_t preferred_mode_index{invalid_mode_index}; std::vector modes; - std::vector formats{mir_pixel_format_argb_8888, // PULL THESE OUT OF THE PROPERTIES - mir_pixel_format_xrgb_8888}; + std::vector formats; + + auto supported_formats = formats_for_output(drm_fd_, connector); + formats.reserve(supported_formats.size()); + for (auto const& format : supported_formats) + { + if (auto mir_format = format.as_mir_format()) + { + formats.push_back(*mir_format); + } + } std::vector edid; if (connected) From 0dc388b1180cee11e47f4647db42766e35cde7bf Mon Sep 17 00:00:00 2001 From: Tarek Ismail Date: Wed, 9 Oct 2024 17:19:50 +0300 Subject: [PATCH 05/27] Fix DRM FB using CRTC width/height --- .../server/kms/atomic_kms_output.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp index 8502c361332..86173a49149 100644 --- a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp +++ b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp @@ -283,7 +283,7 @@ bool mga::AtomicKMSOutput::set_crtc(FBHandle const& fb) AtomicUpdate update; update.add_property(*crtc_props, "MODE_ID", mode->handle()); update.add_property(*connector_props, "CRTC_ID", current_crtc->crtc_id); - + /* Source viewport. Coordinates are 16.16 fixed point format */ update.add_property(*plane_props, "SRC_X", fb_offset.dx.as_uint32_t() << 16); update.add_property(*plane_props, "SRC_Y", fb_offset.dy.as_uint32_t() << 16); @@ -379,24 +379,27 @@ bool mga::AtomicKMSOutput::schedule_page_flip(FBHandle const& fb) return false; } - auto const width = current_crtc->width; - auto const height = current_crtc->height; + auto const crtc_width = current_crtc->width; + auto const crtc_height = current_crtc->height; + + auto const fb_width = fb.size().width.as_uint32_t(); + auto const fb_height = fb.size().height.as_uint32_t(); AtomicUpdate update; update.add_property(*crtc_props, "MODE_ID", mode->handle()); update.add_property(*connector_props, "CRTC_ID", current_crtc->crtc_id); - + /* Source viewport. Coordinates are 16.16 fixed point format */ update.add_property(*plane_props, "SRC_X", fb_offset.dx.as_uint32_t() << 16); update.add_property(*plane_props, "SRC_Y", fb_offset.dy.as_uint32_t() << 16); - update.add_property(*plane_props, "SRC_W", width << 16); - update.add_property(*plane_props, "SRC_H", height << 16); + update.add_property(*plane_props, "SRC_W", fb_width << 16); + update.add_property(*plane_props, "SRC_H", fb_height << 16); /* Destination viewport. Coordinates are *not* 16.16 */ update.add_property(*plane_props, "CRTC_X", 0); update.add_property(*plane_props, "CRTC_Y", 0); - update.add_property(*plane_props, "CRTC_W", width); - update.add_property(*plane_props, "CRTC_H", height); + update.add_property(*plane_props, "CRTC_W", crtc_width); + update.add_property(*plane_props, "CRTC_H", crtc_height); /* Set a surface for the plane */ update.add_property(*plane_props, "CRTC_ID", current_crtc->crtc_id); From 7a6c9344c55854c3c423dc86f5c3fe845e1b5bf9 Mon Sep 17 00:00:00 2001 From: Christopher James Halse Rogers Date: Thu, 17 Oct 2024 18:02:44 +1100 Subject: [PATCH 06/27] platforms/atomic-kms: Fix `AtomicKMSOutput::clear_crtc` The dance required to actually disable an output (and free its resources) is surprisingly involved. --- src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp index 86173a49149..6045b450863 100644 --- a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp +++ b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp @@ -341,7 +341,11 @@ void mga::AtomicKMSOutput::clear_crtc() } AtomicUpdate update; + update.add_property(*connector_props, "CRTC_ID", 0); + update.add_property(*crtc_props, "ACTIVE", 0); + update.add_property(*crtc_props, "MODE_ID", 0); update.add_property(*plane_props, "FB_ID", 0); + update.add_property(*plane_props, "CRTC_ID", 0); auto result = drmModeAtomicCommit(drm_fd_, update, DRM_MODE_ATOMIC_ALLOW_MODESET, nullptr); if (result) From b5f03e07d50be89a52c6323e662b9ae49c0eb1d0 Mon Sep 17 00:00:00 2001 From: Christopher James Halse Rogers Date: Thu, 17 Oct 2024 18:06:00 +1100 Subject: [PATCH 07/27] platforms/atomic-kms: Fix `AtomicKMSOutput::set_power_mode` We might not actually have a `current_crtc` when `set_power_mode` is called (notably, when disabling an output we set `mir_power_mode_off`). Rather than crashing, log an error if we try and turn on an unconfigured output. Silently ignore trying to turn *off* an unconfigured output, as it's already off. --- .../atomic-kms/server/kms/atomic_kms_output.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp index 6045b450863..32200d1ff93 100644 --- a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp +++ b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp @@ -486,14 +486,20 @@ void mga::AtomicKMSOutput::restore_saved_crtc() void mga::AtomicKMSOutput::set_power_mode(MirPowerMode mode) { bool should_be_active = mode == mir_power_mode_on; - std::unique_ptr - request{drmModeAtomicAlloc(), &drmModeAtomicFree}; - drmModeAtomicAddProperty(request.get(), current_crtc->crtc_id, crtc_props->id_for("ACTIVE"), should_be_active); - drmModeAtomicCommit(drm_fd(), request.get(), DRM_MODE_ATOMIC_ALLOW_MODESET, nullptr); + if (current_crtc) + { + AtomicUpdate update; + update.add_property(*crtc_props, "ACTIVE", should_be_active); + drmModeAtomicCommit(drm_fd_, update, DRM_MODE_ATOMIC_ALLOW_MODESET, nullptr); + } + else if (should_be_active) + { + mir::log_error("Attempted to set mir_power_mode_on for unconfigured output"); + } + // An output which doesn't have a CRTC configured is already off, so that's not an error. } - void mga::AtomicKMSOutput::set_gamma(mg::GammaCurves const& gamma) { if (!gamma.red.size()) From 6b5ec1d617c8a07fd5075c6fdd556c0d1f5ae948 Mon Sep 17 00:00:00 2001 From: Christopher James Halse Rogers Date: Thu, 17 Oct 2024 18:09:32 +1100 Subject: [PATCH 08/27] platforms/atomic-kms: Fix modesetting with `AtomicKMSOutput::set_crtc` The way changing the display mode works is that the `configure` sets the desired `mode_index`, and then the *next* `set_crtc` is meant to actually set the mode. This means that we can wait for correctly-sized content to present at the new mode, rather than showing a single black frame before the correct content. But it also means that `set_crtc` needs to look at the requested mode, rather than the current mode! --- .../atomic-kms/server/kms/atomic_kms_output.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp index 32200d1ff93..944541ca316 100644 --- a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp +++ b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp @@ -277,8 +277,11 @@ bool mga::AtomicKMSOutput::set_crtc(FBHandle const& fb) return false; } - auto const width = current_crtc->width; - auto const height = current_crtc->height; + /* We use the *requested* mode rather than the current_crtc + * because we might have been asked to perform a modeset + */ + auto const width = connector->modes[mode_index].hdisplay; + auto const height = connector->modes[mode_index].vdisplay; AtomicUpdate update; update.add_property(*crtc_props, "MODE_ID", mode->handle()); @@ -308,6 +311,9 @@ bool mga::AtomicKMSOutput::set_crtc(FBHandle const& fb) return false; } + // We might have performed a modeset; update our view of the hardware state accordingly! + current_crtc = mgk::get_crtc(drm_fd_, current_crtc->crtc_id); + using_saved_crtc = false; return true; } From 3c2d3ca6d82eb5667046c09aaa5088fa5da193be Mon Sep 17 00:00:00 2001 From: Christopher James Halse Rogers Date: Thu, 17 Oct 2024 18:10:35 +1100 Subject: [PATCH 09/27] platforms/atomic-kms: Fix log message. We were trying to pageflip, so let's say that. --- src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp index 944541ca316..986a6031f55 100644 --- a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp +++ b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp @@ -423,7 +423,7 @@ bool mga::AtomicKMSOutput::schedule_page_flip(FBHandle const& fb) const_cast(event_handler->drm_event_data())); if (ret) { - mir::log_error("Failed to set CRTC: %s (%i)", strerror(-ret), -ret); + mir::log_error("Failed to schedule page flip: %s (%i)", strerror(-ret), -ret); current_crtc = nullptr; event_handler->cancel_flip_events(current_crtc->crtc_id); return false; From 99e65ba34be619ea002b62bce2b11a130f6f2b0a Mon Sep 17 00:00:00 2001 From: Christopher James Halse Rogers Date: Thu, 17 Oct 2024 18:14:10 +1100 Subject: [PATCH 10/27] platforms/atomic-kms: Don't try to pageflip incorrectly sized FBs Currently we expect to only flip framebuffers that are the same size, in pixels, as the output mode. Check in `schedule_page_flip` that this holds. Otherwise, we must be expecting a modeset and need to go through the `set_crtc` codepath. We might, later, update the system to handle using the display scalers to present framebuffers that aren't the same size as the output mode. --- .../atomic-kms/server/kms/atomic_kms_output.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp index 986a6031f55..cd70ab5ea71 100644 --- a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp +++ b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp @@ -395,6 +395,17 @@ bool mga::AtomicKMSOutput::schedule_page_flip(FBHandle const& fb) auto const fb_width = fb.size().width.as_uint32_t(); auto const fb_height = fb.size().height.as_uint32_t(); + if ((crtc_width != fb_width) || (crtc_height != fb_height)) + { + /* If the submitted FB isn't the same size as the output, we need + * a modeset, which can't be done as a pageflip. + * + * The calling code should detect a failure to pagefilp, and fall back + * on `set_crtc`. + */ + return false; + } + AtomicUpdate update; update.add_property(*crtc_props, "MODE_ID", mode->handle()); update.add_property(*connector_props, "CRTC_ID", current_crtc->crtc_id); From b09ddee21c1c9925ad3573d4882ea858ee699e04 Mon Sep 17 00:00:00 2001 From: Christopher James Halse Rogers Date: Fri, 18 Oct 2024 16:57:13 +1100 Subject: [PATCH 11/27] platforms/atomic-kms: Make DisplaySink associated with exactly one KMSOutput. This is a different behaviour to `gbm-kms`. On `gbm-kms`, when outputs have an overlapping view of the logical space they are grouped together so that they share a single *physical* framebuffer. When the overlap is great (such as a complete clone), this should result in lower GPU memory usage (as we need only a single set of framebuffers for all clones) at the cost of tying the refresh rates of each clone together. (And some bugs, like #3641). When the overlap is *not* great, this potential memory saving goes away (and may indeed result in *higher* GPU memory usage - if outputs have different sizes, we may now need to have a bunch of unused pixels, as the FB can only be rectangular). For Atomic KMS, instead, we give each output its own physical framebuffer, regardless of whether it overlaps. --- .../atomic-kms/server/kms/display.cpp | 78 +++++------ .../atomic-kms/server/kms/display_buffer.cpp | 124 +++++------------- .../atomic-kms/server/kms/display_sink.h | 6 +- 3 files changed, 75 insertions(+), 133 deletions(-) diff --git a/src/platforms/atomic-kms/server/kms/display.cpp b/src/platforms/atomic-kms/server/kms/display.cpp index dc0aa42734b..3e5d962860f 100644 --- a/src/platforms/atomic-kms/server/kms/display.cpp +++ b/src/platforms/atomic-kms/server/kms/display.cpp @@ -332,58 +332,58 @@ void mga::Display::configure_locked( }); } - /* Set up used outputs */ - OverlappingOutputGrouping grouping{kms_conf}; - auto group_idx = 0; - - grouping.for_each_group( - [&](OverlappingOutputGroup const& group) + size_t output_idx{0}; + kms_conf.for_each_output( + [&](DisplayConfigurationOutput const& out) { - auto bounding_rect = group.bounding_rectangle(); - std::vector> kms_outputs; - glm::mat2 transformation; - geom::Size current_mode_resolution; - - group.for_each_output( - [&](DisplayConfigurationOutput const& conf_output) - { - auto kms_output = current_display_configuration.get_output_for(conf_output.id); - - auto const mode_index = kms_conf.get_kms_mode_index(conf_output.id, - conf_output.current_mode_index); - kms_output->configure(conf_output.top_left - bounding_rect.top_left, mode_index); - if (!comp) - { - kms_output->set_power_mode(conf_output.power_mode); - kms_output->set_gamma(conf_output.gamma); - kms_outputs.push_back(std::move(kms_output)); - } - - /* - * Presently OverlappingOutputGroup guarantees all grouped - * outputs have the same transformation. - */ - transformation = conf_output.transformation(); - if (conf_output.current_mode_index < conf_output.modes.size()) - current_mode_resolution = conf_output.modes[conf_output.current_mode_index].size; - }); + if (!out.connected || !out.used || + (out.power_mode != mir_power_mode_on) || + (out.current_mode_index >= out.modes.size())) + { + // We don't need to do anything for unconfigured outputs + return; + } + + auto kms_output = kms_conf.get_output_for(out.id); + auto const mode_index = kms_conf.get_kms_mode_index(out.id, out.current_mode_index); + + kms_output->configure({0, 0}, mode_index); + + if (!comp) + { + kms_output->set_power_mode(out.power_mode); + kms_output->set_gamma(out.gamma); + } + + auto const transform = out.transformation(); if (comp) { - display_sinks[group_idx++]->set_transformation(transformation, - bounding_rect); + /* If we don't need a modeset we can just + * update our existing `DisplaySink`s. + * + * Note: this assumes that RealKMSDisplayConfiguration.for_each_output + * iterates over outputs in the same order each time. + */ + display_sinks[output_idx]->set_transformation( + transform, + out.extents()); + ++output_idx; } else { + /* If we need a modeset we'll create a new set + * of `DisplaySink`s + */ auto db = std::make_unique( drm_fd, gbm, event_handler, bypass_option, listener, - kms_outputs, - bounding_rect, - transformation); + std::move(kms_output), + out.extents(), + transform); display_buffers_new.push_back(std::move(db)); } diff --git a/src/platforms/atomic-kms/server/kms/display_buffer.cpp b/src/platforms/atomic-kms/server/kms/display_buffer.cpp index 4c5c17777df..e9f9aa358d3 100644 --- a/src/platforms/atomic-kms/server/kms/display_buffer.cpp +++ b/src/platforms/atomic-kms/server/kms/display_buffer.cpp @@ -53,12 +53,12 @@ mga::DisplaySink::DisplaySink( std::shared_ptr event_handler, mga::BypassOption, std::shared_ptr const& listener, - std::vector> const& outputs, + std::shared_ptr output, geom::Rectangle const& area, glm::mat2 const& transformation) : gbm{std::move(gbm)}, listener(listener), - outputs(outputs), + output{std::move(output)}, event_handler{std::move(event_handler)}, area(area), transform{transformation}, @@ -66,17 +66,7 @@ mga::DisplaySink::DisplaySink( { listener->report_successful_setup_of_native_resources(); - // If any of the outputs have a CRTC mismatch, we will want to set all of them - // so that they're all showing the same buffer. - bool has_crtc_mismatch = false; - for (auto& output : outputs) - { - has_crtc_mismatch = output->has_crtc_mismatch(); - if (has_crtc_mismatch) - break; - } - - if (has_crtc_mismatch) + if (this->output->has_crtc_mismatch()) { mir::log_info("Clearing screen due to differing encountered and target modes"); // TODO: Pull a supported format out of KMS rather than assuming XRGB8888 @@ -90,9 +80,7 @@ mga::DisplaySink::DisplaySink( ::memset(mapping->data(), 24, mapping->len()); visible_fb = std::move(initial_fb); - for (auto &output: outputs) { - output->set_crtc(*visible_fb); - } + this->output->set_crtc(*visible_fb); listener->report_successful_drm_mode_set_crtc_on_construction(); } listener->report_successful_display_construction(); @@ -151,20 +139,17 @@ void mga::DisplaySink::for_each_display_sink(std::functionset_crtc(forced_frame)) - mir::log_error("Failed to set DRM CRTC. " - "Screen contents may be incomplete. " - "Try plugging the monitor in again."); - } + /* + * Note that failure to set the CRTC is not a fatal error. This can + * happen under normal conditions when resizing VirtualBox (which + * actually removes and replaces the virtual output each time so + * sometimes it's really not there). Xorg often reports similar + * errors, and it's not fatal. + */ + if (!output->set_crtc(forced_frame)) + mir::log_error("Failed to set DRM CRTC. " + "Screen contents may be incomplete. " + "Try plugging the monitor in again."); } void mga::DisplaySink::post() @@ -215,50 +200,16 @@ void mga::DisplaySink::post() // Predicted worst case render time for the next frame... auto predicted_render_time = 50ms; - if (holding_client_buffers) - { - /* - * For composited frames we defer wait_for_page_flip till just before - * the next frame, but not for bypass frames. Deferring the flip of - * bypass frames would increase the time we held - * visible_bypass_frame unacceptably, resulting in client stuttering - * unless we allocate more buffers (which I'm trying to avoid). - * Also, bypass does not need the deferred page flip because it has - * no compositing/rendering step for which to save time for. - */ - wait_for_page_flip(); - - // It's very likely the next frame will be bypassed like this one so - // we only need time for kernel page flip scheduling... - predicted_render_time = 5ms; - } - else - { - /* - * Not in clone mode? We can afford to wait for the page flip then, - * making us double-buffered (noticeably less laggy than the triple - * buffering that clone mode requires). - */ - if (outputs.size() == 1) - wait_for_page_flip(); - - /* - * TODO: If you're optimistic about your GPU performance and/or - * measure it carefully you may wish to set predicted_render_time - * to a lower value here for lower latency. - * - *predicted_render_time = 9ms; // e.g. about the same as Weston - */ - } + wait_for_page_flip(); + + /* + * TODO: Make a sensible predicited_render_time + */ recommend_sleep = 0ms; - if (outputs.size() == 1) - { - auto const& output = outputs.front(); - auto const min_frame_interval = 1000ms / output->max_refresh_rate(); - if (predicted_render_time < min_frame_interval) - recommend_sleep = min_frame_interval - predicted_render_time; - } + auto const min_frame_interval = 1000ms / output->max_refresh_rate(); + if (predicted_render_time < min_frame_interval) + recommend_sleep = min_frame_interval - predicted_render_time; } std::chrono::milliseconds mga::DisplaySink::recommended_sleep() const @@ -272,33 +223,24 @@ bool mga::DisplaySink::schedule_page_flip(FBHandle const& bufobj) * Schedule the current front buffer object for display. Note that * the page flip is asynchronous and synchronized with vertical refresh. */ - /* TODO: This works badly if *some* outputs successfully flipped and - * others did not. We should instead have exactly one KMSOutput per DisplaySink - */ - for (auto& output : outputs) + if (output->schedule_page_flip(bufobj)) { - if (output->schedule_page_flip(bufobj)) - { - pending_flips.push_back(output.get()); - } + page_flip_pending = true; + return true; } - - return !pending_flips.empty(); + return false; } void mga::DisplaySink::wait_for_page_flip() { - if (!pending_flips.empty()) + if (page_flip_pending) { - for (auto pending_flip : pending_flips) - { - pending_flip->wait_for_page_flip(); - } - pending_flips.clear(); + output->wait_for_page_flip(); // The previously-scheduled FB has been page-flipped, and is now visible visible_fb = std::move(scheduled_fb); scheduled_fb = nullptr; + page_flip_pending = false; } } @@ -309,7 +251,7 @@ void mga::DisplaySink::schedule_set_crtc() auto mga::DisplaySink::drm_fd() const -> mir::Fd { - return mir::Fd{mir::IntOwnedFd{outputs.front()->drm_fd()}}; + return mir::Fd{mir::IntOwnedFd{output->drm_fd()}}; } auto mga::DisplaySink::gbm_device() const -> std::shared_ptr @@ -342,7 +284,7 @@ auto mga::DisplaySink::maybe_create_allocator(DisplayAllocator::Tag const& type_ { if (!kms_allocator) { - kms_allocator = kms::CPUAddressableDisplayAllocator::create_if_supported(drm_fd(), outputs.front()->size()); + kms_allocator = kms::CPUAddressableDisplayAllocator::create_if_supported(drm_fd(), output->size()); } return kms_allocator.get(); } @@ -350,7 +292,7 @@ auto mga::DisplaySink::maybe_create_allocator(DisplayAllocator::Tag const& type_ { if (!gbm_allocator) { - gbm_allocator = std::make_unique(drm_fd(), gbm, outputs.front()->size()); + gbm_allocator = std::make_unique(drm_fd(), gbm, output->size()); } return gbm_allocator.get(); } diff --git a/src/platforms/atomic-kms/server/kms/display_sink.h b/src/platforms/atomic-kms/server/kms/display_sink.h index 7257ce5263b..359637c1164 100644 --- a/src/platforms/atomic-kms/server/kms/display_sink.h +++ b/src/platforms/atomic-kms/server/kms/display_sink.h @@ -60,7 +60,7 @@ class DisplaySink : public graphics::DisplaySink, std::shared_ptr event_handler, BypassOption bypass_options, std::shared_ptr const& listener, - std::vector> const& outputs, + std::shared_ptr output, geometry::Rectangle const& area, glm::mat2 const& transformation); ~DisplaySink(); @@ -98,8 +98,8 @@ class DisplaySink : public graphics::DisplaySink, std::shared_ptr bypass_bufobj{nullptr}; std::shared_ptr const listener; - std::vector> const outputs; - std::vector pending_flips; + std::shared_ptr const output; + bool page_flip_pending{false}; std::shared_ptr const event_handler; From ebac9f3afaf8aa03322703a1d673650c8b9f2460 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Wed, 23 Oct 2024 11:06:51 +0200 Subject: [PATCH 12/27] Drop dead code --- .../include/gbm_format_conversions.h | 34 ------------------- 1 file changed, 34 deletions(-) delete mode 100644 src/platforms/atomic-kms/include/gbm_format_conversions.h diff --git a/src/platforms/atomic-kms/include/gbm_format_conversions.h b/src/platforms/atomic-kms/include/gbm_format_conversions.h deleted file mode 100644 index 42f78875ad2..00000000000 --- a/src/platforms/atomic-kms/include/gbm_format_conversions.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright © Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License version 2 or 3, - * as published by the Free Software Foundation. - * - * 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - -#ifndef MIR_GRAPHICS_GBM_GBM_FORMAT_CONVERSIONS_H_ -#define MIR_GRAPHICS_GBM_GBM_FORMAT_CONVERSIONS_H_ - -#include -#include -#include - -namespace mir -{ -namespace graphics -{ -namespace atomic -{ -enum : uint32_t { invalid_atomic_format = std::numeric_limits::max() }; - -} -} -} -#endif /* MIR_GRAPHICS_GBM_GBM_FORMAT_CONVERSIONS_H_ */ From 4e3489b40a7fadc706b984ac644f6b5242b8e4fa Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Wed, 23 Oct 2024 11:12:50 +0200 Subject: [PATCH 13/27] Consistent and non-conflicting header guards --- src/platforms/atomic-kms/server/display_helpers.h | 6 +++--- src/platforms/atomic-kms/server/kms/atomic_kms_output.h | 6 +++--- src/platforms/atomic-kms/server/kms/bypass.h | 6 +++--- src/platforms/atomic-kms/server/kms/display.h | 6 +++--- src/platforms/atomic-kms/server/kms/display_sink.h | 6 +++--- src/platforms/atomic-kms/server/kms/egl_helper.h | 6 +++--- .../atomic-kms/server/kms/kms_display_configuration.h | 6 +++--- src/platforms/atomic-kms/server/kms/kms_output.h | 6 +++--- src/platforms/atomic-kms/server/kms/kms_output_container.h | 6 +++--- src/platforms/atomic-kms/server/kms/kms_page_flipper.h | 6 +++--- src/platforms/atomic-kms/server/kms/quirks.h | 6 +++--- .../atomic-kms/server/kms/real_kms_display_configuration.h | 6 +++--- .../atomic-kms/server/kms/real_kms_output_container.h | 6 +++--- 13 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/platforms/atomic-kms/server/display_helpers.h b/src/platforms/atomic-kms/server/display_helpers.h index 6429da51da5..e4cb134e54b 100644 --- a/src/platforms/atomic-kms/server/display_helpers.h +++ b/src/platforms/atomic-kms/server/display_helpers.h @@ -14,8 +14,8 @@ * along with this program. If not, see . */ -#ifndef MIR_GRAPHICS_ATOMIC_DISPLAY_HELPERS_H_ -#define MIR_GRAPHICS_ATOMIC_DISPLAY_HELPERS_H_ +#ifndef MIR_GRAPHICS_GBM_ATOMIC_KMS_DISPLAY_HELPERS_H_ +#define MIR_GRAPHICS_GBM_ATOMIC_KMS_DISPLAY_HELPERS_H_ #include "mir/udev/wrapper.h" #include "mir/fd.h" @@ -86,4 +86,4 @@ class GBMHelper } } } -#endif /* MIR_GRAPHICS_ATOMIC_DISPLAY_HELPERS_H_ */ +#endif /* MIR_GRAPHICS_GBM_ATOMIC_KMS_DISPLAY_HELPERS_H_ */ diff --git a/src/platforms/atomic-kms/server/kms/atomic_kms_output.h b/src/platforms/atomic-kms/server/kms/atomic_kms_output.h index c10a406479c..d67f999ea59 100644 --- a/src/platforms/atomic-kms/server/kms/atomic_kms_output.h +++ b/src/platforms/atomic-kms/server/kms/atomic_kms_output.h @@ -14,8 +14,8 @@ * along with this program. If not, see . */ -#ifndef MIR_GRAPHICS_GBM_ATOMIC_KMS_OUTPUT_H_ -#define MIR_GRAPHICS_GBM_ATOMIC_KMS_OUTPUT_H_ +#ifndef MIR_GRAPHICS_GBM_ATOMIC_KMS_ATOMIC_OUTPUT_H_ +#define MIR_GRAPHICS_GBM_ATOMIC_KMS_ATOMIC_OUTPUT_H_ #include "kms_output.h" #include "kms-utils/drm_mode_resources.h" @@ -100,4 +100,4 @@ class AtomicKMSOutput : public KMSOutput } } -#endif /* MIR_GRAPHICS_GBM_ATOMIC_KMS_OUTPUT_H_ */ +#endif /* MIR_GRAPHICS_GBM_ATOMIC_KMS_ATOMIC_OUTPUT_H_ */ diff --git a/src/platforms/atomic-kms/server/kms/bypass.h b/src/platforms/atomic-kms/server/kms/bypass.h index 0637ff0c429..0159e268842 100644 --- a/src/platforms/atomic-kms/server/kms/bypass.h +++ b/src/platforms/atomic-kms/server/kms/bypass.h @@ -14,8 +14,8 @@ * along with this program. If not, see . */ -#ifndef MIR_GRAPHICS_GBM_BYPASS_H_ -#define MIR_GRAPHICS_GBM_BYPASS_H_ +#ifndef MIR_GRAPHICS_GBM_ATOMIC_KMS_BYPASS_H_ +#define MIR_GRAPHICS_GBM_ATOMIC_KMS_BYPASS_H_ #include "mir/graphics/renderable.h" @@ -41,4 +41,4 @@ class BypassMatch } // namespace graphics } // namespace mir -#endif // MIR_GRAPHICS_GBM_BYPASS_H_ +#endif // MIR_GRAPHICS_GBM_ATOMIC_KMS_BYPASS_H_ diff --git a/src/platforms/atomic-kms/server/kms/display.h b/src/platforms/atomic-kms/server/kms/display.h index ba0961a0606..d99fb417dc7 100644 --- a/src/platforms/atomic-kms/server/kms/display.h +++ b/src/platforms/atomic-kms/server/kms/display.h @@ -14,8 +14,8 @@ * along with this program. If not, see . */ -#ifndef MIR_GRAPHICS_GBM_DISPLAY_H_ -#define MIR_GRAPHICS_GBM_DISPLAY_H_ +#ifndef MIR_GRAPHICS_GBM_ATOMIC_KMS_DISPLAY_H_ +#define MIR_GRAPHICS_GBM_ATOMIC_KMS_DISPLAY_H_ #include "kms-utils/drm_event_handler.h" #include "mir/graphics/display.h" @@ -128,4 +128,4 @@ class GBMDisplayProvider : public graphics::GBMDisplayProvider } } -#endif /* MIR_GRAPHICS_GBM_DISPLAY_H_ */ +#endif /* MIR_GRAPHICS_GBM_ATOMIC_KMS_DISPLAY_H_ */ diff --git a/src/platforms/atomic-kms/server/kms/display_sink.h b/src/platforms/atomic-kms/server/kms/display_sink.h index 359637c1164..01dc0a08761 100644 --- a/src/platforms/atomic-kms/server/kms/display_sink.h +++ b/src/platforms/atomic-kms/server/kms/display_sink.h @@ -14,8 +14,8 @@ * along with this program. If not, see . */ -#ifndef MIR_GRAPHICS_ATOMIC_DISPLAY_SINK_H_ -#define MIR_GRAPHICS_ATOMIC_DISPLAY_SINK_H_ +#ifndef MIR_GRAPHICS_GBM_ATOMIC_KMS_DISPLAY_SINK_H_ +#define MIR_GRAPHICS_GBM_ATOMIC_KMS_DISPLAY_SINK_H_ #include "kms-utils/drm_event_handler.h" #include "mir/graphics/display_sink.h" @@ -123,4 +123,4 @@ class DisplaySink : public graphics::DisplaySink, } } -#endif /* MIR_GRAPHICS_ATOMIC_DISPLAY_SINK_H_ */ +#endif /* MIR_GRAPHICS_GBM_ATOMIC_KMS_DISPLAY_SINK_H_ */ diff --git a/src/platforms/atomic-kms/server/kms/egl_helper.h b/src/platforms/atomic-kms/server/kms/egl_helper.h index ea4468fbd80..4c55f9057a3 100644 --- a/src/platforms/atomic-kms/server/kms/egl_helper.h +++ b/src/platforms/atomic-kms/server/kms/egl_helper.h @@ -14,8 +14,8 @@ * along with this program. If not, see . */ -#ifndef MIR_GRAPHICS_GBM_EGL_HELPER_H_ -#define MIR_GRAPHICS_GBM_EGL_HELPER_H_ +#ifndef MIR_GRAPHICS_GBM_ATOMIC_KMS_EGL_HELPER_H_ +#define MIR_GRAPHICS_GBM_ATOMIC_KMS_EGL_HELPER_H_ #include "../display_helpers.h" #include "mir/graphics/egl_extensions.h" @@ -84,4 +84,4 @@ class EGLHelper } } -#endif /* MIR_GRAPHICS_GBM_EGL_HELPER_H_ */ +#endif /* MIR_GRAPHICS_GBM_ATOMIC_KMS_EGL_HELPER_H_ */ diff --git a/src/platforms/atomic-kms/server/kms/kms_display_configuration.h b/src/platforms/atomic-kms/server/kms/kms_display_configuration.h index 03ead845579..a0c42b8a913 100644 --- a/src/platforms/atomic-kms/server/kms/kms_display_configuration.h +++ b/src/platforms/atomic-kms/server/kms/kms_display_configuration.h @@ -14,8 +14,8 @@ * along with this program. If not, see . */ -#ifndef MIR_GRAPHICS_GBM_KMS_DISPLAY_CONFIGURATION_H_ -#define MIR_GRAPHICS_GBM_KMS_DISPLAY_CONFIGURATION_H_ +#ifndef MIR_GRAPHICS_GBM_ATOMIC_KMS_DISPLAY_CONFIGURATION_H_ +#define MIR_GRAPHICS_GBM_ATOMIC_KMS_DISPLAY_CONFIGURATION_H_ #include "mir/graphics/display_configuration.h" #include @@ -44,4 +44,4 @@ class KMSDisplayConfiguration : public DisplayConfiguration } } -#endif /* MIR_GRAPHICS_GBM_KMS_DISPLAY_CONFIGURATION_H_ */ +#endif /* MIR_GRAPHICS_GBM_ATOMIC_KMS_DISPLAY_CONFIGURATION_H_ */ diff --git a/src/platforms/atomic-kms/server/kms/kms_output.h b/src/platforms/atomic-kms/server/kms/kms_output.h index 6772556d240..aee367f93b6 100644 --- a/src/platforms/atomic-kms/server/kms/kms_output.h +++ b/src/platforms/atomic-kms/server/kms/kms_output.h @@ -14,8 +14,8 @@ * along with this program. If not, see . */ -#ifndef MIR_GRAPHICS_GBM_KMS_OUTPUT_H_ -#define MIR_GRAPHICS_GBM_KMS_OUTPUT_H_ +#ifndef MIR_GRAPHICS_GBM_ATOMIC_KMS_OUTPUT_H_ +#define MIR_GRAPHICS_GBM_ATOMIC_KMS_OUTPUT_H_ #include "mir/geometry/size.h" #include "mir/geometry/point.h" @@ -106,4 +106,4 @@ class KMSOutput } } -#endif /* MIR_GRAPHICS_GBM_KMS_OUTPUT_H_ */ +#endif /* MIR_GRAPHICS_GBM_ATOMIC_KMS_OUTPUT_H_ */ diff --git a/src/platforms/atomic-kms/server/kms/kms_output_container.h b/src/platforms/atomic-kms/server/kms/kms_output_container.h index 2d951cb56ab..3365d2df21b 100644 --- a/src/platforms/atomic-kms/server/kms/kms_output_container.h +++ b/src/platforms/atomic-kms/server/kms/kms_output_container.h @@ -14,8 +14,8 @@ * along with this program. If not, see . */ -#ifndef MIR_GRAPHICS_GBM_KMS_OUTPUT_CONTAINER_H_ -#define MIR_GRAPHICS_GBM_KMS_OUTPUT_CONTAINER_H_ +#ifndef MIR_GRAPHICS_GBM_ATOMIC_KMS_OUTPUT_CONTAINER_H_ +#define MIR_GRAPHICS_GBM_ATOMIC_KMS_OUTPUT_CONTAINER_H_ #include #include @@ -50,4 +50,4 @@ class KMSOutputContainer } } -#endif /* MIR_GRAPHICS_GBM_KMS_OUTPUT_CONTAINER_H_ */ +#endif /* MIR_GRAPHICS_GBM_ATOMIC_KMS_OUTPUT_CONTAINER_H_ */ diff --git a/src/platforms/atomic-kms/server/kms/kms_page_flipper.h b/src/platforms/atomic-kms/server/kms/kms_page_flipper.h index 4cd287ab6f3..048dccd5079 100644 --- a/src/platforms/atomic-kms/server/kms/kms_page_flipper.h +++ b/src/platforms/atomic-kms/server/kms/kms_page_flipper.h @@ -14,8 +14,8 @@ * along with this program. If not, see . */ -#ifndef MIR_GRAPHICS_GBM_KMS_PAGE_FLIPPER_H_ -#define MIR_GRAPHICS_GBM_KMS_PAGE_FLIPPER_H_ +#ifndef MIR_GRAPHICS_GBM_ATOMIC_KMS_PAGE_FLIPPER_H_ +#define MIR_GRAPHICS_GBM_ATOMIC_KMS_PAGE_FLIPPER_H_ #include "page_flipper.h" @@ -73,4 +73,4 @@ class KMSPageFlipper : public PageFlipper } } -#endif /* MIR_GRAPHICS_GBM_KMS_PAGE_FLIPPER_H_ */ +#endif /* MIR_GRAPHICS_GBM_ATOMIC_KMS_PAGE_FLIPPER_H_ */ diff --git a/src/platforms/atomic-kms/server/kms/quirks.h b/src/platforms/atomic-kms/server/kms/quirks.h index 0e3427c4d72..6bd592b1bd9 100644 --- a/src/platforms/atomic-kms/server/kms/quirks.h +++ b/src/platforms/atomic-kms/server/kms/quirks.h @@ -14,8 +14,8 @@ * along with this program. If not, see . */ -#ifndef MIR_GRAPHICS_GBM_KMS_QUIRKS_H_ -#define MIR_GRAPHICS_GBM_KMS_QUIRKS_H_ +#ifndef MIR_GRAPHICS_GBM_ATOMIC_KMS_QUIRKS_H_ +#define MIR_GRAPHICS_GBM_ATOMIC_KMS_QUIRKS_H_ #include #include @@ -64,4 +64,4 @@ class Quirks } -#endif //MIR_GRAPHICS_GBM_KMS_QUIRKS_H_ +#endif //MIR_GRAPHICS_GBM_ATOMIC_KMS_QUIRKS_H_ diff --git a/src/platforms/atomic-kms/server/kms/real_kms_display_configuration.h b/src/platforms/atomic-kms/server/kms/real_kms_display_configuration.h index b58c37d5c26..b4967a1d988 100644 --- a/src/platforms/atomic-kms/server/kms/real_kms_display_configuration.h +++ b/src/platforms/atomic-kms/server/kms/real_kms_display_configuration.h @@ -14,8 +14,8 @@ * along with this program. If not, see . */ -#ifndef MIR_GRAPHICS_ATOMIC_REAL_KMS_DISPLAY_CONFIGURATION_H_ -#define MIR_GRAPHICS_ATOMIC_REAL_KMS_DISPLAY_CONFIGURATION_H_ +#ifndef MIR_GRAPHICS_GBM_ATOMIC_KMS_REALDISPLAY_CONFIGURATION_H_ +#define MIR_GRAPHICS_GBM_ATOMIC_KMS_REALDISPLAY_CONFIGURATION_H_ #include "kms_display_configuration.h" @@ -62,4 +62,4 @@ bool compatible(RealKMSDisplayConfiguration const& conf1, RealKMSDisplayConfigur } } -#endif /* MIR_GRAPHICS_ATOMIC_REAL_KMS_DISPLAY_CONFIGURATION_H_ */ +#endif /* MIR_GRAPHICS_GBM_ATOMIC_KMS_REALDISPLAY_CONFIGURATION_H_ */ diff --git a/src/platforms/atomic-kms/server/kms/real_kms_output_container.h b/src/platforms/atomic-kms/server/kms/real_kms_output_container.h index 97641bd876e..b6a6d67e15f 100644 --- a/src/platforms/atomic-kms/server/kms/real_kms_output_container.h +++ b/src/platforms/atomic-kms/server/kms/real_kms_output_container.h @@ -14,8 +14,8 @@ * along with this program. If not, see . */ -#ifndef MIR_GRAPHICS_GBM_REAL_KMS_OUTPUT_CONTAINER_H_ -#define MIR_GRAPHICS_GBM_REAL_KMS_OUTPUT_CONTAINER_H_ +#ifndef MIR_GRAPHICS_GBM_ATOMIC_KMS_REAL_OUTPUT_CONTAINER_H_ +#define MIR_GRAPHICS_GBM_ATOMIC_KMS_REAL_OUTPUT_CONTAINER_H_ #include "kms_output_container.h" #include "mir/fd.h" @@ -51,4 +51,4 @@ class RealKMSOutputContainer : public KMSOutputContainer } } -#endif /* MIR_GRAPHICS_GBM_REAL_KMS_OUTPUT_CONTAINER_H_ */ +#endif /* MIR_GRAPHICS_GBM_ATOMIC_KMS_REAL_OUTPUT_CONTAINER_H_ */ From b8479940b7e2d0e6d254452fa0e0f190c1a02ccf Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Wed, 23 Oct 2024 11:28:44 +0200 Subject: [PATCH 14/27] Naked #include --- src/platforms/atomic-kms/server/display_helpers.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/platforms/atomic-kms/server/display_helpers.h b/src/platforms/atomic-kms/server/display_helpers.h index e4cb134e54b..e381f06d416 100644 --- a/src/platforms/atomic-kms/server/display_helpers.h +++ b/src/platforms/atomic-kms/server/display_helpers.h @@ -24,10 +24,7 @@ #include #include -#pragma GCC diagnostic push -#pragma GCC diagnostic warning "-Wall" #include -#pragma GCC diagnostic pop #include From 2f539f96dd8c565b399fae9d3ab3f5205251c965 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Wed, 23 Oct 2024 11:36:24 +0200 Subject: [PATCH 15/27] Rationalise DMIR_LOG_COMPONENT --- src/platforms/atomic-kms/CMakeLists.txt | 1 + src/platforms/atomic-kms/server/display_helpers.cpp | 1 - src/platforms/atomic-kms/server/kms/CMakeLists.txt | 1 - src/platforms/atomic-kms/server/kms/display.cpp | 1 - src/platforms/atomic-kms/server/kms/egl_helper.cpp | 1 - src/platforms/atomic-kms/server/kms/platform.cpp | 1 - src/platforms/atomic-kms/server/kms/platform_symbols.cpp | 1 - 7 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/platforms/atomic-kms/CMakeLists.txt b/src/platforms/atomic-kms/CMakeLists.txt index f510a73aec3..55956947374 100644 --- a/src/platforms/atomic-kms/CMakeLists.txt +++ b/src/platforms/atomic-kms/CMakeLists.txt @@ -1 +1,2 @@ +add_definitions(-DMIR_LOG_COMPONENT_FALLBACK="atomic-kms") add_subdirectory(server/) diff --git a/src/platforms/atomic-kms/server/display_helpers.cpp b/src/platforms/atomic-kms/server/display_helpers.cpp index ea4d29655a3..42eaaa05e86 100644 --- a/src/platforms/atomic-kms/server/display_helpers.cpp +++ b/src/platforms/atomic-kms/server/display_helpers.cpp @@ -26,7 +26,6 @@ #include -#define MIR_LOG_COMPONENT "atomic-kms" #include "mir/log.h" #include diff --git a/src/platforms/atomic-kms/server/kms/CMakeLists.txt b/src/platforms/atomic-kms/server/kms/CMakeLists.txt index 537fe92599a..7cce061b31e 100644 --- a/src/platforms/atomic-kms/server/kms/CMakeLists.txt +++ b/src/platforms/atomic-kms/server/kms/CMakeLists.txt @@ -13,7 +13,6 @@ string(REPLACE "-pedantic" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) add_compile_definitions( __GBM__ - MIR_LOG_COMPONENT_FALLBACK="atomic-kms" ) add_library( diff --git a/src/platforms/atomic-kms/server/kms/display.cpp b/src/platforms/atomic-kms/server/kms/display.cpp index 3e5d962860f..377e3249df0 100644 --- a/src/platforms/atomic-kms/server/kms/display.cpp +++ b/src/platforms/atomic-kms/server/kms/display.cpp @@ -42,7 +42,6 @@ #include #include #include -#define MIR_LOG_COMPONENT "atomic-kms" #include "mir/log.h" #include "kms-utils/drm_mode_resources.h" #include "kms-utils/kms_connector.h" diff --git a/src/platforms/atomic-kms/server/kms/egl_helper.cpp b/src/platforms/atomic-kms/server/kms/egl_helper.cpp index b8442eae022..ea7f48f96f2 100644 --- a/src/platforms/atomic-kms/server/kms/egl_helper.cpp +++ b/src/platforms/atomic-kms/server/kms/egl_helper.cpp @@ -21,7 +21,6 @@ #include #include -#define MIR_LOG_COMPONENT "EGL" #include "mir/log.h" namespace mg = mir::graphics; diff --git a/src/platforms/atomic-kms/server/kms/platform.cpp b/src/platforms/atomic-kms/server/kms/platform.cpp index 4b4578fd045..9e6e86d7af3 100644 --- a/src/platforms/atomic-kms/server/kms/platform.cpp +++ b/src/platforms/atomic-kms/server/kms/platform.cpp @@ -25,7 +25,6 @@ #include #include -#define MIR_LOG_COMPONENT "atomic-kms" #include "mir/log.h" namespace mg = mir::graphics; diff --git a/src/platforms/atomic-kms/server/kms/platform_symbols.cpp b/src/platforms/atomic-kms/server/kms/platform_symbols.cpp index fbc4c07513b..5604684babc 100644 --- a/src/platforms/atomic-kms/server/kms/platform_symbols.cpp +++ b/src/platforms/atomic-kms/server/kms/platform_symbols.cpp @@ -16,7 +16,6 @@ #include "mir/graphics/platform.h" #include -#define MIR_LOG_COMPONENT "atomic-kms" #include "mir/log.h" #include "platform.h" From 484154f5d448e97ae39bdee93797ac6af85bce64 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Wed, 23 Oct 2024 11:49:12 +0200 Subject: [PATCH 16/27] Use std::tie to compare structs by member --- .../atomic-kms/server/kms/atomic_kms_output.cpp | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp index cd70ab5ea71..275aa7d320c 100644 --- a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp +++ b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp @@ -40,22 +40,13 @@ namespace mga = mg::atomic; namespace mgk = mg::kms; namespace geom = mir::geometry; - namespace { bool kms_modes_are_equal(drmModeModeInfo const* info1, drmModeModeInfo const* info2) { return (info1 && info2) && - info1->clock == info2->clock && - info1->hdisplay == info2->hdisplay && - info1->hsync_start == info2->hsync_start && - info1->hsync_end == info2->hsync_end && - info1->htotal == info2->htotal && - info1->hskew == info2->hskew && - info1->vdisplay == info2->vdisplay && - info1->vsync_start == info2->vsync_start && - info1->vsync_end == info2->vsync_end && - info1->vtotal == info2->vtotal; + std::tie(info1->clock, info1->hdisplay, info1->hsync_start, info1->hsync_end, info1->htotal, info1->hskew, info1->vdisplay, info1->vsync_start, info1->vsync_end, info1->vtotal) == + std::tie(info2->clock, info2->hdisplay, info2->hsync_start, info2->hsync_end, info2->htotal, info2->hskew, info2->vdisplay, info2->vsync_start, info2->vsync_end, info2->vtotal); } uint32_t create_blob_returning_handle(mir::Fd const& drm_fd, void const* data, size_t len) From e9e94f9492552ad7992ed4e641bbb9941a17be45 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Wed, 23 Oct 2024 11:53:01 +0200 Subject: [PATCH 17/27] Inline pointless variable --- src/platforms/atomic-kms/server/gbm_display_allocator.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/platforms/atomic-kms/server/gbm_display_allocator.cpp b/src/platforms/atomic-kms/server/gbm_display_allocator.cpp index 4e515b798ad..6f2148c98f4 100644 --- a/src/platforms/atomic-kms/server/gbm_display_allocator.cpp +++ b/src/platforms/atomic-kms/server/gbm_display_allocator.cpp @@ -122,12 +122,11 @@ auto create_gbm_surface(gbm_device* gbm, geom::Size size, mg::DRMFormat format, if (modifiers.empty()) { // If we have no no modifiers don't use the with-modifiers creation path. - auto foo = GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT; return gbm_surface_create( gbm, size.width.as_uint32_t(), size.height.as_uint32_t(), format, - foo); + GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT); } else { From 3ffc1e297c94b4e9edf94967dc4752dc3bcba06c Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Wed, 23 Oct 2024 11:58:52 +0200 Subject: [PATCH 18/27] Clearer memory management --- .../atomic-kms/server/gbm_display_allocator.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/platforms/atomic-kms/server/gbm_display_allocator.cpp b/src/platforms/atomic-kms/server/gbm_display_allocator.cpp index 6f2148c98f4..97068704d27 100644 --- a/src/platforms/atomic-kms/server/gbm_display_allocator.cpp +++ b/src/platforms/atomic-kms/server/gbm_display_allocator.cpp @@ -58,7 +58,7 @@ class GBMBoFramebuffer : public mg::FBHandle return std::unique_ptr{new GBMBoFramebuffer{std::move(bo), *cached_fb}}; } - auto fb_id = new std::shared_ptr{ + auto fb_id = std::shared_ptr{ new uint32_t{0}, [drm_fd](uint32_t* fb_id) { @@ -79,13 +79,15 @@ class GBMBoFramebuffer : public mg::FBHandle /* Create a KMS FB object with the gbm_bo attached to it. */ auto ret = drmModeAddFB2(drm_fd, width, height, format, - handles, strides, offsets, fb_id->get(), 0); + handles, strides, offsets, fb_id.get(), 0); if (ret) return nullptr; - gbm_bo_set_user_data(bo.get(), fb_id, [](gbm_bo*, void* fb_ptr) { delete static_cast*>(fb_ptr); }); + // It is weird allocating a smart pointer on the heap, but we delete it + // via gbm_bo_set_user_data()'s destroy_user_data parameter. + gbm_bo_set_user_data(bo.get(), new std::shared_ptr(fb_id), [](gbm_bo*, void* fb_ptr) { delete static_cast*>(fb_ptr); }); - return std::unique_ptr{new GBMBoFramebuffer{std::move(bo), *fb_id}}; + return std::make_unique(std::move(bo), std::move(fb_id)); } operator uint32_t() const override @@ -100,13 +102,13 @@ class GBMBoFramebuffer : public mg::FBHandle gbm_bo_get_width(bo.get()), gbm_bo_get_height(bo.get())}; } -private: + GBMBoFramebuffer(LockedFrontBuffer bo, std::shared_ptr fb) : bo{std::move(bo)}, fb_id{std::move(fb)} { } - +private: LockedFrontBuffer const bo; std::shared_ptr const fb_id; }; From 72c7038fd294f2dbd75e52fe442ce515b6c6eb8d Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Wed, 23 Oct 2024 12:14:23 +0200 Subject: [PATCH 19/27] Insert egl version major and minor into exception --- src/platforms/atomic-kms/server/kms/egl_helper.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/platforms/atomic-kms/server/kms/egl_helper.cpp b/src/platforms/atomic-kms/server/kms/egl_helper.cpp index ea7f48f96f2..30359cd7532 100644 --- a/src/platforms/atomic-kms/server/kms/egl_helper.cpp +++ b/src/platforms/atomic-kms/server/kms/egl_helper.cpp @@ -15,13 +15,16 @@ */ #include "egl_helper.h" + #include "mir/graphics/gl_config.h" #include "mir/graphics/egl_error.h" +#include "mir/log.h" + #include #include -#include -#include "mir/log.h" +#include +#include namespace mg = mir::graphics; namespace mgmh = mir::graphics::atomic::helpers; @@ -74,8 +77,7 @@ void initialise_egl(EGLDisplay dpy, int minimum_major_version, int minimum_minor (major == minimum_major_version && minor < minimum_minor_version)) { BOOST_THROW_EXCEPTION( - boost::enable_error_info(std::runtime_error("Incompatible EGL version"))); - // TODO: Insert egl version major and minor into exception + boost::enable_error_info(std::runtime_error(std::format("Incompatible EGL version: {} ({})", major, minor)))); } } From 8082102461fe3ee0174ecce48f390898b90036b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Sawicz?= Date: Wed, 23 Oct 2024 13:44:13 +0200 Subject: [PATCH 20/27] debian: package atomic-kms (#3607) --- debian/control | 32 +++++++++++++++++++ ...mir-platform-graphics-atomic-kms22.install | 1 + debian/rules | 2 +- snap/snapcraft.yaml | 2 +- tools/update_package_abis.sh | 1 + 5 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 debian/mir-platform-graphics-atomic-kms22.install diff --git a/debian/control b/debian/control index 6b1b2841e48..5be4cee94af 100644 --- a/debian/control +++ b/debian/control @@ -297,6 +297,21 @@ Description: Display server for Ubuntu - platform library for X11 Contains the shared libraries required for the Mir server to interact with the X11 platform. +Package: mir-platform-graphics-atomic-kms22 +Section: libs +Architecture: linux-any +Multi-Arch: same +Pre-Depends: ${misc:Pre-Depends} +Depends: mir-platform-graphics-gbm-kms22, + ${misc:Depends}, + ${shlibs:Depends}, +Description: Display server for Ubuntu - platform library for Atomic KMS + Mir is a display server running on linux systems, with a focus on efficiency, + robust operation and a well-defined driver model. + . + Contains the shared libraries required for the Mir server to interact with + the hardware platform using the Mesa drivers and Atomic KMS API. + Package: mir-platform-graphics-gbm-kms22 Section: libs Architecture: linux-any @@ -404,6 +419,7 @@ Architecture: linux-any Multi-Arch: same Pre-Depends: ${misc:Pre-Depends} Depends: ${misc:Depends}, + mir-platform-graphics-atomic-kms, mir-platform-graphics-gbm-kms, mir-platform-graphics-x, mir-platform-graphics-wayland, @@ -416,6 +432,22 @@ Description: Display server for Ubuntu - desktop driver metapackage This package depends on a full set of graphics and input drivers for traditional desktop systems. +Package: mir-platform-graphics-atomic-kms +Section: libs +Architecture: linux-any +Multi-Arch: same +Pre-Depends: ${misc:Pre-Depends} +Depends: ${misc:Depends}, + mir-platform-graphics-atomic-kms22, + mir-platform-input-evdev10, + mir-platform-rendering-egl-generic, +Description: Display server for Ubuntu - gbm-kms driver metapackage + Mir is a display server running on linux systems, with a focus on efficiency, + robust operation and a well-defined driver model. + . + This package depends on a full set of graphics and input drivers for atomic-kms + systems. + Package: mir-platform-graphics-gbm-kms Section: libs Architecture: linux-any diff --git a/debian/mir-platform-graphics-atomic-kms22.install b/debian/mir-platform-graphics-atomic-kms22.install new file mode 100644 index 00000000000..be383231799 --- /dev/null +++ b/debian/mir-platform-graphics-atomic-kms22.install @@ -0,0 +1 @@ +usr/lib/*/mir/server-platform/graphics-atomic-kms.so.22 diff --git a/debian/rules b/debian/rules index 7d0e2472924..54340461efd 100755 --- a/debian/rules +++ b/debian/rules @@ -65,7 +65,7 @@ export DEB_BUILD_MAINT_OPTIONS $(info COMMON_CONFIGURE_OPTIONS: ${COMMON_CONFIGURE_OPTIONS}) $(info DEB_BUILD_MAINT_OPTIONS: ${DEB_BUILD_MAINT_OPTIONS}) -AVAILABLE_PLATFORMS=gbm-kms\;x11\;wayland\;eglstream-kms +AVAILABLE_PLATFORMS=atomic-kms\;gbm-kms\;x11\;wayland\;eglstream-kms override_dh_auto_configure: ifneq ($(filter armhf,$(DEB_HOST_ARCH)),) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 416ce428d94..d654b6ecf91 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -18,7 +18,7 @@ parts: cmake-parameters: - -DCMAKE_INSTALL_PREFIX=/usr - -DMIR_ENABLE_WLCS_TESTS=OFF - - -DMIR_PLATFORM='gbm-kms;eglstream-kms;x11;wayland' + - -DMIR_PLATFORM='atomic-kms;gbm-kms;eglstream-kms;x11;wayland' build-packages: - build-essential - eglexternalplatform-dev diff --git a/tools/update_package_abis.sh b/tools/update_package_abis.sh index 5a8231d1c2c..3e40c005f51 100755 --- a/tools/update_package_abis.sh +++ b/tools/update_package_abis.sh @@ -17,6 +17,7 @@ packages="\ libmirplatform:MIRPLATFORM_ABI \ libmirserver:MIRSERVER_ABI \ mir-platform-graphics-x:MIR_SERVER_GRAPHICS_PLATFORM_ABI \ + mir-platform-graphics-atomic-kms:MIR_SERVER_GRAPHICS_PLATFORM_ABI \ mir-platform-graphics-gbm-kms:MIR_SERVER_GRAPHICS_PLATFORM_ABI \ mir-platform-graphics-eglstream-kms:MIR_SERVER_GRAPHICS_PLATFORM_ABI \ mir-platform-input-evdev:MIR_SERVER_INPUT_PLATFORM_ABI\ From 1b0062ff8146a2a7c6d1363cf15d5e9dce0f7053 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 24 Oct 2024 11:23:53 +0200 Subject: [PATCH 21/27] Failing to get gamma curves shouldn't be fatal in `AtomicKMSOutput::update_from_hardware_state()` --- .../server/kms/atomic_kms_output.cpp | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp index 275aa7d320c..90a27de135a 100644 --- a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp +++ b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp @@ -756,18 +756,25 @@ void mga::AtomicKMSOutput::update_from_hardware_state( }(); GammaCurves gamma; - if (current_crtc && crtc_props->has_property("GAMMA_LUT") && crtc_props->has_property("GAMMA_LUT_SIZE")) - { - PropertyBlobData gamma_lut{drm_fd_, static_cast((*crtc_props)["GAMMA_LUT"])}; - auto const gamma_size = gamma_lut.data().size(); - gamma.red.reserve(gamma_size); - gamma.green.reserve(gamma_size); - gamma.blue.reserve(gamma_size); - for (auto const& entry : gamma_lut.data()) + if (connected && current_crtc && crtc_props->has_property("GAMMA_LUT") && crtc_props->has_property("GAMMA_LUT_SIZE")) + { + try + { + PropertyBlobData gamma_lut{drm_fd_, static_cast((*crtc_props)["GAMMA_LUT"])}; + auto const gamma_size = gamma_lut.data().size(); + gamma.red.reserve(gamma_size); + gamma.green.reserve(gamma_size); + gamma.blue.reserve(gamma_size); + for (auto const& entry : gamma_lut.data()) + { + gamma.red.push_back(entry.red); + gamma.green.push_back(entry.green); + gamma.blue.push_back(entry.blue); + } + } + catch (...) { - gamma.red.push_back(entry.red); - gamma.green.push_back(entry.green); - gamma.blue.push_back(entry.blue); + log(logging::Severity::warning, MIR_LOG_COMPONENT, std::current_exception(), "Failed to get gamma curves"); } } From a816adbb49357ece4d5c71ce40d12dac08461507 Mon Sep 17 00:00:00 2001 From: Christopher James Halse Rogers Date: Fri, 25 Oct 2024 10:38:37 +1100 Subject: [PATCH 22/27] platforms/atomic-kms: Don't crash on pageflip failure. --- src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp index 90a27de135a..b54a05d84e2 100644 --- a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp +++ b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp @@ -426,8 +426,8 @@ bool mga::AtomicKMSOutput::schedule_page_flip(FBHandle const& fb) if (ret) { mir::log_error("Failed to schedule page flip: %s (%i)", strerror(-ret), -ret); - current_crtc = nullptr; event_handler->cancel_flip_events(current_crtc->crtc_id); + current_crtc = nullptr; return false; } From 75910a62ecb38b5f45b793625515651e7055717c Mon Sep 17 00:00:00 2001 From: Christopher James Halse Rogers Date: Fri, 25 Oct 2024 16:02:24 +1100 Subject: [PATCH 23/27] platforms/atomic-kms: Synchronise access to CRTC configuration. *Almost* all of this is only accessed on a single thread, the compositor's submission thread. *Almost*. Unfortunately, `configure` is generally called from the ServerAction loop, which is a different thread to the composition thread. `ensure_crtc` can also (transitively) be called from `configuration`, which also happens off-composition-thread. Wrap this ball of wax up in a `mir::Synchronised<>`, to ensure we're not racy. (This also affects `gbm-kms`, but to a much lesser extent) --- .../server/kms/atomic_kms_output.cpp | 214 +++++++++++------- .../atomic-kms/server/kms/atomic_kms_output.h | 30 ++- 2 files changed, 147 insertions(+), 97 deletions(-) diff --git a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp index b54a05d84e2..ef86b646f3c 100644 --- a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp +++ b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp @@ -177,15 +177,32 @@ class mga::AtomicKMSOutput::PropertyBlob mir::Fd const drm_fd; }; +/* Note: + * (At least) Clang includes -Wmissing-designated-field-initializers to -Wextra + * This (questionably?) warns when using designated field initialisers but + * not explicitly initialising every field member. + * (The standard says anything not specified is ~default-initialised) + * + * That's why the Configuration initialiser contains so many default values here + */ mga::AtomicKMSOutput::AtomicKMSOutput( mir::Fd drm_master, kms::DRMModeConnectorUPtr connector, std::shared_ptr event_handler) : drm_fd_{drm_master}, event_handler{std::move(event_handler)}, - connector{std::move(connector)}, - mode_index{0}, - current_crtc(), + configuration{ + Configuration { + .connector = std::move(connector), + .mode_index = 0, + .fb_offset = {}, + .current_crtc = nullptr, + .current_plane = nullptr, + .mode = nullptr, + .crtc_props = nullptr, + .plane_props = nullptr, + .connector_props = nullptr + }}, saved_crtc(), using_saved_crtc{true} { @@ -193,9 +210,10 @@ mga::AtomicKMSOutput::AtomicKMSOutput( kms::DRMModeResources resources{drm_fd_}; - if (this->connector->encoder_id) + auto conf = configuration.lock(); + if (conf->connector->encoder_id) { - auto encoder = resources.encoder(this->connector->encoder_id); + auto encoder = resources.encoder(conf->connector->encoder_id); if (encoder->crtc_id) { saved_crtc = *resources.crtc(encoder->crtc_id); @@ -210,7 +228,7 @@ mga::AtomicKMSOutput::~AtomicKMSOutput() uint32_t mga::AtomicKMSOutput::id() const { - return connector->connector_id; + return configuration.lock()->connector->connector_id; } void mga::AtomicKMSOutput::reset() @@ -218,10 +236,11 @@ void mga::AtomicKMSOutput::reset() kms::DRMModeResources resources{drm_fd_}; /* Update the connector to ensure we have the latest information */ + auto conf = configuration.lock(); try { - connector = resources.connector(connector->connector_id); - connector_props = std::make_unique(drm_fd_, connector); + conf->connector = resources.connector(conf->connector->connector_id); + conf->connector_props = std::make_unique(drm_fd_, conf->connector); } catch (std::exception const& e) { @@ -229,81 +248,88 @@ void mga::AtomicKMSOutput::reset() } /* Discard previously current crtc */ - current_crtc = nullptr; + conf->current_crtc = nullptr; } geom::Size mga::AtomicKMSOutput::size() const { + auto conf = configuration.lock(); // Disconnected hardware has no modes: invent a size - if (connector->connection == DRM_MODE_DISCONNECTED) + if (conf->connector->connection == DRM_MODE_DISCONNECTED) return {0, 0}; - drmModeModeInfo const& mode(connector->modes[mode_index]); + drmModeModeInfo const& mode(conf->connector->modes[conf->mode_index]); return {mode.hdisplay, mode.vdisplay}; } int mga::AtomicKMSOutput::max_refresh_rate() const { - if (connector->connection == DRM_MODE_DISCONNECTED) + auto conf = configuration.lock(); + if (conf->connector->connection == DRM_MODE_DISCONNECTED) return 1; - drmModeModeInfo const& current_mode = connector->modes[mode_index]; + drmModeModeInfo const& current_mode = conf->connector->modes[conf->mode_index]; return current_mode.vrefresh; } void mga::AtomicKMSOutput::configure(geom::Displacement offset, size_t kms_mode_index) { - fb_offset = offset; - mode_index = kms_mode_index; - mode = std::make_unique(drm_fd_, &connector->modes[mode_index], sizeof(connector->modes[mode_index])); - ensure_crtc(); + auto conf = configuration.lock(); + conf->fb_offset = offset; + conf->mode_index = kms_mode_index; + conf->mode = std::make_unique( + drm_fd_, + &conf->connector->modes[conf->mode_index], + sizeof(conf->connector->modes[conf->mode_index])); + ensure_crtc(*conf); } bool mga::AtomicKMSOutput::set_crtc(FBHandle const& fb) { - if (!ensure_crtc()) + auto conf = configuration.lock(); + if (!ensure_crtc(*conf)) { mir::log_error("Output %s has no associated CRTC to set a framebuffer on", - mgk::connector_name(connector).c_str()); + mgk::connector_name(conf->connector).c_str()); return false; } /* We use the *requested* mode rather than the current_crtc * because we might have been asked to perform a modeset */ - auto const width = connector->modes[mode_index].hdisplay; - auto const height = connector->modes[mode_index].vdisplay; + auto const width = conf->connector->modes[conf->mode_index].hdisplay; + auto const height = conf->connector->modes[conf->mode_index].vdisplay; AtomicUpdate update; - update.add_property(*crtc_props, "MODE_ID", mode->handle()); - update.add_property(*connector_props, "CRTC_ID", current_crtc->crtc_id); + update.add_property(*conf->crtc_props, "MODE_ID", conf->mode->handle()); + update.add_property(*conf->connector_props, "CRTC_ID", conf->current_crtc->crtc_id); /* Source viewport. Coordinates are 16.16 fixed point format */ - update.add_property(*plane_props, "SRC_X", fb_offset.dx.as_uint32_t() << 16); - update.add_property(*plane_props, "SRC_Y", fb_offset.dy.as_uint32_t() << 16); - update.add_property(*plane_props, "SRC_W", width << 16); - update.add_property(*plane_props, "SRC_H", height << 16); + update.add_property(*conf->plane_props, "SRC_X", conf->fb_offset.dx.as_uint32_t() << 16); + update.add_property(*conf->plane_props, "SRC_Y", conf->fb_offset.dy.as_uint32_t() << 16); + update.add_property(*conf->plane_props, "SRC_W", width << 16); + update.add_property(*conf->plane_props, "SRC_H", height << 16); /* Destination viewport. Coordinates are *not* 16.16 */ - update.add_property(*plane_props, "CRTC_X", 0); - update.add_property(*plane_props, "CRTC_Y", 0); - update.add_property(*plane_props, "CRTC_W", width); - update.add_property(*plane_props, "CRTC_H", height); + update.add_property(*conf->plane_props, "CRTC_X", 0); + update.add_property(*conf->plane_props, "CRTC_Y", 0); + update.add_property(*conf->plane_props, "CRTC_W", width); + update.add_property(*conf->plane_props, "CRTC_H", height); /* Set a surface for the plane */ - update.add_property(*plane_props, "CRTC_ID", current_crtc->crtc_id); - update.add_property(*plane_props, "FB_ID", fb); + update.add_property(*conf->plane_props, "CRTC_ID", conf->current_crtc->crtc_id); + update.add_property(*conf->plane_props, "FB_ID", fb); auto ret = drmModeAtomicCommit(drm_fd_, update, DRM_MODE_ATOMIC_ALLOW_MODESET, nullptr); if (ret) { mir::log_error("Failed to set CRTC: %s (%i)", strerror(-ret), -ret); - current_crtc = nullptr; + conf->current_crtc = nullptr; return false; } // We might have performed a modeset; update our view of the hardware state accordingly! - current_crtc = mgk::get_crtc(drm_fd_, current_crtc->crtc_id); + conf->current_crtc = mgk::get_crtc(drm_fd_, conf->current_crtc->crtc_id); using_saved_crtc = false; return true; @@ -311,20 +337,22 @@ bool mga::AtomicKMSOutput::set_crtc(FBHandle const& fb) bool mga::AtomicKMSOutput::has_crtc_mismatch() { - if (!ensure_crtc()) + auto conf = configuration.lock(); + if (!ensure_crtc(*conf)) { - mir::log_error("Output %s has no associated CRTC to get ", mgk::connector_name(connector).c_str()); + mir::log_error("Output %s has no associated CRTC to get ", mgk::connector_name(conf->connector).c_str()); return true; } - return !kms_modes_are_equal(¤t_crtc->mode, &connector->modes[mode_index]); + return !kms_modes_are_equal(&conf->current_crtc->mode, &conf->connector->modes[conf->mode_index]); } void mga::AtomicKMSOutput::clear_crtc() { + auto conf = configuration.lock(); try { - ensure_crtc(); + ensure_crtc(*conf); } catch (...) { @@ -338,11 +366,11 @@ void mga::AtomicKMSOutput::clear_crtc() } AtomicUpdate update; - update.add_property(*connector_props, "CRTC_ID", 0); - update.add_property(*crtc_props, "ACTIVE", 0); - update.add_property(*crtc_props, "MODE_ID", 0); - update.add_property(*plane_props, "FB_ID", 0); - update.add_property(*plane_props, "CRTC_ID", 0); + update.add_property(*conf->connector_props, "CRTC_ID", 0); + update.add_property(*conf->crtc_props, "ACTIVE", 0); + update.add_property(*conf->crtc_props, "MODE_ID", 0); + update.add_property(*conf->plane_props, "FB_ID", 0); + update.add_property(*conf->plane_props, "CRTC_ID", 0); auto result = drmModeAtomicCommit(drm_fd_, update, DRM_MODE_ATOMIC_ALLOW_MODESET, nullptr); if (result) @@ -357,31 +385,32 @@ void mga::AtomicKMSOutput::clear_crtc() * Whatever we're switching to can handle the CRTCs; this should not be fatal. */ mir::log_info("Couldn't clear output %s (drmModeSetCrtc: %s (%i))", - mgk::connector_name(connector).c_str(), + mgk::connector_name(conf->connector).c_str(), strerror(-result), -result); } else { fatal_error("Couldn't clear output %s (drmModeSetCrtc = %d)", - mgk::connector_name(connector).c_str(), result); + mgk::connector_name(conf->connector).c_str(), result); } } - current_crtc = nullptr; + conf->current_crtc = nullptr; } bool mga::AtomicKMSOutput::schedule_page_flip(FBHandle const& fb) { - if (!ensure_crtc()) + auto conf = configuration.lock(); + if (!ensure_crtc(*conf)) { mir::log_error("Output %s has no associated CRTC to set a framebuffer on", - mgk::connector_name(connector).c_str()); + mgk::connector_name(conf->connector).c_str()); return false; } - auto const crtc_width = current_crtc->width; - auto const crtc_height = current_crtc->height; + auto const crtc_width = conf->current_crtc->width; + auto const crtc_height = conf->current_crtc->height; auto const fb_width = fb.size().width.as_uint32_t(); auto const fb_height = fb.size().height.as_uint32_t(); @@ -398,26 +427,26 @@ bool mga::AtomicKMSOutput::schedule_page_flip(FBHandle const& fb) } AtomicUpdate update; - update.add_property(*crtc_props, "MODE_ID", mode->handle()); - update.add_property(*connector_props, "CRTC_ID", current_crtc->crtc_id); + update.add_property(*conf->crtc_props, "MODE_ID", conf->mode->handle()); + update.add_property(*conf->connector_props, "CRTC_ID", conf->current_crtc->crtc_id); /* Source viewport. Coordinates are 16.16 fixed point format */ - update.add_property(*plane_props, "SRC_X", fb_offset.dx.as_uint32_t() << 16); - update.add_property(*plane_props, "SRC_Y", fb_offset.dy.as_uint32_t() << 16); - update.add_property(*plane_props, "SRC_W", fb_width << 16); - update.add_property(*plane_props, "SRC_H", fb_height << 16); + update.add_property(*conf->plane_props, "SRC_X", conf->fb_offset.dx.as_uint32_t() << 16); + update.add_property(*conf->plane_props, "SRC_Y", conf->fb_offset.dy.as_uint32_t() << 16); + update.add_property(*conf->plane_props, "SRC_W", fb_width << 16); + update.add_property(*conf->plane_props, "SRC_H", fb_height << 16); /* Destination viewport. Coordinates are *not* 16.16 */ - update.add_property(*plane_props, "CRTC_X", 0); - update.add_property(*plane_props, "CRTC_Y", 0); - update.add_property(*plane_props, "CRTC_W", crtc_width); - update.add_property(*plane_props, "CRTC_H", crtc_height); + update.add_property(*conf->plane_props, "CRTC_X", 0); + update.add_property(*conf->plane_props, "CRTC_Y", 0); + update.add_property(*conf->plane_props, "CRTC_W", crtc_width); + update.add_property(*conf->plane_props, "CRTC_H", crtc_height); /* Set a surface for the plane */ - update.add_property(*plane_props, "CRTC_ID", current_crtc->crtc_id); - update.add_property(*plane_props, "FB_ID", fb); + update.add_property(*conf->plane_props, "CRTC_ID", conf->current_crtc->crtc_id); + update.add_property(*conf->plane_props, "FB_ID", fb); - pending_page_flip = event_handler->expect_flip_event(current_crtc->crtc_id, [](auto, auto){}); + pending_page_flip = event_handler->expect_flip_event(conf->current_crtc->crtc_id, [](auto, auto){}); auto ret = drmModeAtomicCommit( drm_fd_, update, @@ -426,8 +455,8 @@ bool mga::AtomicKMSOutput::schedule_page_flip(FBHandle const& fb) if (ret) { mir::log_error("Failed to schedule page flip: %s (%i)", strerror(-ret), -ret); - event_handler->cancel_flip_events(current_crtc->crtc_id); - current_crtc = nullptr; + event_handler->cancel_flip_events(conf->current_crtc->crtc_id); + conf->current_crtc = nullptr; return false; } @@ -459,33 +488,39 @@ bool mga::AtomicKMSOutput::has_cursor() const return false; } -bool mga::AtomicKMSOutput::ensure_crtc() +bool mga::AtomicKMSOutput::ensure_crtc(Configuration& to_update) { /* Nothing to do if we already have a crtc */ - if (current_crtc) + if (to_update.current_crtc) return true; /* If the output is not connected there is nothing to do */ - if (connector->connection != DRM_MODE_CONNECTED) + if (to_update.connector->connection != DRM_MODE_CONNECTED) return false; // Update the connector as we may unexpectedly fail in find_crtc_and_index_for_connector() // https://github.com/MirServer/mir/issues/2661 - connector = kms::get_connector(drm_fd_, connector->connector_id); - std::tie(current_crtc, current_plane) = mgk::find_crtc_with_primary_plane(drm_fd_, connector); - crtc_props = std::make_unique(drm_fd_, current_crtc); - plane_props = std::make_unique(drm_fd_, current_plane); + to_update.connector = kms::get_connector(drm_fd_, to_update.connector->connector_id); + std::tie(to_update.current_crtc, to_update.current_plane) = mgk::find_crtc_with_primary_plane(drm_fd_, to_update.connector); + if (!to_update.current_crtc || !to_update.current_plane) + { + return false; + } + + to_update.crtc_props = std::make_unique(drm_fd_, to_update.current_crtc); + to_update.plane_props = std::make_unique(drm_fd_, to_update.current_plane); - return (current_crtc != nullptr); + return true; } void mga::AtomicKMSOutput::restore_saved_crtc() { + auto conf = configuration.lock(); if (!using_saved_crtc) { drmModeSetCrtc(drm_fd_, saved_crtc.crtc_id, saved_crtc.buffer_id, saved_crtc.x, saved_crtc.y, - &connector->connector_id, 1, &saved_crtc.mode); + &conf->connector->connector_id, 1, &saved_crtc.mode); using_saved_crtc = true; } @@ -495,10 +530,11 @@ void mga::AtomicKMSOutput::set_power_mode(MirPowerMode mode) { bool should_be_active = mode == mir_power_mode_on; - if (current_crtc) + auto conf = configuration.lock(); + if (conf->current_crtc) { AtomicUpdate update; - update.add_property(*crtc_props, "ACTIVE", should_be_active); + update.add_property(*conf->crtc_props, "ACTIVE", should_be_active); drmModeAtomicCommit(drm_fd_, update, DRM_MODE_ATOMIC_ALLOW_MODESET, nullptr); } else if (should_be_active) @@ -516,10 +552,11 @@ void mga::AtomicKMSOutput::set_gamma(mg::GammaCurves const& gamma) return; } - if (!ensure_crtc()) + auto conf = configuration.lock(); + if (!ensure_crtc(*conf)) { mir::log_warning("Output %s has no associated CRTC to set gamma on", - mgk::connector_name(connector).c_str()); + mgk::connector_name(conf->connector).c_str()); return; } @@ -540,22 +577,23 @@ void mga::AtomicKMSOutput::set_gamma(mg::GammaCurves const& gamma) PropertyBlob lut{drm_fd_, drm_lut.get(), sizeof(struct drm_color_lut) * gamma.red.size()}; AtomicUpdate update; - update.add_property(*crtc_props, "GAMMA_LUT", lut.handle()); + update.add_property(*conf->crtc_props, "GAMMA_LUT", lut.handle()); drmModeAtomicCommit(drm_fd(), update, DRM_MODE_ATOMIC_ALLOW_MODESET, nullptr); } void mga::AtomicKMSOutput::refresh_hardware_state() { - connector = kms::get_connector(drm_fd_, connector->connector_id); - current_crtc = nullptr; + auto conf = configuration.lock(); + conf->connector = kms::get_connector(drm_fd_, conf->connector->connector_id); + conf->current_crtc = nullptr; - if (connector->encoder_id) + if (conf->connector->encoder_id) { - auto encoder = kms::get_encoder(drm_fd_, connector->encoder_id); + auto encoder = kms::get_encoder(drm_fd_, conf->connector->encoder_id); if (encoder->crtc_id) { - current_crtc = kms::get_crtc(drm_fd_, encoder->crtc_id); + conf->current_crtc = kms::get_crtc(drm_fd_, encoder->crtc_id); } } } @@ -716,6 +754,12 @@ auto formats_for_output(mir::Fd const& drm_fd, mgk::DRMModeConnectorUPtr const& void mga::AtomicKMSOutput::update_from_hardware_state( DisplayConfigurationOutput& output) const { + auto conf = configuration.lock(); + + auto const& connector = conf->connector; + auto const& current_crtc = conf->current_crtc; + auto const& crtc_props = conf->crtc_props; + DisplayConfigurationOutputType const type{ kms_connector_type_to_output_type(connector->connector_type)}; geom::Size physical_size{connector->mmWidth, connector->mmHeight}; diff --git a/src/platforms/atomic-kms/server/kms/atomic_kms_output.h b/src/platforms/atomic-kms/server/kms/atomic_kms_output.h index d67f999ea59..58d2e806a4d 100644 --- a/src/platforms/atomic-kms/server/kms/atomic_kms_output.h +++ b/src/platforms/atomic-kms/server/kms/atomic_kms_output.h @@ -20,6 +20,7 @@ #include "kms_output.h" #include "kms-utils/drm_mode_resources.h" #include "mir/fd.h" +#include "mir/synchronised.h" #include #include @@ -73,7 +74,22 @@ class AtomicKMSOutput : public KMSOutput int drm_fd() const override; private: - bool ensure_crtc(); + class PropertyBlob; + + struct Configuration + { + kms::DRMModeConnectorUPtr connector; + size_t mode_index; + geometry::Displacement fb_offset; + kms::DRMModeCrtcUPtr current_crtc; + kms::DRMModePlaneUPtr current_plane; + std::unique_ptr mode; + std::unique_ptr crtc_props; + std::unique_ptr plane_props; + std::unique_ptr connector_props; + }; + + bool ensure_crtc(Configuration& to_update); void restore_saved_crtc(); mir::Fd const drm_fd_; @@ -81,17 +97,7 @@ class AtomicKMSOutput : public KMSOutput std::future pending_page_flip; - class PropertyBlob; - - kms::DRMModeConnectorUPtr connector; - size_t mode_index; - geometry::Displacement fb_offset; - kms::DRMModeCrtcUPtr current_crtc; - kms::DRMModePlaneUPtr current_plane; - std::unique_ptr mode; - std::unique_ptr crtc_props; - std::unique_ptr plane_props; - std::unique_ptr connector_props; + mir::Synchronised configuration; drmModeCrtc saved_crtc; bool using_saved_crtc; }; From ee57db39581e0e1c5c29d7e463368eab05f5f117 Mon Sep 17 00:00:00 2001 From: Christopher James Halse Rogers Date: Wed, 30 Oct 2024 12:21:26 +0100 Subject: [PATCH 24/27] platforms/atomic-kms: Clean up unused resources in `refresh_hardware_state` When we refresh the hardware state of each `KMSOutput` check whether it has resources currently assigned but is now not connected. If so, free the resources before continuing. --- .../atomic-kms/server/kms/atomic_kms_output.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp index ef86b646f3c..69db9d2fded 100644 --- a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp +++ b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp @@ -585,6 +585,22 @@ void mga::AtomicKMSOutput::refresh_hardware_state() { auto conf = configuration.lock(); conf->connector = kms::get_connector(drm_fd_, conf->connector->connector_id); + + if (conf->current_crtc && (conf->connector->connection != DRM_MODE_CONNECTED)) + { + AtomicUpdate update; + update.add_property(*conf->connector_props, "CRTC_ID", 0); + update.add_property(*conf->crtc_props, "ACTIVE", 0); + update.add_property(*conf->crtc_props, "MODE_ID", 0); + update.add_property(*conf->plane_props, "FB_ID", 0); + update.add_property(*conf->plane_props, "CRTC_ID", 0); + + drmModeAtomicCommit(drm_fd(), update, DRM_MODE_ATOMIC_ALLOW_MODESET, nullptr); + + conf->connector_props = nullptr; + conf->crtc_props = nullptr; + conf->plane_props = nullptr; + } conf->current_crtc = nullptr; if (conf->connector->encoder_id) From bec4b25cd6d941916f57b7f1157aad9dd7978285 Mon Sep 17 00:00:00 2001 From: Christopher James Halse Rogers Date: Thu, 7 Nov 2024 18:37:45 +1100 Subject: [PATCH 25/27] platforms/atomic-kms: Always set CRTC to ACTIVE when doing a modeset --- src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp index 69db9d2fded..ab18f8bb349 100644 --- a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp +++ b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp @@ -303,6 +303,7 @@ bool mga::AtomicKMSOutput::set_crtc(FBHandle const& fb) AtomicUpdate update; update.add_property(*conf->crtc_props, "MODE_ID", conf->mode->handle()); update.add_property(*conf->connector_props, "CRTC_ID", conf->current_crtc->crtc_id); + update.add_property(*conf->crtc_props, "ACTIVE", 1); /* Source viewport. Coordinates are 16.16 fixed point format */ update.add_property(*conf->plane_props, "SRC_X", conf->fb_offset.dx.as_uint32_t() << 16); From 3aae8bb3e55da079caf95642dff59d5c8dfbe9c6 Mon Sep 17 00:00:00 2001 From: Christopher James Halse Rogers Date: Thu, 7 Nov 2024 18:38:28 +1100 Subject: [PATCH 26/27] platforms/atomic-kms: Log when DPMS setting fails --- src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp index ab18f8bb349..3ab0a8a98dd 100644 --- a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp +++ b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp @@ -536,7 +536,10 @@ void mga::AtomicKMSOutput::set_power_mode(MirPowerMode mode) { AtomicUpdate update; update.add_property(*conf->crtc_props, "ACTIVE", should_be_active); - drmModeAtomicCommit(drm_fd_, update, DRM_MODE_ATOMIC_ALLOW_MODESET, nullptr); + if (auto err = drmModeAtomicCommit(drm_fd_, update, DRM_MODE_ATOMIC_ALLOW_MODESET, nullptr)) + { + mir::log_warning("Failed to set DPMS %s (%s [%i])", should_be_active ? "active" : "off", strerror(-err), -err); + } } else if (should_be_active) { From 5efbaef7a4e0eaa81f50cb542166617407de2564 Mon Sep 17 00:00:00 2001 From: Christopher James Halse Rogers Date: Thu, 7 Nov 2024 18:42:58 +1100 Subject: [PATCH 27/27] platforms/atomic-kms: Release resources for disconnected outputs. Otherwise we *can* be in a position where hardware resources are bound to a disconnected output, preventing them being used if a display is plugged in to a different connector. --- .../server/kms/atomic_kms_output.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp index 3ab0a8a98dd..9357c925e72 100644 --- a/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp +++ b/src/platforms/atomic-kms/server/kms/atomic_kms_output.cpp @@ -241,6 +241,24 @@ void mga::AtomicKMSOutput::reset() { conf->connector = resources.connector(conf->connector->connector_id); conf->connector_props = std::make_unique(drm_fd_, conf->connector); + + if (conf->current_crtc && (conf->connector->connection != DRM_MODE_CONNECTED)) + { + AtomicUpdate update; + update.add_property(*conf->connector_props, "CRTC_ID", 0); + update.add_property(*conf->crtc_props, "ACTIVE", 0); + update.add_property(*conf->crtc_props, "MODE_ID", 0); + update.add_property(*conf->plane_props, "FB_ID", 0); + update.add_property(*conf->plane_props, "CRTC_ID", 0); + + if (auto err = drmModeAtomicCommit(drm_fd(), update, DRM_MODE_ATOMIC_ALLOW_MODESET, nullptr)) + { + mir::log_warning("Failed release resources for disconnected output: %s (%i)", strerror(-err), -err); + } + + conf->crtc_props = nullptr; + conf->plane_props = nullptr; + } } catch (std::exception const& e) {