summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rw-r--r--include/drm++/gem.hpp184
-rw-r--r--src/gem.cpp134
3 files changed, 319 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 497f394..a23eddd 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -8,6 +8,7 @@ project(drm++
include(cmake/Diagnostics.cmake)
add_library(drm++ SHARED
+ "src/gem.cpp"
"src/ioctl.cpp"
"src/syncobject.cpp")
diff --git a/include/drm++/gem.hpp b/include/drm++/gem.hpp
new file mode 100644
index 0000000..1dfe1ba
--- /dev/null
+++ b/include/drm++/gem.hpp
@@ -0,0 +1,184 @@
+/* SPDX-License-Identifier: MIT */
+
+#pragma once
+
+#include "drm++/helper.hpp"
+
+namespace drm::gem {
+
+/// A GEM object wrapping memory.
+/// @throws drm::ioctl::Exception on failure
+class Object {
+ friend class DumbBuffer;
+public:
+ /// Import a DMA-BUF file descriptor into a GEM object.
+ /// Requires DRM_PRIME_CAP_IMPORT.
+ ///
+ /// An import can fail for various driver-specific reasons.
+ ///
+ /// @param close Close fd after import (regardless of success)
+ Object(int fd, int dmabuf_fd, bool close = true);
+
+ /// Flags for exporting fds
+ enum class ExportFlags : u32 {
+ None = 0,
+ ReadWrite = 1 << 0, //!< Allow read/write access (drivers may ignore this)
+ CloseOnExec = 1 << 1, //!< Close fd on execve()
+ };
+
+ /// Obtain a DMA-BUF file descriptor for the GEM object, incrementing the reference count.
+ /// Requires DRM_PRIME_CAP_EXPORT.
+ ///
+ /// An export can fail for various driver-specific reasons, including lack of support on this
+ /// specific GEM object.
+ ///
+ /// @param flags Flags to set on the exported file descriptor.
+ [[nodiscard]] int exportFd(ExportFlags flags = ExportFlags::None) const;
+
+ /// Change the handle of the GEM object.
+ ///
+ /// SAFETY: This is a dangerous operation, as the previous handle will be invalidated. There is
+ /// no guarantee that importing a DMA-BUF file descriptor will return a new handle, so other
+ /// instances of this class on the same file descriptor may become invalid. Use with caution.
+ ///
+ /// @param handle An unused GEM handle to change into.
+ void changeHandle(u32 handle);
+
+ // Private access
+ GETTER(fd)
+ GETTER(handle)
+
+ // Move constructor/operator
+ Object(Object&& other) noexcept : m_fd(other.m_fd), m_handle(other.m_handle) {
+ other.m_fd = -1; // invalidate other
+ }
+
+ Object& operator=(Object&& other) noexcept {
+ if (this != &other) {
+ this->destruct();
+ this->m_handle = other.m_handle;
+ this->m_fd = other.m_fd;
+ other.m_fd = -1; // invalidate other
+ }
+
+ return *this;
+ }
+
+ // Copy constructor/operator
+ Object(const Object& other) = delete;
+ Object& operator=(const Object& other) = delete;
+
+ // Destructor
+ ~Object() noexcept {
+ this->destruct();
+ }
+
+private:
+ int m_fd; // indicates object validity (>= 0)
+ u32 m_handle;
+
+ void destruct() noexcept;
+};
+
+/// A dumb buffer GEM object.
+/// Requires DRM_CAP_DUMB_BUFFER.
+/// @throws drm::ioctl::Exception on failure
+class DumbBuffer : public Object {
+public:
+ /// Create a new dumb buffer.
+ ///
+ /// The bits per pixel (bpp) also indicates the format and compatible formats
+ /// with similar pixel layouts. All buffers are strictly linear.
+ ///
+ /// +-----+------------------------+------------------------+
+ /// | BPP | Framebuffer format | Compatible formats |
+ /// +=====+========================+========================+
+ /// | 32 | * DRM_FORMAT_XRGB8888 | * DRM_FORMAT_BGRX8888 |
+ /// | | | * DRM_FORMAT_RGBX8888 |
+ /// | | | * DRM_FORMAT_XBGR8888 |
+ /// +-----+------------------------+------------------------+
+ /// | 24 | * DRM_FORMAT_RGB888 | * DRM_FORMAT_BGR888 |
+ /// +-----+------------------------+------------------------+
+ /// | 16 | * DRM_FORMAT_RGB565 | * DRM_FORMAT_BGR565 |
+ /// +-----+------------------------+------------------------+
+ /// | 15 | * DRM_FORMAT_XRGB1555 | * DRM_FORMAT_BGRX1555 |
+ /// | | | * DRM_FORMAT_RGBX1555 |
+ /// | | | * DRM_FORMAT_XBGR1555 |
+ /// ------+------------------------+------------------------+
+ /// | 8 | * DRM_FORMAT_C8 | * DRM_FORMAT_D8 |
+ /// | | | * DRM_FORMAT_R8 |
+ /// +-----+------------------------+------------------------+
+ /// | 4 | * DRM_FORMAT_C4 | * DRM_FORMAT_D4 |
+ /// | | | * DRM_FORMAT_R4 |
+ /// +-----+------------------------+------------------------+
+ /// | 2 | * DRM_FORMAT_C2 | * DRM_FORMAT_D2 |
+ /// | | | * DRM_FORMAT_R2 |
+ /// +-----+------------------------+------------------------+
+ /// | 1 | * DRM_FORMAT_C1 | * DRM_FORMAT_D1 |
+ /// | | | * DRM_FORMAT_R1 |
+ /// +-----+------------------------+------------------------+
+ ///
+ /// Table is not exhaustive, however other bpp values should only be used for legacy purposes.
+ ///
+ /// Support for all bits per pixel is optional and successful creation of a dumb buffer does
+ /// not guarantee that all related formats are compatible.
+ DumbBuffer(int fd, u32 width, u32 height, u32 bpp);
+
+ /// Map the dumb buffer into userspace memory. May be called multiple times,
+ /// will return the same pointer if already mapped.
+ /// @throws std::system_error on mmap failure
+ [[nodiscard]] void* map();
+
+ // Private access
+ GETTER(width)
+ GETTER(height)
+ GETTER(bpp)
+ GETTER(pitch)
+ GETTER(size)
+
+ // Move constructor/operator
+ DumbBuffer(DumbBuffer&& other) noexcept
+ : Object(std::move(other)), // NOLINTBEGIN (use after move)
+ m_width(other.m_width), m_height(other.m_height), m_bpp(other.m_bpp),
+ m_pitch(other.m_pitch), m_size(other.m_size),
+ m_map(other.m_map) {
+ other.m_map = nullptr; // invalidate other
+ } // NOLINTEND
+
+ DumbBuffer& operator=(DumbBuffer&& other) noexcept {
+ if (this != &other) {
+ this->destruct();
+ Object::operator=(std::move(other)); // NOLINTBEGIN (use after move)
+ this->m_width = other.m_width;
+ this->m_height = other.m_height;
+ this->m_bpp = other.m_bpp;
+ this->m_pitch = other.m_pitch;
+ this->m_size = other.m_size;
+ this->m_map = other.m_map;
+ other.m_map = nullptr; // invalidate other
+ } // NOLINTEND
+
+ return *this;
+ }
+
+ // Copy constructor/operator
+ DumbBuffer(const DumbBuffer& other) = delete;
+ DumbBuffer& operator=(const DumbBuffer& other) = delete;
+
+ // Destructor
+ ~DumbBuffer() noexcept {
+ this->destruct();
+ }
+
+private:
+ u32 m_width;
+ u32 m_height;
+ u32 m_bpp;
+ u32 m_pitch;
+ u64 m_size;
+ void* m_map{nullptr}; // indicate mapping validity
+
+ void destruct() noexcept;
+};
+
+}
diff --git a/src/gem.cpp b/src/gem.cpp
new file mode 100644
index 0000000..4b81703
--- /dev/null
+++ b/src/gem.cpp
@@ -0,0 +1,134 @@
+/* SPDX-License-Identifier: MIT */
+
+#include "drm++/gem.hpp"
+#include "drm++/helper.hpp"
+#include "drm++/ioctl.hpp"
+
+#include <cerrno>
+#include <system_error>
+
+#include <drm.h>
+#include <drm_mode.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+using namespace drm;
+using namespace drm::gem;
+
+/* GEM object class */
+
+Object::Object(int fd, int dmabuf_fd, bool close) : m_fd(fd) {
+ try {
+ drm_prime_handle args{
+ .fd = dmabuf_fd
+ };
+ ioctl::perform(this->m_fd, DRM_IOCTL_PRIME_FD_TO_HANDLE, args);
+
+ this->m_handle = args.handle;
+ } catch (...) {
+ if (close) {
+ ::close(dmabuf_fd);
+ }
+
+ throw;
+ }
+
+ if (close) {
+ ::close(dmabuf_fd);
+ }
+}
+
+int Object::exportFd(ExportFlags flags) const {
+ drm_prime_handle args{
+ .handle = this->m_handle,
+ .flags = static_cast<u32>(flags)
+ };
+ ioctl::perform(this->m_fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, args);
+
+ return args.fd;
+}
+
+void Object::changeHandle(u32 handle) {
+ drm_gem_close args{
+ .handle = this->m_handle
+ };
+ ioctl::perform(this->m_fd, DRM_IOCTL_GEM_CLOSE, args);
+
+ this->m_handle = handle;
+}
+
+void Object::destruct() noexcept {
+ if (this->m_fd < 0) {
+ return;
+ }
+
+ drm_gem_close args{
+ .handle = this->m_handle
+ };
+ try {
+ ioctl::perform(this->m_fd, DRM_IOCTL_GEM_CLOSE, args);
+ } catch(...) {
+ (void) 0; // not much we can do about the leak
+ }
+
+ this->m_fd = -1;
+}
+
+/* Dumb buffer class */
+
+DumbBuffer::DumbBuffer(int fd, u32 width, u32 height, u32 bpp)
+ : Object(fd, -1), m_width(width), m_height(height), m_bpp(bpp) {
+ drm_mode_create_dumb args{
+ .height = height,
+ .width = width,
+ .bpp = bpp
+ };
+ ioctl::perform(this->m_fd, DRM_IOCTL_MODE_CREATE_DUMB, args);
+
+ this->m_handle = args.handle;
+ this->m_pitch = args.pitch;
+ this->m_size = args.size;
+}
+
+void* DumbBuffer::map() {
+ if (this->m_map != nullptr) {
+ return this->m_map;
+ }
+
+ drm_mode_map_dumb args{
+ .handle = this->m_handle
+ };
+ ioctl::perform(this->m_fd, DRM_IOCTL_MODE_MAP_DUMB, args);
+
+ void* map{::mmap(nullptr, this->m_size, PROT_READ | PROT_WRITE, MAP_SHARED,
+ this->m_fd, static_cast<off_t>(args.offset))};
+ if (map == MAP_FAILED) {
+ throw std::system_error(errno, std::generic_category(), "mmap failed");
+ }
+
+ this->m_map = map;
+ return this->m_map;
+}
+
+void DumbBuffer::destruct() noexcept {
+ if (this->m_fd < 0) {
+ return;
+ }
+
+ if (this->m_map != nullptr) {
+ ::munmap(this->m_map, this->m_size);
+ this->m_map = nullptr;
+ }
+
+ drm_mode_destroy_dumb args{
+ .handle = this->m_handle
+ };
+ try {
+ ioctl::perform(this->m_fd, DRM_IOCTL_MODE_DESTROY_DUMB, args);
+ } catch(...) {
+ (void) 0; // not much we can do about the leak
+ }
+
+ this->m_fd = -1;
+}