| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | /* | ||
| 3 | * This file is part of the Falcon Player (FPP) and is Copyright (C) | ||
| 4 | * 2013-2022 by the Falcon Player Developers. | ||
| 5 | * | ||
| 6 | * The Falcon Player (FPP) is free software, and is covered under | ||
| 7 | * multiple Open Source licenses. Please see the included 'LICENSES' | ||
| 8 | * file for descriptions of what files are covered by each license. | ||
| 9 | * | ||
| 10 | * This source file is covered under the LGPL v2.1 as described in the | ||
| 11 | * included LICENSE.LGPL file. | ||
| 12 | */ | ||
| 13 | |||
| 14 | #if __has_include(<xf86drm.h>) | ||
| 15 | #include "FrameBuffer.h" | ||
| 16 | #include <xf86drm.h> | ||
| 17 | #include <xf86drmMode.h> | ||
| 18 | #define HAS_KMS_FB | ||
| 19 | |||
| 20 | #include <map> | ||
| 21 | #include <set> | ||
| 22 | #include <string> | ||
| 23 | #include <vector> | ||
| 24 | |||
| 25 | struct DumbBuffer { | ||
| 26 | uint32_t handle = 0; | ||
| 27 | uint32_t fb_id = 0; | ||
| 28 | uint32_t width = 0; | ||
| 29 | uint32_t height = 0; | ||
| 30 | uint32_t stride = 0; | ||
| 31 | uint32_t format = 0; | ||
| 32 | uint64_t size = 0; | ||
| 33 | void* mapped = nullptr; | ||
| 34 | }; | ||
| 35 | |||
| 36 | struct CardInfo { | ||
| 37 | int fd = -1; | ||
| 38 | std::string path; | ||
| 39 | std::vector<uint32_t> reservedConnectors; | ||
| 40 | std::vector<uint32_t> reservedCrtcs; | ||
| 41 | std::vector<uint32_t> reservedPlanes; | ||
| 42 | }; | ||
| 43 | |||
| 44 | class KMSFrameBuffer : public FrameBuffer { | ||
| 45 | public: | ||
| 46 | KMSFrameBuffer(); | ||
| 47 | virtual ~KMSFrameBuffer(); | ||
| 48 | |||
| 49 | virtual int InitializeFrameBuffer() override; | ||
| 50 | virtual void DestroyFrameBuffer() override; | ||
| 51 | virtual void SyncLoop() override; | ||
| 52 | virtual void SyncDisplay(bool pageChanged = false) override; | ||
| 53 | virtual void WaitForPageFree() override; | ||
| 54 | |||
| 55 | virtual void EnableDisplay() override; | ||
| 56 | virtual void DisableDisplay() override; | ||
| 57 | |||
| 58 | 13 | virtual bool SupportsVariableRefresh() override { return m_variableRefresh; } | |
| 59 | virtual int GetMaxRefreshRate() override; | ||
| 60 | virtual bool SetRefreshRate(int fps) override; | ||
| 61 | |||
| 62 | int m_cardFd = -1; | ||
| 63 | uint32_t m_connectorId = 0; | ||
| 64 | uint32_t m_crtcId = 0; | ||
| 65 | int m_crtcPipe = 0; // index of m_crtcId in the card's crtc list (vblank pipe) | ||
| 66 | uint32_t m_planeId = 0; | ||
| 67 | std::string m_connectorName; | ||
| 68 | drmModeModeInfo m_mode = {}; | ||
| 69 | DumbBuffer m_fb[3] = {}; | ||
| 70 | bool m_displayEnabled = false; | ||
| 71 | |||
| 72 | static std::atomic_int FRAMEBUFFER_COUNT; | ||
| 73 | static std::vector<CardInfo*> CARDS; | ||
| 74 | |||
| 75 | private: | ||
| 76 | static std::string ConnectorFullName(int fd, drmModeConnectorPtr conn); | ||
| 77 | static bool CreateDumbBuffer(int fd, uint32_t width, uint32_t height, uint32_t format, DumbBuffer& buf); | ||
| 78 | static void DestroyDumbBuffer(int fd, DumbBuffer& buf); | ||
| 79 | uint32_t FindPlaneForCrtc(int fd, uint32_t crtcId, uint32_t format); | ||
| 80 | |||
| 81 | // Rewrites the vertical timing of `mode` for `vactive` scanlines at `fps`, | ||
| 82 | // keeping the horizontal timing and pixel clock (and thus the DPI/WS bit | ||
| 83 | // timing) unchanged. Requesting an fps above what the vactive allows is | ||
| 84 | // clamped to the minimum vertical blanking (i.e. the maximum rate). | ||
| 85 | void ApplyVerticalTiming(drmModeModeInfo& mode, int vactive, int fps); | ||
| 86 | |||
| 87 | // Drain the completion event of an outstanding (m_flipPending) page flip. | ||
| 88 | // Blocks in poll() until the flip retires at vblank (or timeoutMs elapses), | ||
| 89 | // so it costs no CPU while waiting. | ||
| 90 | void WaitForPendingFlip(int timeoutMs); | ||
| 91 | static void PageFlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void* data); | ||
| 92 | static void VBlankHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void* data); | ||
| 93 | |||
| 94 | // What a queued page flip carries back to PageFlipHandler() instead of | ||
| 95 | // "this". Instances on one card share its fd, so any of them can drain an | ||
| 96 | // event queued by another - including one queued by an instance that has | ||
| 97 | // since been deleted. The handler resolves the token through FLIP_TARGETS | ||
| 98 | // and drops one that no longer names a live instance. Never reused. | ||
| 99 | uint32_t m_flipToken = 0; | ||
| 100 | static std::map<uint32_t, KMSFrameBuffer*> FLIP_TARGETS; // guarded by drmMasterLock | ||
| 101 | static uint32_t NEXT_FLIP_TOKEN; // guarded by drmMasterLock | ||
| 102 | |||
| 103 | // True while a drmModePageFlip has been queued but its vblank completion | ||
| 104 | // event has not yet been drained. Guarded by drmMasterLock (only touched | ||
| 105 | // from SyncDisplay / WaitForPendingFlip, both of which hold that lock). | ||
| 106 | bool m_flipPending = false; | ||
| 107 | |||
| 108 | // Flip-path health counters (guarded by drmMasterLock like m_flipPending). | ||
| 109 | // Logged rate-limited at Warn so brief output hitches are visible without | ||
| 110 | // debug logging: a stutter on the panel with these counters unchanged means | ||
| 111 | // the delay is NOT in the KMS flip path. | ||
| 112 | uint32_t m_flipWaitTimeouts = 0; | ||
| 113 | uint32_t m_flipRejects = 0; | ||
| 114 | uint32_t m_slowSyncs = 0; | ||
| 115 | int m_maxSyncMS = 0; | ||
| 116 | int m_lastSlowSyncMS = 0; | ||
| 117 | long long m_lastFlipWarnMS = 0; | ||
| 118 | // Totals as of the last warning, so we only log when something new | ||
| 119 | // happened (a warning line then timestamps the event itself). | ||
| 120 | uint64_t m_warnedEventTotal = 0; | ||
| 121 | |||
| 122 | // Vblank cadence tracking. Each flip completion event carries the vblank | ||
| 123 | // sequence number; at a steady output rate the delta between consecutive | ||
| 124 | // flips is constant (e.g. 2 at 20fps on a 40Hz panel). If the software | ||
| 125 | // frame clock beats against the panel clock, the delta oscillates (1,3, | ||
| 126 | // 1,3,...) around each phase crossing - visible as a brief judder on the | ||
| 127 | // pixels even though every software metric looks perfect. | ||
| 128 | uint32_t m_lastFlipSeq = 0; | ||
| 129 | uint32_t m_vblankDeltaCounts[5] = { 0, 0, 0, 0, 0 }; // [0]=unused, 1..3, [4]=4+ | ||
| 130 | uint32_t m_vblankCadenceBreaks = 0; // delta changed vs previous delta | ||
| 131 | uint32_t m_prevVblankDelta = 0; | ||
| 132 | // Vblank timestamp (monotonic us) of the last completed flip, used to | ||
| 133 | // phase-align flip submission away from vblank boundaries. | ||
| 134 | uint64_t m_lastFlipTimeUS = 0; | ||
| 135 | uint32_t m_phaseNudges = 0; | ||
| 136 | |||
| 137 | CardInfo* m_cardInfo = nullptr; | ||
| 138 | |||
| 139 | // Display hotplug. A KMS client that is DRM master owns recovery after a | ||
| 140 | // sink disconnects and comes back: the kernel keeps the CRTC scanning out | ||
| 141 | // and keeps accepting page flips, but nothing restarts the link, and some | ||
| 142 | // sinks (projectors in particular) stay black until a full modeset. A | ||
| 143 | // thread listens for the kernel's DRM hotplug uevents, re-probes this | ||
| 144 | // output's connector, and redoes the modeset when the sink returns. | ||
| 145 | static void StartHotplugMonitor(); | ||
| 146 | static void StopHotplugMonitor(); | ||
| 147 | static void HotplugMonitorLoop(int sock, int wakeFd); | ||
| 148 | // onlyAbsent: re-probe just the outputs that have no display (the periodic | ||
| 149 | // recheck), leaving the others - and their EDID reads - alone. | ||
| 150 | static void HandleHotplug(const std::string& cardPath, const std::set<uint32_t>& connectorIds, bool onlyAbsent); | ||
| 151 | static bool SinkPresent(int fd, drmModeConnectorPtr conn); | ||
| 152 | static bool PickMode(drmModeConnectorPtr conn, drmModeModeInfo& mode); | ||
| 153 | static drmModeConnectorPtr ProbeConnectorLocked(int fd, uint32_t connectorId); | ||
| 154 | // Full off/on modeset of this output; caller holds drmMasterLock. | ||
| 155 | void RestartDisplayLocked(); | ||
| 156 | |||
| 157 | // A media pipeline's kmssink holds this connector (DrmCards.h). | ||
| 158 | bool MediaHoldsConnector() const; | ||
| 159 | // While it does, our plane is off: hidden under the video it would still | ||
| 160 | // count against the display load the video needs. Where the driver won't | ||
| 161 | // take it off, it shows this black buffer instead, made on first use. | ||
| 162 | // m_mediaBlanked says we stood aside. Both under drmMasterLock. | ||
| 163 | DumbBuffer m_blankFb = {}; | ||
| 164 | bool m_mediaBlanked = false; | ||
| 165 | void BlankForMediaLocked(); | ||
| 166 | void ShowAfterMediaLocked(); | ||
| 167 | // DrmCards' media-hold listener: a lease on connectorId was taken or given back. | ||
| 168 | static void MediaHoldChanged(int fd, uint32_t connectorId); | ||
| 169 | // Light the CRTC in m_mode and show the current page on our plane. Caller | ||
| 170 | // holds drmMasterLock and DRM master. Returns the plane commit's result. | ||
| 171 | int ModesetLocked(); | ||
| 172 | |||
| 173 | // Set (under drmMasterLock) once InitializeFrameBuffer() has filled in the | ||
| 174 | // card/connector/CRTC, cleared again before DestroyFrameBuffer() releases | ||
| 175 | // them; the hotplug thread only looks at instances with it set. | ||
| 176 | bool m_hotplugReady = false; | ||
| 177 | // Whether a display was there at the last probe (see SinkPresent()). | ||
| 178 | // False from the start when the output came up without one, running the | ||
| 179 | // stand-in mode. | ||
| 180 | bool m_sinkPresent = false; // guarded by drmMasterLock | ||
| 181 | // Whether the "no display" warning is up for this output; moves with | ||
| 182 | // m_sinkPresent, both only through SetSinkPresentLocked(). | ||
| 183 | bool m_noDisplayWarned = false; // guarded by drmMasterLock | ||
| 184 | void SetSinkPresentLocked(bool present); | ||
| 185 | }; | ||
| 186 | |||
| 187 | #endif | ||
| 188 |