From f888bfb6e421e4bf4e8914586a01de7bd8cb4080 Mon Sep 17 00:00:00 2001 From: Vaxry Date: Sun, 23 Jun 2024 19:40:40 +0200 Subject: [PATCH] DRM: Init DRM/Libinput session code Adds the bare basic of DRM code, which only initializes the environment atm. We cannot render yet. --- CMakeLists.txt | 4 +- include/aquamarine/backend/Backend.hpp | 3 + include/aquamarine/backend/DRM.hpp | 214 +++++++ include/aquamarine/backend/Misc.hpp | 11 + include/aquamarine/backend/Session.hpp | 90 +++ src/backend/Backend.cpp | 18 + src/backend/Session.cpp | 235 ++++++++ src/backend/drm/DRM.cpp | 760 +++++++++++++++++++++++++ src/backend/drm/Props.cpp | 183 ++++++ src/backend/drm/Props.hpp | 13 + 10 files changed, 1529 insertions(+), 2 deletions(-) create mode 100644 include/aquamarine/backend/DRM.hpp create mode 100644 include/aquamarine/backend/Misc.hpp create mode 100644 include/aquamarine/backend/Session.hpp create mode 100644 src/backend/Session.cpp create mode 100644 src/backend/drm/DRM.cpp create mode 100644 src/backend/drm/Props.cpp create mode 100644 src/backend/drm/Props.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fd994ff..675880a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ set(INCLUDE ${CMAKE_INSTALL_FULL_INCLUDEDIR}) set(LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR}) find_package(PkgConfig REQUIRED) -pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols hyprutils>=0.1.2 pixman-1 wayland-client libdrm gbm) +pkg_check_modules(deps REQUIRED IMPORTED_TARGET libseat libinput wayland-client wayland-protocols hyprutils>=0.1.2 pixman-1 wayland-client libdrm gbm libudev) configure_file(aquamarine.pc.in aquamarine.pc @ONLY) @@ -83,7 +83,7 @@ protocolNew("stable/linux-dmabuf" "linux-dmabuf-v1" false) add_custom_target(tests) add_executable(simpleWindow "tests/SimpleWindow.cpp") -target_link_libraries(simpleWindow PRIVATE aquamarine PkgConfig::deps) +target_link_libraries(simpleWindow PRIVATE PkgConfig::deps aquamarine) add_test(NAME "simpleWindow" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND simpleWindow "simpleWindow") add_dependencies(tests simpleWindow) diff --git a/include/aquamarine/backend/Backend.hpp b/include/aquamarine/backend/Backend.hpp index 6da64c3..ab8ef49 100644 --- a/include/aquamarine/backend/Backend.hpp +++ b/include/aquamarine/backend/Backend.hpp @@ -8,6 +8,7 @@ #include #include "../allocator/Allocator.hpp" #include "Misc.hpp" +#include "Session.hpp" namespace Aquamarine { enum eBackendType { @@ -108,6 +109,7 @@ namespace Aquamarine { Hyprutils::Memory::CSharedPointer allocator; bool ready = false; + Hyprutils::Memory::CSharedPointer session; private: CBackend(); @@ -118,6 +120,7 @@ namespace Aquamarine { std::vector> implementations; SBackendOptions options; Hyprutils::Memory::CWeakPointer self; + std::vector sessionFDs; // struct { diff --git a/include/aquamarine/backend/DRM.hpp b/include/aquamarine/backend/DRM.hpp new file mode 100644 index 0000000..2844a2a --- /dev/null +++ b/include/aquamarine/backend/DRM.hpp @@ -0,0 +1,214 @@ +#pragma once + +#include "./Backend.hpp" +#include "../allocator/Swapchain.hpp" +#include "../output/Output.hpp" +#include "../input/Input.hpp" +#include +#include +#include + +namespace Aquamarine { + class CDRMBackend; + struct SDRMConnector; + + struct SDRMFB { + uint32_t id = 0; + Hyprutils::Memory::CSharedPointer buffer; + Hyprutils::Memory::CWeakPointer backend; + }; + + struct SDRMLayer { + Hyprutils::Memory::CSharedPointer current /* displayed */, queued /* submitted */, pending /* to be submitted */; + Hyprutils::Memory::CWeakPointer backend; + }; + + struct SDRMPlane { + bool init(drmModePlane* plane); + + uint64_t type = 0; + uint32_t id = 0; + uint32_t initialID = 0; + + Hyprutils::Memory::CSharedPointer current /* displayed */, queued /* submitted */; + Hyprutils::Memory::CWeakPointer backend; + Hyprutils::Memory::CWeakPointer self; + std::vector formats; + + union UDRMPlaneProps { + struct { + uint32_t type; + uint32_t rotation; // Not guaranteed to exist + uint32_t in_formats; // Not guaranteed to exist + + // atomic-modesetting only + + uint32_t src_x; + uint32_t src_y; + uint32_t src_w; + uint32_t src_h; + uint32_t crtc_x; + uint32_t crtc_y; + uint32_t crtc_w; + uint32_t crtc_h; + uint32_t fb_id; + uint32_t crtc_id; + uint32_t fb_damage_clips; + uint32_t hotspot_x; + uint32_t hotspot_y; + }; + uint32_t props[16] = {0}; + }; + UDRMPlaneProps props; + }; + + struct SDRMCRTC { + uint32_t id = 0; + std::vector layers; + int32_t refresh = 0; + + struct { + int gammaSize = 0; + } legacy; + + Hyprutils::Memory::CSharedPointer primary; + Hyprutils::Memory::CSharedPointer cursor; + Hyprutils::Memory::CWeakPointer backend; + + union UDRMCRTCProps { + struct { + // None of these are guaranteed to exist + uint32_t vrr_enabled; + uint32_t gamma_lut; + uint32_t gamma_lut_size; + + // atomic-modesetting only + + uint32_t active; + uint32_t mode_id; + }; + uint32_t props[6] = {0}; + }; + UDRMCRTCProps props; + }; + + class CDRMOutput : public IOutput { + public: + virtual ~CDRMOutput(); + virtual bool commit(); + virtual bool test(); + virtual Hyprutils::Memory::CSharedPointer getBackend(); + virtual bool setCursor(Hyprutils::Memory::CSharedPointer buffer, const Hyprutils::Math::Vector2D& hotspot); + virtual void moveCursor(const Hyprutils::Math::Vector2D& coord); + virtual void scheduleFrame(); + virtual Hyprutils::Math::Vector2D maxCursorSize(); + + Hyprutils::Memory::CWeakPointer self; + + private: + CDRMOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer backend_, Hyprutils::Memory::CSharedPointer connector_); + + Hyprutils::Memory::CWeakPointer backend; + Hyprutils::Memory::CSharedPointer connector; + + friend struct SDRMConnector; + }; + + struct SDRMConnector { + ~SDRMConnector(); + + bool init(drmModeConnector* connector); + void connect(drmModeConnector* connector); + void disconnect(); + Hyprutils::Memory::CSharedPointer getCurrentCRTC(const drmModeConnector* connector); + drmModeModeInfo* getCurrentMode(); + void parseEDID(std::vector data); + + Hyprutils::Memory::CSharedPointer output; + Hyprutils::Memory::CWeakPointer backend; + Hyprutils::Memory::CWeakPointer self; + std::string szName; + drmModeConnection status = DRM_MODE_DISCONNECTED; + uint32_t id = 0; + std::array maxBpcBounds = {0, 0}; + Hyprutils::Memory::CSharedPointer crtc; + int32_t refresh = 0; + uint32_t possibleCrtcs = 0; + std::string make, serial, model; + + bool cursorEnabled = false; + Hyprutils::Math::Vector2D cursorPos, cursorSize, cursorHotspot; + Hyprutils::Memory::CSharedPointer pendingCursorFB; + + union UDRMConnectorProps { + struct { + uint32_t edid; + uint32_t dpms; + uint32_t link_status; // not guaranteed to exist + uint32_t path; + uint32_t vrr_capable; // not guaranteed to exist + uint32_t subconnector; // not guaranteed to exist + uint32_t non_desktop; + uint32_t panel_orientation; // not guaranteed to exist + uint32_t content_type; // not guaranteed to exist + uint32_t max_bpc; // not guaranteed to exist + + // atomic-modesetting only + + uint32_t crtc_id; + }; + uint32_t props[4] = {0}; + }; + UDRMConnectorProps props; + }; + + class CDRMBackend : public IBackendImplementation { + public: + virtual ~CDRMBackend(); + virtual eBackendType type(); + virtual bool start(); + virtual int pollFD(); + virtual int drmFD(); + virtual bool dispatchEvents(); + virtual uint32_t capabilities(); + virtual bool setCursor(Hyprutils::Memory::CSharedPointer buffer, const Hyprutils::Math::Vector2D& hotspot); + virtual void onReady(); + virtual std::vector getRenderFormats(); + virtual std::vector getCursorFormats(); + + Hyprutils::Memory::CWeakPointer self; + + private: + CDRMBackend(Hyprutils::Memory::CSharedPointer backend); + + static Hyprutils::Memory::CSharedPointer attempt(Hyprutils::Memory::CSharedPointer backend); + bool registerGPU(Hyprutils::Memory::CSharedPointer gpu_, Hyprutils::Memory::CSharedPointer primary_ = {}); + bool checkFeatures(); + bool initResources(); + bool grabFormats(); + void scanConnectors(); + + Hyprutils::Memory::CSharedPointer gpu; + Hyprutils::Memory::CWeakPointer primary; + + Hyprutils::Memory::CWeakPointer backend; + + std::vector> crtcs; + std::vector> planes; + std::vector> connectors; + std::vector formats; + + struct { + Hyprutils::Math::Vector2D cursorSize; + bool supportsAsyncCommit = false; + bool supportsAddFb2Modifiers = false; + } drmProps; + + friend class CBackend; + friend struct SDRMFB; + friend struct SDRMConnector; + friend struct SDRMCRTC; + friend struct SDRMPlane; + friend struct CDRMOutput; + }; +}; diff --git a/include/aquamarine/backend/Misc.hpp b/include/aquamarine/backend/Misc.hpp new file mode 100644 index 0000000..bce8847 --- /dev/null +++ b/include/aquamarine/backend/Misc.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +namespace Aquamarine { + struct SDRMFormat { + uint32_t drmFormat = 0; /* DRM_FORMAT_INVALID */ + std::vector modifiers; + }; +}; diff --git a/include/aquamarine/backend/Session.hpp b/include/aquamarine/backend/Session.hpp new file mode 100644 index 0000000..9460531 --- /dev/null +++ b/include/aquamarine/backend/Session.hpp @@ -0,0 +1,90 @@ +#pragma once + +#include +#include +#include +#include + +struct udev; +struct udev_monitor; +struct udev_device; +struct libseat; + +namespace Aquamarine { + class CBackend; + class CSession; + + class CSessionDevice { + public: + CSessionDevice(Hyprutils::Memory::CSharedPointer session_, const std::string& path_); + ~CSessionDevice(); + + static Hyprutils::Memory::CSharedPointer openIfKMS(Hyprutils::Memory::CSharedPointer session_, const std::string& path_); + + bool supportsKMS(); + + int fd = -1; + int deviceID = -1; + dev_t dev; + std::string path; + + enum eChangeEventType { + AQ_SESSION_EVENT_CHANGE_HOTPLUG = 0, + AQ_SESSION_EVENT_CHANGE_LEASE, + }; + + struct SChangeEvent { + eChangeEventType type = AQ_SESSION_EVENT_CHANGE_HOTPLUG; + + struct { + uint32_t connectorID = 0, propID = 0; + } hotplug; + }; + + struct { + Hyprutils::Signal::CSignal change; + Hyprutils::Signal::CSignal remove; + } events; + + private: + Hyprutils::Memory::CWeakPointer session; + }; + + class CSession { + public: + ~CSession(); + + static Hyprutils::Memory::CSharedPointer attempt(Hyprutils::Memory::CSharedPointer backend_); + + bool active = true; // whether the current vt is ours + uint32_t vt = 0; // 0 means unsupported + std::string seatName; + + std::vector> sessionDevices; + + udev* udevHandle = nullptr; + udev_monitor* udevMonitor = nullptr; + libseat* libseatHandle = nullptr; + + std::vector pollFDs(); + + void dispatchPendingEventsAsync(); + + struct SAddDrmCardEvent { + std::string path; + }; + + struct { + Hyprutils::Signal::CSignal changeActive; + Hyprutils::Signal::CSignal addDrmCard; + Hyprutils::Signal::CSignal destroy; + } events; + + private: + Hyprutils::Memory::CWeakPointer backend; + + void dispatchUdevEvents(); + + friend class CSessionDevice; + }; +}; diff --git a/src/backend/Backend.cpp b/src/backend/Backend.cpp index ddf1459..38ed42b 100644 --- a/src/backend/Backend.cpp +++ b/src/backend/Backend.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -49,6 +50,14 @@ Hyprutils::Memory::CSharedPointer Aquamarine::CBackend::create(const s auto ref = SP(new CWaylandBackend(backend)); backend->implementations.emplace_back(ref); ref->self = ref; + } else if (b.backendType == AQ_BACKEND_DRM) { + auto ref = CDRMBackend::attempt(backend); + if (!ref) { + backend->log(AQ_LOG_ERROR, "DRM Backend failed"); + continue; + } + backend->implementations.emplace_back(ref); + ref->self = ref; } else { backend->log(AQ_LOG_ERROR, std::format("Unknown backend id: {}", (int)b.backendType)); continue; @@ -108,6 +117,8 @@ bool Aquamarine::CBackend::start() { b->onReady(); } + sessionFDs = session->pollFDs(); + return true; } @@ -186,6 +197,10 @@ std::vector Aquamarine::CBackend::getPollFDs() { result.push_back(fd); } + for (auto& sfd : sessionFDs) { + result.push_back(sfd); + } + return result; } @@ -204,6 +219,9 @@ void Aquamarine::CBackend::dispatchEventsAsync() { for (auto& i : implementations) { i->dispatchEvents(); } + + if (session) + session->dispatchPendingEventsAsync(); } bool Aquamarine::CBackend::hasSession() { diff --git a/src/backend/Session.cpp b/src/backend/Session.cpp new file mode 100644 index 0000000..69239bd --- /dev/null +++ b/src/backend/Session.cpp @@ -0,0 +1,235 @@ +#include + +extern "C" { +#include +#include +#include +#include +#include +#include +} + +using namespace Aquamarine; +using namespace Hyprutils::Memory; +#define SP CSharedPointer + +// we can't really do better with libseat logs +// because they don't allow us to pass "data" or anything... +// Nobody should create multiple backends anyways really +Hyprutils::Memory::CSharedPointer backendInUse; + +static Aquamarine::eBackendLogLevel logLevelFromLibseat(enum libseat_log_level level) { + switch (level) { + case LIBSEAT_LOG_LEVEL_ERROR: return AQ_LOG_ERROR; + case LIBSEAT_LOG_LEVEL_SILENT: return AQ_LOG_TRACE; + default: break; + } + + return AQ_LOG_DEBUG; +} + +static void libseatLog(libseat_log_level level, const char* fmt, va_list args) { + if (!backendInUse) + return; + + static char string[1024]; + vsnprintf(string, sizeof(string), fmt, args); + + backendInUse->log(logLevelFromLibseat(level), std::format("[libseat] {}", string)); +} + +static void libseatEnableSeat(struct libseat* seat, void* data) { + auto PSESSION = (Aquamarine::CSession*)data; + PSESSION->active = true; + PSESSION->events.changeActive.emit(); +} + +static void libseatDisableSeat(struct libseat* seat, void* data) { + auto PSESSION = (Aquamarine::CSession*)data; + PSESSION->active = false; + PSESSION->events.changeActive.emit(); + libseat_disable_seat(PSESSION->libseatHandle); +} + +static const libseat_seat_listener libseatListener = { + .enable_seat = libseatEnableSeat, + .disable_seat = libseatDisableSeat, +}; + +Aquamarine::CSessionDevice::CSessionDevice(Hyprutils::Memory::CSharedPointer session_, const std::string& path_) : session(session_), path(path_) { + deviceID = libseat_open_device(session->libseatHandle, path.c_str(), &fd); + if (deviceID < 0) { + session->backend->log(AQ_LOG_ERROR, std::format("libseat: Couldn't open device at {}", path_)); + return; + } + + struct stat stat_; + if (fstat(fd, &stat_) < 0) { + session->backend->log(AQ_LOG_ERROR, std::format("libseat: Couldn't stat device at {}", path_)); + deviceID = -1; + return; + } + + dev = stat_.st_rdev; +} + +Aquamarine::CSessionDevice::~CSessionDevice() { + if (fd < 0) + return; + libseat_close_device(session->libseatHandle, fd); + close(fd); +} + +bool Aquamarine::CSessionDevice::supportsKMS() { + if (deviceID < 0) + return false; + + bool kms = drmIsKMS(fd); + + if (kms) + session->backend->log(AQ_LOG_DEBUG, std::format("libseat: Device {} supports kms", path)); + else + session->backend->log(AQ_LOG_DEBUG, std::format("libseat: Device {} does not support kms", path)); + + return kms; +} + +SP Aquamarine::CSessionDevice::openIfKMS(SP session_, const std::string& path_) { + auto dev = makeShared(session_, path_); + if (!dev->supportsKMS()) + return nullptr; + return dev; +} + +SP Aquamarine::CSession::attempt(Hyprutils::Memory::CSharedPointer backend_) { + if (!backend_) + return nullptr; + + auto session = makeShared(); + session->backend = backend_; + backendInUse = backend_; + + // ------------ Libseat + + libseat_set_log_handler(libseatLog); + libseat_set_log_level(LIBSEAT_LOG_LEVEL_INFO); + + session->libseatHandle = libseat_open_seat(&libseatListener, session.get()); + + if (!session->libseatHandle) { + session->backend->log(AQ_LOG_ERROR, "libseat: failed to open a seat"); + return nullptr; + } + + auto seatName = libseat_seat_name(session->libseatHandle); + if (!seatName) { + session->backend->log(AQ_LOG_ERROR, "libseat: failed to get seat name"); + return nullptr; + } + + session->seatName = seatName; + + // dispatch any already pending events + session->dispatchPendingEventsAsync(); + + // ----------- Udev + + session->udevHandle = udev_new(); + if (!session->udevHandle) { + session->backend->log(AQ_LOG_ERROR, "udev: failed to create a new context"); + return nullptr; + } + + session->udevMonitor = udev_monitor_new_from_netlink(session->udevHandle, "udev"); + if (!session->udevMonitor) { + session->backend->log(AQ_LOG_ERROR, "udev: failed to create a new udevMonitor"); + return nullptr; + } + + udev_monitor_filter_add_match_subsystem_devtype(session->udevMonitor, "drm", nullptr); + udev_monitor_enable_receiving(session->udevMonitor); + + return session; +} + +Aquamarine::CSession::~CSession() { + sessionDevices.clear(); + + if (libseatHandle) + libseat_close_seat(libseatHandle); + if (udevMonitor) + udev_monitor_unref(udevMonitor); + if (udevHandle) + udev_unref(udevHandle); + + libseatHandle = nullptr; + udevMonitor = nullptr; + udevHandle = nullptr; +} + +static bool isDRMCard(const char* sysname) { + const char prefix[] = DRM_PRIMARY_MINOR_NAME; + if (strncmp(sysname, prefix, strlen(prefix)) != 0) + return false; + + for (size_t i = strlen(prefix); sysname[i] != '\0'; i++) { + if (sysname[i] < '0' || sysname[i] > '9') + return false; + } + + return true; +} + +void Aquamarine::CSession::dispatchUdevEvents() { + auto device = udev_monitor_receive_device(udevMonitor); + + if (!device) + return; + + auto sysname = udev_device_get_sysname(device); + auto devnode = udev_device_get_devnode(device); + auto action = udev_device_get_action(device); + + backend->log(AQ_LOG_DEBUG, std::format("udev: new udev {} event for {}", action ? action : "unknown", sysname ? sysname : "unknown")); + + if (!isDRMCard(sysname) || !action || !devnode) { + udev_device_unref(device); + return; + } + + if (action == std::string{"add"}) + events.addDrmCard.emit(SAddDrmCardEvent{.path = devnode}); + else if (action == std::string{"change"} || action == std::string{"remove"}) { + dev_t deviceNum = udev_device_get_devnum(device); + + for (auto& d : sessionDevices) { + if (d->dev != deviceNum) + continue; + + if (action == std::string{"change"}) { + backend->log(AQ_LOG_DEBUG, std::format("udev: DRM device {} changed", sysname ? sysname : "unknown")); + backend->log(AQ_LOG_ERROR, "udev: FIXME: change event is a STUB"); + } else if (action == std::string{"remove"}) { + backend->log(AQ_LOG_DEBUG, std::format("udev: DRM device {} removed", sysname ? sysname : "unknown")); + backend->log(AQ_LOG_ERROR, "udev: FIXME: remove event is a STUB"); + } + } + } + + udev_device_unref(device); + return; +} + +void Aquamarine::CSession::dispatchPendingEventsAsync() { + if (libseat_dispatch(libseatHandle, 0) == -1) + backend->log(AQ_LOG_ERROR, "Couldn't dispatch libseat events"); + + dispatchUdevEvents(); +} + +std::vector Aquamarine::CSession::pollFDs() { + if (!libseatHandle || !udevMonitor || !udevHandle) + return {}; + + return {libseat_get_fd(libseatHandle), udev_monitor_get_fd(udevMonitor)}; +} diff --git a/src/backend/drm/DRM.cpp b/src/backend/drm/DRM.cpp new file mode 100644 index 0000000..841975e --- /dev/null +++ b/src/backend/drm/DRM.cpp @@ -0,0 +1,760 @@ +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +#include +} + +#include "Props.hpp" + +using namespace Aquamarine; +using namespace Hyprutils::Memory; +using namespace Hyprutils::Math; +#define SP CSharedPointer + +Aquamarine::CDRMBackend::CDRMBackend(SP backend_) : backend(backend_) { + ; +} + +static udev_enumerate* enumDRMCards(udev* udev) { + auto enumerate = udev_enumerate_new(udev); + if (!enumerate) + return nullptr; + + udev_enumerate_add_match_subsystem(enumerate, "drm"); + udev_enumerate_add_match_sysname(enumerate, DRM_PRIMARY_MINOR_NAME "[0-9]*"); + + if (udev_enumerate_scan_devices(enumerate)) { + udev_enumerate_unref(enumerate); + return nullptr; + } + + return enumerate; +} + +static std::vector> scanGPUs(SP backend) { + // FIXME: This provides no explicit way to set a preferred gpu + + auto enumerate = enumDRMCards(backend->session->udevHandle); + + if (!enumerate) { + backend->log(AQ_LOG_ERROR, "drm: couldn't enumerate gpus with udev"); + return {}; + } + + if (!udev_enumerate_get_list_entry(enumerate)) { + // TODO: wait for them. + backend->log(AQ_LOG_ERROR, "drm: No gpus in scanGPUs."); + return {}; + } + + udev_list_entry* entry = nullptr; + size_t i = 0; + + std::deque> devices; + + udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(enumerate)) { + auto path = udev_list_entry_get_name(entry); + auto device = udev_device_new_from_syspath(backend->session->udevHandle, path); + if (!device) { + backend->log(AQ_LOG_WARNING, std::format("drm: Skipping device {}", path ? path : "unknown")); + continue; + } + + backend->log(AQ_LOG_DEBUG, std::format("drm: Enumerated device {}", path ? path : "unknown")); + + auto seat = udev_device_get_property_value(device, "ID_SEAT"); + if (!seat) + seat = "seat0"; + + if (!backend->session->seatName.empty() && backend->session->seatName != seat) { + backend->log(AQ_LOG_WARNING, std::format("drm: Skipping device {} because seat {} doesn't match our {}", path ? path : "unknown", seat, backend->session->seatName)); + udev_device_unref(device); + continue; + } + + auto pciDevice = udev_device_get_parent_with_subsystem_devtype(device, "pci", nullptr); + bool isBootVGA = false; + if (pciDevice) { + auto id = udev_device_get_sysattr_value(pciDevice, "boot_vga"); + isBootVGA = id && id == std::string{"1"}; + } + + if (!udev_device_get_devnode(device)) { + backend->log(AQ_LOG_ERROR, std::format("drm: Skipping device {}, no devnode", path ? path : "unknown")); + udev_device_unref(device); + continue; + } + + auto sessionDevice = CSessionDevice::openIfKMS(backend->session, udev_device_get_devnode(device)); + if (!sessionDevice) { + backend->log(AQ_LOG_ERROR, std::format("drm: Skipping device {}, not a KMS device", path ? path : "unknown")); + udev_device_unref(device); + continue; + } + + udev_device_unref(device); + + if (isBootVGA) + devices.push_front(sessionDevice); + else + devices.push_back(sessionDevice); + + ++i; + } + + udev_enumerate_unref(enumerate); + + std::vector> vecDevices; + for (auto& d : devices) { + vecDevices.push_back(d); + } + + return vecDevices; +} + +SP Aquamarine::CDRMBackend::attempt(SP backend) { + if (!backend->session) + backend->session = CSession::attempt(backend); + + if (!backend->session) { + backend->log(AQ_LOG_ERROR, "Failed to open a session"); + return nullptr; + } + + if (!backend->session->active) { + backend->log(AQ_LOG_DEBUG, "Session is not active, waiting for 5s"); + + auto started = std::chrono::system_clock::now(); + + while (!backend->session->active) { + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + backend->session->dispatchPendingEventsAsync(); + + if (std::chrono::duration_cast(std::chrono::system_clock::now() - started).count() >= 5000) { + backend->log(AQ_LOG_DEBUG, "Session timeout reached"); + break; + } + } + + if (!backend->session->active) { + backend->log(AQ_LOG_DEBUG, "Session could not be activated in time"); + return nullptr; + } + } + + auto gpus = scanGPUs(backend); + + if (gpus.empty()) { + backend->log(AQ_LOG_ERROR, "drm: Found no gpus to use, cannot continue"); + return nullptr; + } + + backend->log(AQ_LOG_DEBUG, std::format("drm: Found {} GPUs", gpus.size())); + + // FIXME: this will ignore multi-gpu setups and only create one backend + auto drmBackend = SP(new CDRMBackend(backend)); + drmBackend->self = drmBackend; + + if (!drmBackend->registerGPU(gpus.at(0))) { + backend->log(AQ_LOG_ERROR, std::format("drm: Failed to register gpu at fd {}", gpus[0]->fd)); + return nullptr; + } else + backend->log(AQ_LOG_DEBUG, std::format("drm: Registered gpu at fd {}", gpus[0]->fd)); + + // TODO: consider listening for new devices + // But if you expect me to handle gpu hotswaps you are probably insane LOL + + if (!drmBackend->checkFeatures()) { + backend->log(AQ_LOG_ERROR, "drm: Failed checking features"); + return nullptr; + } + + if (!drmBackend->initResources()) { + backend->log(AQ_LOG_ERROR, "drm: Failed initializing resources"); + return nullptr; + } + + backend->log(AQ_LOG_DEBUG, std::format("drm: Basic init pass for gpu {}", gpus[0]->path)); + + drmBackend->grabFormats(); + + drmBackend->scanConnectors(); + + return drmBackend; +} + +Aquamarine::CDRMBackend::~CDRMBackend() { + ; +} + +bool Aquamarine::CDRMBackend::checkFeatures() { + uint64_t curW = 0, curH = 0; + if (drmGetCap(gpu->fd, DRM_CAP_CURSOR_WIDTH, &curW)) + curW = 64; + if (drmGetCap(gpu->fd, DRM_CAP_CURSOR_HEIGHT, &curH)) + curH = 64; + + drmProps.cursorSize = Hyprutils::Math::Vector2D{(double)curW, (double)curH}; + + uint64_t cap = 0; + if (drmGetCap(gpu->fd, DRM_CAP_PRIME, &cap) || !(cap & DRM_PRIME_CAP_IMPORT)) { + backend->log(AQ_LOG_ERROR, std::format("drm: DRM_PRIME_CAP_IMPORT unsupported")); + return false; + } + + if (drmGetCap(gpu->fd, DRM_CAP_CRTC_IN_VBLANK_EVENT, &cap) || !cap) { + backend->log(AQ_LOG_ERROR, std::format("drm: DRM_CAP_CRTC_IN_VBLANK_EVENT unsupported")); + return false; + } + + if (drmGetCap(gpu->fd, DRM_CAP_TIMESTAMP_MONOTONIC, &cap) || !cap) { + backend->log(AQ_LOG_ERROR, std::format("drm: DRM_PRIME_CAP_IMPORT unsupported")); + return false; + } + + if (drmSetClientCap(gpu->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) { + backend->log(AQ_LOG_ERROR, std::format("drm: DRM_CLIENT_CAP_UNIVERSAL_PLANES unsupported")); + return false; + } + + drmProps.supportsAsyncCommit = drmGetCap(gpu->fd, DRM_CAP_ASYNC_PAGE_FLIP, &cap) == 0 && cap == 1; + drmProps.supportsAddFb2Modifiers = drmGetCap(gpu->fd, DRM_CAP_ADDFB2_MODIFIERS, &cap) == 0 && cap == 1; + + // TODO: allow no-modifiers? + + return true; +} + +bool Aquamarine::CDRMBackend::initResources() { + auto resources = drmModeGetResources(gpu->fd); + if (!resources) { + backend->log(AQ_LOG_ERROR, std::format("drm: drmModeGetResources failed")); + return false; + } + + backend->log(AQ_LOG_DEBUG, std::format("drm: found {} CRTCs", resources->count_crtcs)); + + for (size_t i = 0; i < resources->count_crtcs; ++i) { + auto CRTC = makeShared(); + CRTC->id = resources->crtcs[i]; + CRTC->backend = self; + + auto drmCRTC = drmModeGetCrtc(gpu->fd, CRTC->id); + if (!drmCRTC) { + backend->log(AQ_LOG_ERROR, std::format("drm: drmModeGetCrtc for crtc {} failed", CRTC->id)); + drmModeFreeResources(resources); + crtcs.clear(); + return false; + } + + CRTC->legacy.gammaSize = drmCRTC->gamma_size; + drmModeFreeCrtc(drmCRTC); + + if (!getDRMCRTCProps(gpu->fd, CRTC->id, &CRTC->props)) { + backend->log(AQ_LOG_ERROR, std::format("drm: getDRMCRTCProps for crtc {} failed", CRTC->id)); + drmModeFreeResources(resources); + crtcs.clear(); + return false; + } + + crtcs.emplace_back(CRTC); + } + + if (crtcs.size() > 32) { + backend->log(AQ_LOG_CRITICAL, "drm: Cannot support more than 32 CRTCs"); + return false; + } + + // initialize planes + auto planeResources = drmModeGetPlaneResources(gpu->fd); + if (!planeResources) { + backend->log(AQ_LOG_ERROR, std::format("drm: drmModeGetPlaneResources failed")); + return false; + } + + backend->log(AQ_LOG_DEBUG, std::format("drm: found {} planes", planeResources->count_planes)); + + for (uint32_t i = 0; i < planeResources->count_planes; ++i) { + auto id = planeResources->planes[i]; + auto plane = drmModeGetPlane(gpu->fd, id); + if (!plane) { + backend->log(AQ_LOG_ERROR, std::format("drm: drmModeGetPlane for plane {} failed", id)); + drmModeFreeResources(resources); + crtcs.clear(); + planes.clear(); + return false; + } + + auto aqPlane = makeShared(); + aqPlane->backend = self; + aqPlane->self = aqPlane; + if (!aqPlane->init((drmModePlane*)plane)) { + backend->log(AQ_LOG_ERROR, std::format("drm: aqPlane->init for plane {} failed", id)); + drmModeFreeResources(resources); + crtcs.clear(); + planes.clear(); + return false; + } + + drmModeFreePlane(plane); + } + + drmModeFreePlaneResources(planeResources); + drmModeFreeResources(resources); + return true; +} + +bool Aquamarine::CDRMBackend::grabFormats() { + // FIXME: do this properly maybe? + return true; +} + +bool Aquamarine::CDRMBackend::registerGPU(SP gpu_, SP primary_) { + gpu = gpu_; + primary = primary_; + + auto drmName = drmGetDeviceNameFromFd2(gpu->fd); + auto drmVer = drmGetVersion(gpu->fd); + + backend->log(AQ_LOG_DEBUG, std::format("drm: Starting backend for {}, with driver {}", drmName ? drmName : "unknown", drmVer->name ? drmVer->name : "unknown")); + + drmFreeVersion(drmVer); + + // FIXME: listen to change and remove events from session + + return true; +} + +eBackendType Aquamarine::CDRMBackend::type() { + return eBackendType::AQ_BACKEND_DRM; +} + +void Aquamarine::CDRMBackend::scanConnectors() { + backend->log(AQ_LOG_DEBUG, std::format("drm: Scanning connectors for {}", gpu->path)); + + auto resources = drmModeGetResources(gpu->fd); + if (!resources) { + backend->log(AQ_LOG_ERROR, std::format("drm: Scanning connectors for {} failed", gpu->path)); + return; + } + + for (size_t i = 0; i < resources->count_connectors; ++i) { + uint32_t connectorID = resources->connectors[i]; + + SP conn; + auto drmConn = drmModeGetConnector(gpu->fd, connectorID); + + backend->log(AQ_LOG_DEBUG, std::format("drm: Scanning connector id {}", connectorID)); + + if (!drmConn) { + backend->log(AQ_LOG_ERROR, std::format("drm: Failed to get connector id {}", connectorID)); + continue; + } + + auto it = std::find_if(connectors.begin(), connectors.end(), [connectorID](const auto& e) { return e->id == connectorID; }); + if (it == connectors.end()) { + backend->log(AQ_LOG_DEBUG, std::format("drm: Initializing connector id {}", connectorID)); + conn = connectors.emplace_back(SP(new SDRMConnector())); + conn->self = conn; + conn->backend = self; + if (!conn->init(drmConn)) { + backend->log(AQ_LOG_ERROR, std::format("drm: Connector id {} failed initializing", connectorID)); + connectors.pop_back(); + continue; + } + } else + conn = *it; + + backend->log(AQ_LOG_DEBUG, std::format("drm: Connector {} connection state:", (int)drmConn->connection)); + + if (conn->status == DRM_MODE_DISCONNECTED && drmConn->connection == DRM_MODE_CONNECTED) { + backend->log(AQ_LOG_DEBUG, std::format("drm: Connector {} connected", conn->szName)); + conn->connect(drmConn); + } else if (conn->status == DRM_MODE_CONNECTED && drmConn->connection == DRM_MODE_DISCONNECTED) { + backend->log(AQ_LOG_DEBUG, std::format("drm: Connector {} disconnected", conn->szName)); + conn->disconnect(); + } + + drmModeFreeConnector(drmConn); + } + + drmModeFreeResources(resources); +} + +bool Aquamarine::CDRMBackend::start() { + scanConnectors(); + + return true; +} + +int Aquamarine::CDRMBackend::pollFD() { + return gpu->fd; +} + +int Aquamarine::CDRMBackend::drmFD() { + return gpu->fd; +} + +static void handlePF(int fd, unsigned seq, unsigned tv_sec, unsigned tv_usec, unsigned crtc_id, void* data) { + // FIXME: +} + +bool Aquamarine::CDRMBackend::dispatchEvents() { + drmEventContext event = { + .version = 3, + .page_flip_handler2 = ::handlePF, + }; + + if (drmHandleEvent(gpu->fd, &event) != 0) + backend->log(AQ_LOG_ERROR, std::format("drm: Failed to handle event on fd {}", gpu->fd)); + + return true; +} + +uint32_t Aquamarine::CDRMBackend::capabilities() { + return eBackendCapabilities::AQ_BACKEND_CAPABILITY_POINTER; +} + +bool Aquamarine::CDRMBackend::setCursor(SP buffer, const Hyprutils::Math::Vector2D& hotspot) { + return false; +} + +void Aquamarine::CDRMBackend::onReady() { + ; +} + +std::vector Aquamarine::CDRMBackend::getRenderFormats() { + for (auto& p : planes) { + if (p->type != DRM_PLANE_TYPE_PRIMARY) + continue; + + return p->formats; + } + + return {}; +} + +std::vector Aquamarine::CDRMBackend::getCursorFormats() { + for (auto& p : planes) { + if (p->type != DRM_PLANE_TYPE_CURSOR) + continue; + + return p->formats; + } + + return {}; +} + +bool Aquamarine::SDRMPlane::init(drmModePlane* plane) { + id = plane->plane_id; + + if (!getDRMPlaneProps(backend->gpu->fd, id, &props)) + return false; + + if (!getDRMProp(backend->gpu->fd, id, props.type, &type)) + return false; + + initialID = id; + + for (size_t i = 0; i < plane->count_formats; ++i) { + if (type != DRM_PLANE_TYPE_CURSOR) + formats.emplace_back(SDRMFormat{.drmFormat = plane->formats[i], .modifiers = {DRM_FORMAT_MOD_LINEAR, DRM_FORMAT_MOD_INVALID}}); + else + formats.emplace_back(SDRMFormat{.drmFormat = plane->formats[i], .modifiers = {DRM_FORMAT_MOD_LINEAR}}); + } + + if (props.in_formats && backend->drmProps.supportsAddFb2Modifiers) { + uint64_t blobID = 0; + if (!getDRMProp(backend->gpu->fd, id, props.in_formats, &blobID)) + return false; + + auto blob = drmModeGetPropertyBlob(backend->gpu->fd, blobID); + if (!blob) + return false; + + drmModeFormatModifierIterator iter = {0}; + while (drmModeFormatModifierBlobIterNext(blob, &iter)) { + auto it = std::find_if(formats.begin(), formats.end(), [iter](const auto& e) { return e.drmFormat == iter.fmt; }); + + if (it == formats.end()) + formats.emplace_back(SDRMFormat{.drmFormat = iter.fmt, .modifiers = {iter.mod}}); + else + it->modifiers.emplace_back(iter.mod); + } + + drmModeFreePropertyBlob(blob); + } + + for (size_t i = 0; i < backend->crtcs.size(); ++i) { + uint32_t crtcBit = (1 << i); + if (!(plane->possible_crtcs & crtcBit)) + continue; + + auto CRTC = backend->crtcs.at(i); + if (type == DRM_PLANE_TYPE_PRIMARY && !CRTC->primary) { + CRTC->primary = self.lock(); + break; + } + + if (type == DRM_PLANE_TYPE_CURSOR && !CRTC->cursor) { + CRTC->cursor = self.lock(); + break; + } + } + + return true; +} + +SP Aquamarine::SDRMConnector::getCurrentCRTC(const drmModeConnector* connector) { + uint32_t crtcID = 0; + if (props.crtc_id) { + uint64_t value = 0; + if (!getDRMProp(backend->gpu->fd, id, props.crtc_id, &value)) { + backend->backend->log(AQ_LOG_ERROR, "drm: Failed to get CRTC_ID"); + return nullptr; + } + crtcID = static_cast(value); + } else if (connector->encoder_id) { + auto encoder = drmModeGetEncoder(backend->gpu->fd, connector->encoder_id); + if (!encoder) { + backend->backend->log(AQ_LOG_ERROR, "drm: drmModeGetEncoder failed"); + return nullptr; + } + crtcID = encoder->crtc_id; + drmModeFreeEncoder(encoder); + } else + return nullptr; + + auto it = std::find_if(backend->crtcs.begin(), backend->crtcs.end(), [crtcID](const auto& e) { return e->id == crtcID; }); + + if (it == backend->crtcs.end()) { + backend->backend->log(AQ_LOG_ERROR, std::format("drm: Failed to find a CRTC with ID {}", crtcID)); + return nullptr; + } + + return *it; +} + +bool Aquamarine::SDRMConnector::init(drmModeConnector* connector) { + id = connector->connector_id; + + if (!getDRMConnectorProps(backend->gpu->fd, id, &props)) + return false; + + auto name = drmModeGetConnectorTypeName(connector->connector_type); + if (!name) + name = "ERROR"; + + szName = std::format("{}-{}", name, connector->connector_type_id); + + possibleCrtcs = drmModeConnectorGetPossibleCrtcs(backend->gpu->fd, connector); + if (!possibleCrtcs) + backend->backend->log(AQ_LOG_ERROR, "drm: No CRTCs possible"); + + crtc = getCurrentCRTC(connector); + + return true; +} + +Aquamarine::SDRMConnector::~SDRMConnector() { + disconnect(); +} + +static int32_t calculateRefresh(const drmModeModeInfo& mode) { + int32_t refresh = (mode.clock * 1000000LL / mode.htotal + mode.vtotal / 2) / mode.vtotal; + + if (mode.flags & DRM_MODE_FLAG_INTERLACE) + refresh *= 2; + + if (mode.flags & DRM_MODE_FLAG_DBLSCAN) + refresh /= 2; + + if (mode.vscan > 1) + refresh /= mode.vscan; + + return refresh; +} + +drmModeModeInfo* Aquamarine::SDRMConnector::getCurrentMode() { + if (!crtc) + return nullptr; + + if (crtc->props.mode_id) { + size_t size = 0; + return (drmModeModeInfo*)getDRMPropBlob(backend->gpu->fd, crtc->id, crtc->props.mode_id, &size); + ; + } + + auto drmCrtc = drmModeGetCrtc(backend->gpu->fd, crtc->id); + if (!drmCrtc) + return nullptr; + if (!drmCrtc->mode_valid) { + drmModeFreeCrtc(drmCrtc); + return nullptr; + } + + drmModeModeInfo* modeInfo = (drmModeModeInfo*)malloc(sizeof(drmModeModeInfo)); + if (!modeInfo) { + drmModeFreeCrtc(drmCrtc); + return nullptr; + } + + *modeInfo = drmCrtc->mode; + drmModeFreeCrtc(drmCrtc); + + return modeInfo; +} + +void Aquamarine::SDRMConnector::parseEDID(std::vector data) { + // TODO: libdisplay-info prolly +} + +void Aquamarine::SDRMConnector::connect(drmModeConnector* connector) { + if (output) { + backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Not connecting connector {} because it's already connected", szName)); + return; + } + + backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Connecting connector {}, CRTC ID {}", szName, crtc ? crtc->id : -1)); + + output = SP(new CDRMOutput(szName, backend, self.lock())); + output->self = output; + output->connector = self.lock(); + + backend->backend->log(AQ_LOG_DEBUG, "drm: Dumping detected modes:"); + + auto currentModeInfo = getCurrentMode(); + + for (int i = 0; i < connector->count_modes; ++i) { + auto& drmMode = connector->modes[i]; + + if (drmMode.flags & DRM_MODE_FLAG_INTERLACE) { + backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Skipping mode {} because it's interlaced", i)); + continue; + } + + auto aqMode = makeShared(); + aqMode->pixelSize = {drmMode.hdisplay, drmMode.vdisplay}; + aqMode->refreshRate = calculateRefresh(drmMode); + aqMode->preferred = (drmMode.type & DRM_MODE_TYPE_PREFERRED); + + output->modes.emplace_back(aqMode); + + if (currentModeInfo && std::memcmp(&drmMode, currentModeInfo, sizeof(drmModeModeInfo))) { + output->state->setMode(aqMode); + + //uint64_t modeID = 0; + // getDRMProp(backend->gpu->fd, crtc->id, crtc->props.mode_id, &modeID); + + crtc->refresh = calculateRefresh(drmMode); + } + + backend->backend->log(AQ_LOG_DEBUG, + std::format("drm: Mode {}: {}x{}@{:.2f}Hz {}", i, (int)aqMode->pixelSize.x, (int)aqMode->pixelSize.y, aqMode->refreshRate / 1000.0, + aqMode->preferred ? " (preferred)" : "")); + } + + output->physicalSize = {(double)connector->mmWidth, (double)connector->mmHeight}; + + backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Physical size {} (mm)", output->physicalSize)); + + switch (connector->subpixel) { + case DRM_MODE_SUBPIXEL_NONE: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_NONE; break; + case DRM_MODE_SUBPIXEL_UNKNOWN: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_UNKNOWN; break; + case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_HORIZONTAL_RGB; break; + case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_HORIZONTAL_BGR; break; + case DRM_MODE_SUBPIXEL_VERTICAL_RGB: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_VERTICAL_RGB; break; + case DRM_MODE_SUBPIXEL_VERTICAL_BGR: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_VERTICAL_BGR; break; + default: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_UNKNOWN; + } + + uint64_t prop = 0; + if (getDRMProp(backend->gpu->fd, id, props.non_desktop, &prop)) { + if (prop == 1) + backend->backend->log(AQ_LOG_DEBUG, "drm: Non-desktop connector"); + output->nonDesktop = prop; + } + + maxBpcBounds.fill(0); + + if (props.max_bpc && !introspectDRMPropRange(backend->gpu->fd, props.max_bpc, maxBpcBounds.data(), &maxBpcBounds[1])) + backend->backend->log(AQ_LOG_ERROR, "drm: Failed to check max_bpc"); + + size_t edidLen = 0; + uint8_t* edidData = (uint8_t*)getDRMPropBlob(backend->gpu->fd, id, props.edid, &edidLen); + + std::vector edid{edidData, edidData + edidLen}; + parseEDID(edid); + + free(edidData); + edid = {}; + + // TODO: subconnectors + + output->make = make; + output->model = model; + output->serial = serial; + output->description = std::format("{} {} {} ({})", make, model, serial, szName); + + backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Description {}", output->description)); + + status = DRM_MODE_CONNECTED; + + backend->backend->events.newOutput.emit(output); +} + +void Aquamarine::SDRMConnector::disconnect() { + if (!output) { + backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Not disconnecting connector {} because it's already disconnected", szName)); + return; + } + + output->events.destroy.emit(); + output.reset(); + + status = DRM_MODE_DISCONNECTED; +} + +Aquamarine::CDRMOutput::~CDRMOutput() { + ; +} + +bool Aquamarine::CDRMOutput::commit() { + return true; +} + +bool Aquamarine::CDRMOutput::test() { + return true; +} + +SP Aquamarine::CDRMOutput::getBackend() { + return backend.lock(); +} + +bool Aquamarine::CDRMOutput::setCursor(SP buffer, const Vector2D& hotspot) { + return false; // FIXME: +} + +void Aquamarine::CDRMOutput::moveCursor(const Vector2D& coord) { + ; // FIXME: +} + +void Aquamarine::CDRMOutput::scheduleFrame() { + ; +} + +Vector2D Aquamarine::CDRMOutput::maxCursorSize() { + return backend->drmProps.cursorSize; +} + +Aquamarine::CDRMOutput::CDRMOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer backend_, Hyprutils::Memory::CSharedPointer connector_) : + backend(backend_), connector(connector_) { + name = name_; +} diff --git a/src/backend/drm/Props.cpp b/src/backend/drm/Props.cpp new file mode 100644 index 0000000..cd8bac1 --- /dev/null +++ b/src/backend/drm/Props.cpp @@ -0,0 +1,183 @@ +#include + +extern "C" { +#include +#include +#include +#include +} + +#include + +using namespace Aquamarine; +using namespace Hyprutils::Memory; +#define SP CSharedPointer + +struct prop_info { + const char* name; + size_t index; +}; + +static const struct prop_info connector_info[] = { +#define INDEX(name) (offsetof(SDRMConnector::UDRMConnectorProps, name) / sizeof(uint32_t)) + {"CRTC_ID", INDEX(crtc_id)}, + {"DPMS", INDEX(dpms)}, + {"EDID", INDEX(edid)}, + {"PATH", INDEX(path)}, + {"content type", INDEX(content_type)}, + {"link-status", INDEX(link_status)}, + {"max bpc", INDEX(max_bpc)}, + {"non-desktop", INDEX(non_desktop)}, + {"panel orientation", INDEX(panel_orientation)}, + {"subconnector", INDEX(subconnector)}, + {"vrr_capable", INDEX(vrr_capable)}, +#undef INDEX +}; + +static const struct prop_info crtc_info[] = { +#define INDEX(name) (offsetof(SDRMCRTC::UDRMCRTCProps, name) / sizeof(uint32_t)) + {"ACTIVE", INDEX(active)}, {"GAMMA_LUT", INDEX(gamma_lut)}, {"GAMMA_LUT_SIZE", INDEX(gamma_lut_size)}, {"MODE_ID", INDEX(mode_id)}, {"VRR_ENABLED", INDEX(vrr_enabled)}, +#undef INDEX +}; + +static const struct prop_info plane_info[] = { +#define INDEX(name) (offsetof(SDRMPlane::UDRMPlaneProps, name) / sizeof(uint32_t)) + {"CRTC_H", INDEX(crtc_h)}, {"CRTC_ID", INDEX(crtc_id)}, + {"CRTC_W", INDEX(crtc_w)}, {"CRTC_X", INDEX(crtc_x)}, + {"CRTC_Y", INDEX(crtc_y)}, {"FB_DAMAGE_CLIPS", INDEX(fb_damage_clips)}, + {"FB_ID", INDEX(fb_id)}, {"HOTSPOT_X", INDEX(hotspot_x)}, + {"HOTSPOT_Y", INDEX(hotspot_y)}, {"IN_FORMATS", INDEX(in_formats)}, + {"SRC_H", INDEX(src_h)}, {"SRC_W", INDEX(src_w)}, + {"SRC_X", INDEX(src_x)}, {"SRC_Y", INDEX(src_y)}, + {"rotation", INDEX(rotation)}, {"type", INDEX(type)}, +#undef INDEX +}; + +namespace Aquamarine { + + static int comparePropInfo(const void* arg1, const void* arg2) { + const char* key = (const char*)arg1; + const prop_info* elem = (prop_info*)arg2; + + return strcmp(key, elem->name); + } + + static bool scanProperties(int fd, uint32_t id, uint32_t type, uint32_t* result, const prop_info* info, size_t info_len) { + drmModeObjectProperties* props = drmModeObjectGetProperties(fd, id, type); + if (!props) + return false; + + for (uint32_t i = 0; i < props->count_props; ++i) { + drmModePropertyRes* prop = drmModeGetProperty(fd, props->props[i]); + if (!prop) + continue; + + const prop_info* p = (prop_info*)bsearch(prop->name, info, info_len, sizeof(info[0]), comparePropInfo); + if (p) + result[p->index] = prop->prop_id; + + drmModeFreeProperty(prop); + } + + drmModeFreeObjectProperties(props); + return true; + } + + bool getDRMConnectorProps(int fd, uint32_t id, SDRMConnector::UDRMConnectorProps* out) { + return scanProperties(fd, id, DRM_MODE_OBJECT_CONNECTOR, out->props, connector_info, sizeof(connector_info) / sizeof(connector_info[0])); + } + + bool getDRMCRTCProps(int fd, uint32_t id, SDRMCRTC::UDRMCRTCProps* out) { + return scanProperties(fd, id, DRM_MODE_OBJECT_CRTC, out->props, crtc_info, sizeof(crtc_info) / sizeof(crtc_info[0])); + } + + bool getDRMPlaneProps(int fd, uint32_t id, SDRMPlane::UDRMPlaneProps* out) { + return scanProperties(fd, id, DRM_MODE_OBJECT_PLANE, out->props, plane_info, sizeof(plane_info) / sizeof(plane_info[0])); + } + + bool getDRMProp(int fd, uint32_t obj, uint32_t prop, uint64_t* ret) { + drmModeObjectProperties* props = drmModeObjectGetProperties(fd, obj, DRM_MODE_OBJECT_ANY); + if (!props) + return false; + + bool found = false; + + for (uint32_t i = 0; i < props->count_props; ++i) { + if (props->props[i] == prop) { + *ret = props->prop_values[i]; + found = true; + break; + } + } + + drmModeFreeObjectProperties(props); + return found; + } + + void* getDRMPropBlob(int fd, uint32_t obj, uint32_t prop, size_t* ret_len) { + uint64_t blob_id; + if (!getDRMProp(fd, obj, prop, &blob_id)) + return nullptr; + + drmModePropertyBlobRes* blob = drmModeGetPropertyBlob(fd, blob_id); + if (!blob) + return nullptr; + + void* ptr = malloc(blob->length); + if (!ptr) { + drmModeFreePropertyBlob(blob); + return nullptr; + } + + memcpy(ptr, blob->data, blob->length); + *ret_len = blob->length; + + drmModeFreePropertyBlob(blob); + return ptr; + } + + char* getDRMPropEnum(int fd, uint32_t obj, uint32_t prop_id) { + uint64_t value; + if (!getDRMProp(fd, obj, prop_id, &value)) + return nullptr; + + drmModePropertyRes* prop = drmModeGetProperty(fd, prop_id); + if (!prop) + return nullptr; + + char* str = nullptr; + for (int i = 0; i < prop->count_enums; i++) { + if (prop->enums[i].value == value) { + str = strdup(prop->enums[i].name); + break; + } + } + + drmModeFreeProperty(prop); + + return str; + } + + bool introspectDRMPropRange(int fd, uint32_t prop_id, uint64_t* min, uint64_t* max) { + drmModePropertyRes* prop = drmModeGetProperty(fd, prop_id); + if (!prop) + return false; + + if (drmModeGetPropertyType(prop) != DRM_MODE_PROP_RANGE) { + drmModeFreeProperty(prop); + return false; + } + + if (prop->count_values != 2) + abort(); + + if (min != nullptr) + *min = prop->values[0]; + if (max != nullptr) + *max = prop->values[1]; + + drmModeFreeProperty(prop); + return true; + } + +}; diff --git a/src/backend/drm/Props.hpp b/src/backend/drm/Props.hpp new file mode 100644 index 0000000..24a7806 --- /dev/null +++ b/src/backend/drm/Props.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace Aquamarine { + bool getDRMConnectorProps(int fd, uint32_t id, SDRMConnector::UDRMConnectorProps* out); + bool getDRMCRTCProps(int fd, uint32_t id, SDRMCRTC::UDRMCRTCProps* out); + bool getDRMPlaneProps(int fd, uint32_t id, SDRMPlane::UDRMPlaneProps* out); + bool getDRMProp(int fd, uint32_t obj, uint32_t prop, uint64_t* ret); + void* getDRMPropBlob(int fd, uint32_t obj, uint32_t prop, size_t* ret_len); + char* getDRMPropEnum(int fd, uint32_t obj, uint32_t prop_id); + bool introspectDRMPropRange(int fd, uint32_t prop_id, uint64_t* min, uint64_t* max); +};