diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4b6fa998d44..f78f843fdc4 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