GCC Code Coverage Report


Directory: ./
File: framebuffer/KMSFrameBuffer.h
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 1 1 100.0%
Functions: 1 1 100.0%
Branches: 0 0 -%

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