| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * This file is part of the Falcon Player (FPP) and is Copyright (C) | ||
| 3 | * 2013-2022 by the Falcon Player Developers. | ||
| 4 | * | ||
| 5 | * The Falcon Player (FPP) is free software, and is covered under | ||
| 6 | * multiple Open Source licenses. Please see the included 'LICENSES' | ||
| 7 | * file for descriptions of what files are covered by each license. | ||
| 8 | * | ||
| 9 | * This source file is covered under the LGPL v2.1 as described in the | ||
| 10 | * included LICENSE.LGPL file. | ||
| 11 | */ | ||
| 12 | #include "fpp-pch.h" | ||
| 13 | |||
| 14 | #include "KMSFrameBuffer.h" | ||
| 15 | |||
| 16 | #ifdef HAS_KMS_FB | ||
| 17 | #include <libdrm/drm.h> | ||
| 18 | #include <libdrm/drm_fourcc.h> | ||
| 19 | #include <sys/ioctl.h> | ||
| 20 | #include <sys/mman.h> | ||
| 21 | #include <cmath> | ||
| 22 | #include <cstring> | ||
| 23 | #include <cerrno> | ||
| 24 | #include <fcntl.h> | ||
| 25 | #include <linux/netlink.h> | ||
| 26 | #include <poll.h> | ||
| 27 | #include <sys/eventfd.h> | ||
| 28 | #include <sys/socket.h> | ||
| 29 | #include <unistd.h> | ||
| 30 | #include <algorithm> | ||
| 31 | #include <thread> | ||
| 32 | |||
| 33 | #include "../common.h" | ||
| 34 | #include "../common_mini.h" | ||
| 35 | #include "../Warnings.h" | ||
| 36 | #include "../log.h" | ||
| 37 | #include "../mediaoutput/DrmCards.h" | ||
| 38 | #include "../mediaoutput/mediaoutput.h" | ||
| 39 | |||
| 40 | std::atomic_int KMSFrameBuffer::FRAMEBUFFER_COUNT(0); | ||
| 41 | std::vector<CardInfo*> KMSFrameBuffer::CARDS; | ||
| 42 | std::map<uint32_t, KMSFrameBuffer*> KMSFrameBuffer::FLIP_TARGETS; | ||
| 43 | uint32_t KMSFrameBuffer::NEXT_FLIP_TOKEN = 0; | ||
| 44 | |||
| 45 | 23 | std::string KMSFrameBuffer::ConnectorFullName(int fd, drmModeConnectorPtr conn) { | |
| 46 | 23 | const char* typeName = drmModeGetConnectorTypeName(conn->connector_type); | |
| 47 |
2/4✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
|
40 | std::string name = typeName ? typeName : "Unknown"; |
| 48 |
2/4✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
|
23 | name += "-" + std::to_string(conn->connector_type_id); |
| 49 | 23 | return name; | |
| 50 | ✗ | } | |
| 51 | |||
| 52 | // A plugged-in display: HDMI, DVI, DisplayPort and VGA can be unplugged, and a | ||
| 53 | // sink on them answers with an EDID. Panels (DPI, DSI, ...) are there for good. | ||
| 54 | 42 | static bool IsExternalConnector(uint32_t type) { | |
| 55 |
2/2✓ Branch 0 taken 14 times.
✓ Branch 1 taken 28 times.
|
42 | switch (type) { |
| 56 | 14 | case DRM_MODE_CONNECTOR_HDMIA: | |
| 57 | case DRM_MODE_CONNECTOR_HDMIB: | ||
| 58 | case DRM_MODE_CONNECTOR_DVII: | ||
| 59 | case DRM_MODE_CONNECTOR_DVID: | ||
| 60 | case DRM_MODE_CONNECTOR_DVIA: | ||
| 61 | case DRM_MODE_CONNECTOR_DisplayPort: | ||
| 62 | case DRM_MODE_CONNECTOR_VGA: | ||
| 63 | 14 | return true; | |
| 64 | 28 | default: | |
| 65 | 28 | return false; | |
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | // Whether a display is actually on the connector. For an external connector | ||
| 70 | // that takes an EDID: vc4.force_hotplug (the ForceHDMI setting) pins it to | ||
| 71 | // "connected" and the kernel then makes up a few modes (up to 1024x768) even | ||
| 72 | // with nothing plugged in. | ||
| 73 | 24 | bool KMSFrameBuffer::SinkPresent(int fd, drmModeConnectorPtr conn) { | |
| 74 |
4/6✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 20 times.
|
24 | if (!conn || conn->connection != DRM_MODE_CONNECTED || conn->count_modes == 0) { |
| 75 | 4 | return false; | |
| 76 | } | ||
| 77 |
2/2✓ Branch 0 taken 14 times.
✓ Branch 1 taken 6 times.
|
20 | if (!IsExternalConnector(conn->connector_type)) { |
| 78 | 14 | return true; | |
| 79 | } | ||
| 80 | 6 | bool edid = false; | |
| 81 |
4/4✓ Branch 0 taken 7 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 1 times.
|
12 | for (int i = 0; i < conn->count_props && !edid; i++) { |
| 82 | 6 | drmModePropertyPtr prop = drmModeGetProperty(fd, conn->props[i]); | |
| 83 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | if (prop) { |
| 84 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
6 | edid = !strcmp(prop->name, "EDID") && conn->prop_values[i] != 0; |
| 85 | 6 | drmModeFreeProperty(prop); | |
| 86 | } | ||
| 87 | } | ||
| 88 | 6 | return edid; | |
| 89 | } | ||
| 90 | |||
| 91 | // The mode forced with video=<connector>:<mode> on the kernel command line, | ||
| 92 | // which is what the Force HDMI Resolution settings write. The kernel flags | ||
| 93 | // the matching probed mode USERDEF, or adds one of its own if none matches. | ||
| 94 | 22 | static bool CommandLineMode(drmModeConnectorPtr conn, drmModeModeInfo& mode) { | |
| 95 |
3/4✓ Branch 0 taken 97 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 75 times.
✓ Branch 3 taken 22 times.
|
97 | for (int mi = 0; conn && mi < conn->count_modes; mi++) { |
| 96 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 75 times.
|
75 | if (conn->modes[mi].type & DRM_MODE_TYPE_USERDEF) { |
| 97 | ✗ | mode = conn->modes[mi]; | |
| 98 | ✗ | return true; | |
| 99 | } | ||
| 100 | } | ||
| 101 | 22 | return false; | |
| 102 | } | ||
| 103 | |||
| 104 | // The mode forced on the kernel command line, else the display's preferred | ||
| 105 | // mode, else its first. Not the preferred mode first: with two displays that | ||
| 106 | // prefer different modes, that ran each in its own one whatever was forced. | ||
| 107 | 20 | bool KMSFrameBuffer::PickMode(drmModeConnectorPtr conn, drmModeModeInfo& mode) { | |
| 108 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
|
20 | if (CommandLineMode(conn, mode)) { |
| 109 | ✗ | return true; | |
| 110 | } | ||
| 111 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | for (int mi = 0; mi < conn->count_modes; mi++) { |
| 112 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | if (conn->modes[mi].type & DRM_MODE_TYPE_PREFERRED) { |
| 113 | 20 | mode = conn->modes[mi]; | |
| 114 | 20 | return true; | |
| 115 | } | ||
| 116 | } | ||
| 117 | ✗ | if (conn->count_modes > 0) { | |
| 118 | ✗ | mode = conn->modes[0]; | |
| 119 | ✗ | return true; | |
| 120 | } | ||
| 121 | ✗ | return false; | |
| 122 | } | ||
| 123 | |||
| 124 | // What an external connector runs until a display shows up: CEA-861 1080p60, | ||
| 125 | // which any HDMI sink takes. | ||
| 126 | 2 | static drmModeModeInfo FallbackMode() { | |
| 127 | 2 | drmModeModeInfo m = {}; | |
| 128 | 2 | m.clock = 148500; | |
| 129 | 2 | m.hdisplay = 1920; | |
| 130 | 2 | m.hsync_start = 2008; | |
| 131 | 2 | m.hsync_end = 2052; | |
| 132 | 2 | m.htotal = 2200; | |
| 133 | 2 | m.vdisplay = 1080; | |
| 134 | 2 | m.vsync_start = 1084; | |
| 135 | 2 | m.vsync_end = 1089; | |
| 136 | 2 | m.vtotal = 1125; | |
| 137 | 2 | m.vrefresh = 60; | |
| 138 | 2 | m.flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC; | |
| 139 | 2 | m.type = DRM_MODE_TYPE_DRIVER; | |
| 140 | 2 | snprintf(m.name, sizeof(m.name), "1920x1080"); | |
| 141 | 2 | return m; | |
| 142 | } | ||
| 143 | |||
| 144 | // "1920x1080@60 148500kHz h 1920/2008/2052/2200 v 1080/1084/1089/1125 flags 0x5" | ||
| 145 | ✗ | static std::string ModeLine(const drmModeModeInfo& m) { | |
| 146 | char buf[160]; | ||
| 147 | ✗ | snprintf(buf, sizeof(buf), "%dx%d@%d %ukHz h %d/%d/%d/%d v %d/%d/%d/%d flags 0x%x", | |
| 148 | ✗ | m.hdisplay, m.vdisplay, m.vrefresh, m.clock, m.hdisplay, m.hsync_start, m.hsync_end, m.htotal, | |
| 149 | ✗ | m.vdisplay, m.vsync_start, m.vsync_end, m.vtotal, m.flags); | |
| 150 | ✗ | return buf; | |
| 151 | } | ||
| 152 | |||
| 153 | ✗ | static bool SameTiming(const drmModeModeInfo& a, const drmModeModeInfo& b) { | |
| 154 | ✗ | return a.clock == b.clock && a.hdisplay == b.hdisplay && a.hsync_start == b.hsync_start && | |
| 155 | ✗ | a.hsync_end == b.hsync_end && a.htotal == b.htotal && a.vdisplay == b.vdisplay && | |
| 156 | ✗ | a.vsync_start == b.vsync_start && a.vsync_end == b.vsync_end && a.vtotal == b.vtotal && | |
| 157 | ✗ | a.flags == b.flags; | |
| 158 | } | ||
| 159 | |||
| 160 | // Up while an output is running with no display on its connector, which on a | ||
| 161 | // show night usually means a projector that is off or lost its signal. Comes | ||
| 162 | // down when the display appears and when the output is torn down; built by one | ||
| 163 | // function so the add and the remove always match. | ||
| 164 | static constexpr int WARNING_ID_NO_DISPLAY = 71; | ||
| 165 | 2 | static std::string NoDisplayWarning(const std::string& connector) { | |
| 166 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | return "Video output " + connector + ": no display detected - check that the projector or monitor is on and connected"; |
| 167 | } | ||
| 168 | |||
| 169 | // The flag and the banner move together; caller holds drmMasterLock. | ||
| 170 | 24 | void KMSFrameBuffer::SetSinkPresentLocked(bool present) { | |
| 171 | 24 | m_sinkPresent = present; | |
| 172 |
4/4✓ Branch 0 taken 4 times.
✓ Branch 1 taken 20 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
|
24 | if (!present && !m_noDisplayWarned) { |
| 173 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | WarningHolder::AddWarning(WARNING_ID_NO_DISPLAY, NoDisplayWarning(m_connectorName)); |
| 174 | 2 | m_noDisplayWarned = true; | |
| 175 |
3/4✓ Branch 0 taken 20 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 20 times.
|
22 | } else if (present && m_noDisplayWarned) { |
| 176 | ✗ | WarningHolder::RemoveWarning(WARNING_ID_NO_DISPLAY, NoDisplayWarning(m_connectorName)); | |
| 177 | ✗ | m_noDisplayWarned = false; | |
| 178 | } | ||
| 179 | 24 | } | |
| 180 | |||
| 181 | // Only the DRM master gets a real probe; for anyone else the kernel reads back | ||
| 182 | // its cached state, which after a hotplug can be "connected" with no modes - | ||
| 183 | // the HPD interrupt updates the status but not the mode list. The real probe | ||
| 184 | // reads the EDID over DDC and also refreshes the sysfs mode list. The card's | ||
| 185 | // shared fd is master (see DrmCards.h). Caller holds drmMasterLock. | ||
| 186 | 24 | drmModeConnectorPtr KMSFrameBuffer::ProbeConnectorLocked(int fd, uint32_t connectorId) { | |
| 187 | 24 | DrmCards::EnsureMaster(fd); | |
| 188 | 24 | return drmModeGetConnector(fd, connectorId); | |
| 189 | } | ||
| 190 | |||
| 191 | 53 | bool KMSFrameBuffer::CreateDumbBuffer(int fd, uint32_t width, uint32_t height, uint32_t format, DumbBuffer& buf) { | |
| 192 | uint32_t bpp; | ||
| 193 |
2/2✓ Branch 0 taken 44 times.
✓ Branch 1 taken 9 times.
|
53 | if (format == DRM_FORMAT_RGB888) { |
| 194 | 44 | bpp = 24; | |
| 195 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | } else if (format == DRM_FORMAT_RGB565) { |
| 196 | 9 | bpp = 16; | |
| 197 | } else { | ||
| 198 | ✗ | bpp = 32; | |
| 199 | } | ||
| 200 | |||
| 201 | 53 | struct drm_mode_create_dumb creq = {}; | |
| 202 | 53 | creq.width = width; | |
| 203 | 53 | creq.height = height; | |
| 204 | 53 | creq.bpp = bpp; | |
| 205 | |||
| 206 |
2/4✓ Branch 0 taken 53 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 53 times.
|
53 | if (drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq) < 0) { |
| 207 | ✗ | return false; | |
| 208 | } | ||
| 209 | |||
| 210 | 53 | uint32_t handles[4] = { creq.handle, 0, 0, 0 }; | |
| 211 | 53 | uint32_t pitches[4] = { creq.pitch, 0, 0, 0 }; | |
| 212 | 53 | uint32_t offsets[4] = { 0, 0, 0, 0 }; | |
| 213 | 53 | uint32_t fb_id = 0; | |
| 214 | |||
| 215 |
2/4✓ Branch 0 taken 53 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 53 times.
|
53 | if (drmModeAddFB2(fd, width, height, format, handles, pitches, offsets, &fb_id, 0) < 0) { |
| 216 | ✗ | struct drm_mode_destroy_dumb dreq = {}; | |
| 217 | ✗ | dreq.handle = creq.handle; | |
| 218 | ✗ | drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); | |
| 219 | ✗ | return false; | |
| 220 | } | ||
| 221 | |||
| 222 | 53 | struct drm_mode_map_dumb mreq = {}; | |
| 223 | 53 | mreq.handle = creq.handle; | |
| 224 |
2/4✓ Branch 0 taken 53 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 53 times.
|
53 | if (drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq) < 0) { |
| 225 | ✗ | drmModeRmFB(fd, fb_id); | |
| 226 | ✗ | struct drm_mode_destroy_dumb dreq = {}; | |
| 227 | ✗ | dreq.handle = creq.handle; | |
| 228 | ✗ | drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); | |
| 229 | ✗ | return false; | |
| 230 | } | ||
| 231 | |||
| 232 | 53 | void* mapped = mmap(nullptr, creq.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mreq.offset); | |
| 233 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 53 times.
|
53 | if (mapped == MAP_FAILED) { |
| 234 | ✗ | drmModeRmFB(fd, fb_id); | |
| 235 | ✗ | struct drm_mode_destroy_dumb dreq = {}; | |
| 236 | ✗ | dreq.handle = creq.handle; | |
| 237 | ✗ | drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); | |
| 238 | ✗ | return false; | |
| 239 | } | ||
| 240 | |||
| 241 | 53 | buf.handle = creq.handle; | |
| 242 | 53 | buf.fb_id = fb_id; | |
| 243 | 53 | buf.width = width; | |
| 244 | 53 | buf.height = height; | |
| 245 | 53 | buf.stride = creq.pitch; | |
| 246 | 53 | buf.format = format; | |
| 247 | 53 | buf.size = creq.size; | |
| 248 | 53 | buf.mapped = mapped; | |
| 249 | 53 | return true; | |
| 250 | } | ||
| 251 | |||
| 252 | 45 | void KMSFrameBuffer::DestroyDumbBuffer(int fd, DumbBuffer& buf) { | |
| 253 |
1/2✓ Branch 0 taken 45 times.
✗ Branch 1 not taken.
|
45 | if (buf.mapped) { |
| 254 | 45 | munmap(buf.mapped, buf.size); | |
| 255 | 45 | buf.mapped = nullptr; | |
| 256 | } | ||
| 257 |
1/2✓ Branch 0 taken 45 times.
✗ Branch 1 not taken.
|
45 | if (buf.fb_id) { |
| 258 | 45 | drmModeRmFB(fd, buf.fb_id); | |
| 259 | 45 | buf.fb_id = 0; | |
| 260 | } | ||
| 261 |
1/2✓ Branch 0 taken 45 times.
✗ Branch 1 not taken.
|
45 | if (buf.handle) { |
| 262 | 45 | struct drm_mode_destroy_dumb dreq = {}; | |
| 263 | 45 | dreq.handle = buf.handle; | |
| 264 |
1/2✓ Branch 0 taken 45 times.
✗ Branch 1 not taken.
|
45 | drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); |
| 265 | 45 | buf.handle = 0; | |
| 266 | } | ||
| 267 | 45 | buf = {}; | |
| 268 | 45 | } | |
| 269 | |||
| 270 | 22 | uint32_t KMSFrameBuffer::FindPlaneForCrtc(int fd, uint32_t crtcId, uint32_t format) { | |
| 271 | 22 | drmModeResPtr res = drmModeGetResources(fd); | |
| 272 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
|
22 | if (!res) { |
| 273 | ✗ | return 0; | |
| 274 | } | ||
| 275 | |||
| 276 | 22 | int crtcIndex = -1; | |
| 277 |
1/2✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
|
29 | for (int i = 0; i < res->count_crtcs; i++) { |
| 278 |
2/2✓ Branch 0 taken 22 times.
✓ Branch 1 taken 7 times.
|
29 | if (res->crtcs[i] == crtcId) { |
| 279 | 22 | crtcIndex = i; | |
| 280 | 22 | break; | |
| 281 | } | ||
| 282 | } | ||
| 283 | 22 | drmModeFreeResources(res); | |
| 284 | |||
| 285 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
|
22 | if (crtcIndex < 0) { |
| 286 | ✗ | return 0; | |
| 287 | } | ||
| 288 | |||
| 289 | 22 | drmModePlaneResPtr planes = drmModeGetPlaneResources(fd); | |
| 290 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
|
22 | if (!planes) { |
| 291 | ✗ | return 0; | |
| 292 | } | ||
| 293 | |||
| 294 | 22 | uint32_t result = 0; | |
| 295 |
3/4✓ Branch 0 taken 53 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
✓ Branch 3 taken 22 times.
|
53 | for (uint32_t i = 0; i < planes->count_planes && result == 0; i++) { |
| 296 | 31 | drmModePlanePtr plane = drmModeGetPlane(fd, planes->planes[i]); | |
| 297 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
|
31 | if (!plane) { |
| 298 | ✗ | continue; | |
| 299 | } | ||
| 300 | |||
| 301 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 24 times.
|
31 | if (!(plane->possible_crtcs & (1u << crtcIndex))) { |
| 302 | 7 | drmModeFreePlane(plane); | |
| 303 | 7 | continue; | |
| 304 | } | ||
| 305 | |||
| 306 | // Check if already reserved | ||
| 307 | 24 | bool reserved = false; | |
| 308 |
2/2✓ Branch 0 taken 27 times.
✓ Branch 1 taken 24 times.
|
51 | for (auto* card : CARDS) { |
| 309 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
|
27 | for (auto pid : card->reservedPlanes) { |
| 310 | ✗ | if (pid == plane->plane_id) { | |
| 311 | ✗ | reserved = true; | |
| 312 | ✗ | break; | |
| 313 | } | ||
| 314 | } | ||
| 315 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
|
27 | if (reserved) break; |
| 316 | } | ||
| 317 | // Or that a media pipeline's lease holds. | ||
| 318 |
5/6✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 22 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 22 times.
|
24 | if (reserved || DrmCards::InUse(fd, plane->plane_id)) { |
| 319 | 2 | drmModeFreePlane(plane); | |
| 320 | 2 | continue; | |
| 321 | } | ||
| 322 | |||
| 323 |
1/2✓ Branch 0 taken 96 times.
✗ Branch 1 not taken.
|
96 | for (uint32_t j = 0; j < plane->count_formats; j++) { |
| 324 |
2/2✓ Branch 0 taken 22 times.
✓ Branch 1 taken 74 times.
|
96 | if (plane->formats[j] == format) { |
| 325 | 22 | result = plane->plane_id; | |
| 326 | 22 | break; | |
| 327 | } | ||
| 328 | } | ||
| 329 | 22 | drmModeFreePlane(plane); | |
| 330 | } | ||
| 331 | 22 | drmModeFreePlaneResources(planes); | |
| 332 | 22 | return result; | |
| 333 | } | ||
| 334 | |||
| 335 | 23 | KMSFrameBuffer::KMSFrameBuffer() { | |
| 336 |
1/2✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
|
23 | LogDebug(VB_CHANNELOUT, "KMSFrameBuffer::KMSFrameBuffer()\n"); |
| 337 | |||
| 338 |
1/2✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
|
23 | if (FRAMEBUFFER_COUNT == 0) { |
| 339 |
2/2✓ Branch 0 taken 230 times.
✓ Branch 1 taken 23 times.
|
253 | for (int cn = 0; cn < 10; cn++) { |
| 340 |
1/2✓ Branch 0 taken 230 times.
✗ Branch 1 not taken.
|
230 | std::string path = "/dev/dri/card" + std::to_string(cn); |
| 341 |
3/4✓ Branch 0 taken 230 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✓ Branch 3 taken 205 times.
|
230 | if (FileExists(path)) { |
| 342 | // The card's one fd for all of fppd, which stays DRM master: | ||
| 343 | // media pipelines get leases from it rather than a master of | ||
| 344 | // their own, so nothing has to take turns (DrmCards.h). | ||
| 345 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | int fd = DrmCards::Acquire(path); |
| 346 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 25 times.
|
25 | if (fd < 0) { |
| 347 | ✗ | continue; | |
| 348 | } | ||
| 349 |
2/4✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 25 times.
|
25 | CardInfo* info = new CardInfo(); |
| 350 | 25 | info->fd = fd; | |
| 351 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | info->path = path; |
| 352 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | CARDS.push_back(info); |
| 353 | } | ||
| 354 |
1/2✓ Branch 0 taken 230 times.
✗ Branch 1 not taken.
|
230 | } |
| 355 |
1/2✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
|
23 | StartHotplugMonitor(); |
| 356 |
1/2✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
|
23 | DrmCards::SetMediaHoldListener(&KMSFrameBuffer::MediaHoldChanged); |
| 357 | } | ||
| 358 | 23 | ++FRAMEBUFFER_COUNT; | |
| 359 | |||
| 360 |
1/2✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
|
23 | std::unique_lock<std::mutex> lock(drmMasterLock); |
| 361 | 23 | m_flipToken = ++NEXT_FLIP_TOKEN; | |
| 362 |
1/2✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
|
23 | FLIP_TARGETS[m_flipToken] = this; |
| 363 | 23 | } | |
| 364 | |||
| 365 | 76 | KMSFrameBuffer::~KMSFrameBuffer() { | |
| 366 | // The draw loop writes into the mapped dumb buffers and calls the virtual | ||
| 367 | // SyncDisplay(); it has to be dead before this destructor releases any of | ||
| 368 | // that. ~FrameBuffer() joins far too late - by then the vtable is already | ||
| 369 | // demoted and the card fds below may be closed. | ||
| 370 | 38 | StopDrawLoop(); | |
| 371 | |||
| 372 | { | ||
| 373 | 38 | std::unique_lock<std::mutex> lock(drmMasterLock); | |
| 374 | // Retire our last flip while this object can still take its event. | ||
| 375 | // Past this point one that is still queued is dropped by token. | ||
| 376 |
2/2✓ Branch 0 taken 17 times.
✓ Branch 1 taken 2 times.
|
38 | if (m_flipPending) { |
| 377 |
1/2✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
|
34 | int vr = m_mode.vrefresh > 0 ? m_mode.vrefresh : 60; |
| 378 | 34 | WaitForPendingFlip((1000 / vr) + 20); | |
| 379 | } | ||
| 380 | 38 | FLIP_TARGETS.erase(m_flipToken); | |
| 381 | |||
| 382 |
4/6✓ Branch 0 taken 18 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 18 times.
✗ Branch 5 not taken.
|
38 | if (m_displayEnabled && m_crtcId && m_planeId) { |
| 383 | 36 | LogInfo(VB_CHANNELOUT, "KMSFrameBuffer::~KMSFrameBuffer() disabling display before destruction\n"); | |
| 384 |
1/2✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
|
36 | if (DrmCards::EnsureMaster(m_cardFd)) { |
| 385 | 36 | drmModeSetPlane(m_cardFd, m_planeId, m_crtcId, m_fb[m_cPage].fb_id, 0, | |
| 386 | 0, 0, 0, 0, 0, 0, 0, 0); | ||
| 387 | 36 | m_displayEnabled = false; | |
| 388 | } | ||
| 389 | } | ||
| 390 | 38 | } | |
| 391 | |||
| 392 | // Must run here, not from ~FrameBuffer(): the base destructor can only reach | ||
| 393 | // FrameBuffer::DestroyFrameBuffer(), so the dumb buffers and the connector / | ||
| 394 | // CRTC / plane reservations were never released on delete. It also has to | ||
| 395 | // happen before the card fds are closed below. | ||
| 396 | 38 | KMSFrameBuffer::DestroyFrameBuffer(); | |
| 397 | |||
| 398 | 38 | --FRAMEBUFFER_COUNT; | |
| 399 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
38 | if (FRAMEBUFFER_COUNT == 0) { |
| 400 | 38 | StopHotplugMonitor(); | |
| 401 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 19 times.
|
74 | for (auto* card : CARDS) { |
| 402 |
1/2✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
|
36 | if (card->fd >= 0) { |
| 403 | 36 | DrmCards::Release(card->path); | |
| 404 | } | ||
| 405 |
1/2✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
|
36 | delete card; |
| 406 | } | ||
| 407 | 38 | CARDS.clear(); | |
| 408 | } | ||
| 409 | 76 | } | |
| 410 | |||
| 411 | 23 | int KMSFrameBuffer::InitializeFrameBuffer(void) { | |
| 412 | 23 | LogDebug(VB_CHANNELOUT, "KMSFrameBuffer::InitializeFrameBuffer()\n"); | |
| 413 | |||
| 414 |
2/2✓ Branch 0 taken 25 times.
✓ Branch 1 taken 1 times.
|
26 | for (auto* card : CARDS) { |
| 415 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | drmModeResPtr res = drmModeGetResources(card->fd); |
| 416 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 22 times.
|
25 | if (!res) { |
| 417 | 3 | continue; | |
| 418 | } | ||
| 419 | |||
| 420 |
1/2✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
|
23 | for (int ci = 0; ci < res->count_connectors; ci++) { |
| 421 |
1/2✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
|
23 | drmModeConnectorPtr conn = drmModeGetConnectorCurrent(card->fd, res->connectors[ci]); |
| 422 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 23 times.
|
23 | if (!conn) { |
| 423 | 1 | continue; | |
| 424 | } | ||
| 425 |
1/2✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
|
23 | std::string cname = ConnectorFullName(card->fd, conn); |
| 426 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 22 times.
|
23 | if (m_device != cname) { |
| 427 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | drmModeFreeConnector(conn); |
| 428 | 1 | continue; | |
| 429 | } | ||
| 430 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | drmModeFreeConnector(conn); |
| 431 | { | ||
| 432 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | std::unique_lock<std::mutex> lock(drmMasterLock); |
| 433 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | conn = ProbeConnectorLocked(card->fd, res->connectors[ci]); |
| 434 | 22 | } | |
| 435 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
|
22 | if (!conn) { |
| 436 | ✗ | continue; | |
| 437 | } | ||
| 438 | |||
| 439 | // An external display that is off or unplugged right now is not a | ||
| 440 | // reason to have no output: run a standard mode until it shows up | ||
| 441 | // and the hotplug handler adopts its own. Before, the output was | ||
| 442 | // simply never created, and only a restart brought it back. | ||
| 443 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | bool present = SinkPresent(card->fd, conn); |
| 444 | 22 | bool external = IsExternalConnector(conn->connector_type); | |
| 445 |
3/6✓ Branch 0 taken 2 times.
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
22 | if (!present && !external && conn->connection != DRM_MODE_CONNECTED) { |
| 446 | ✗ | drmModeFreeConnector(conn); | |
| 447 | ✗ | continue; | |
| 448 | } | ||
| 449 | |||
| 450 | // Check if already reserved | ||
| 451 | 22 | bool alreadyReserved = false; | |
| 452 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
|
22 | for (auto cid : card->reservedConnectors) { |
| 453 | ✗ | if (cid == conn->connector_id) { | |
| 454 | ✗ | alreadyReserved = true; | |
| 455 | ✗ | break; | |
| 456 | } | ||
| 457 | } | ||
| 458 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
|
22 | if (alreadyReserved) { |
| 459 | ✗ | drmModeFreeConnector(conn); | |
| 460 | ✗ | continue; | |
| 461 | } | ||
| 462 | |||
| 463 | // Find a CRTC for this connector | ||
| 464 | 22 | uint32_t crtcId = 0; | |
| 465 | |||
| 466 | // Try the encoder's current CRTC first | ||
| 467 |
2/2✓ Branch 0 taken 20 times.
✓ Branch 1 taken 2 times.
|
22 | if (conn->encoder_id) { |
| 468 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | drmModeEncoderPtr enc = drmModeGetEncoder(card->fd, conn->encoder_id); |
| 469 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | if (enc) { |
| 470 | // Check if this CRTC is already reserved | ||
| 471 | 20 | bool crtcReserved = false; | |
| 472 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
|
20 | for (auto rid : card->reservedCrtcs) { |
| 473 | ✗ | if (rid == enc->crtc_id) { | |
| 474 | ✗ | crtcReserved = true; | |
| 475 | ✗ | break; | |
| 476 | } | ||
| 477 | } | ||
| 478 |
3/4✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19 times.
✓ Branch 3 taken 1 times.
|
20 | if (!crtcReserved && enc->crtc_id) { |
| 479 | 19 | crtcId = enc->crtc_id; | |
| 480 | } | ||
| 481 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | drmModeFreeEncoder(enc); |
| 482 | } | ||
| 483 | } | ||
| 484 | |||
| 485 | // If no CRTC yet, try all encoders for this connector | ||
| 486 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 19 times.
|
22 | if (crtcId == 0) { |
| 487 |
3/4✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
6 | for (int ei = 0; ei < conn->count_encoders && crtcId == 0; ei++) { |
| 488 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | drmModeEncoderPtr enc = drmModeGetEncoder(card->fd, conn->encoders[ei]); |
| 489 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (!enc) continue; |
| 490 |
4/4✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 2 times.
|
10 | for (int ci2 = 0; ci2 < res->count_crtcs && crtcId == 0; ci2++) { |
| 491 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 4 times.
|
7 | if (enc->possible_crtcs & (1u << ci2)) { |
| 492 | 3 | bool crtcReserved = false; | |
| 493 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | for (auto rid : card->reservedCrtcs) { |
| 494 | ✗ | if (rid == res->crtcs[ci2]) { | |
| 495 | ✗ | crtcReserved = true; | |
| 496 | ✗ | break; | |
| 497 | } | ||
| 498 | } | ||
| 499 |
4/8✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
|
3 | if (!crtcReserved && !DrmCards::InUse(card->fd, res->crtcs[ci2])) { |
| 500 | 3 | crtcId = res->crtcs[ci2]; | |
| 501 | } | ||
| 502 | } | ||
| 503 | } | ||
| 504 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | drmModeFreeEncoder(enc); |
| 505 | } | ||
| 506 | } | ||
| 507 | |||
| 508 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
|
22 | if (crtcId == 0) { |
| 509 | ✗ | drmModeFreeConnector(conn); | |
| 510 | ✗ | drmModeFreeResources(res); | |
| 511 | ✗ | return 0; | |
| 512 | } | ||
| 513 | |||
| 514 | 22 | drmModeModeInfo mode = {}; | |
| 515 | 22 | bool foundMode = false; | |
| 516 |
3/4✓ Branch 0 taken 2 times.
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
|
22 | if (present || !external) { |
| 517 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | foundMode = PickMode(conn, mode); |
| 518 | } else { | ||
| 519 | // A forced connector (video=...e, or ForceHDMI) with no EDID | ||
| 520 | // still carries the command line's mode: run that. | ||
| 521 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (!CommandLineMode(conn, mode)) { |
| 522 | 2 | mode = FallbackMode(); | |
| 523 | } | ||
| 524 | 2 | foundMode = true; | |
| 525 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | LogInfo(VB_CHANNELOUT, "KMSFrameBuffer: no display on %s yet, running %dx%d @ %dHz until one appears\n", |
| 526 | cname.c_str(), mode.hdisplay, mode.vdisplay, mode.vrefresh); | ||
| 527 | } | ||
| 528 | |||
| 529 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
|
22 | if (!foundMode) { |
| 530 | ✗ | drmModeFreeConnector(conn); | |
| 531 | ✗ | drmModeFreeResources(res); | |
| 532 | ✗ | return 0; | |
| 533 | } | ||
| 534 | |||
| 535 | 22 | m_mode = mode; | |
| 536 | 22 | m_connectorId = conn->connector_id; | |
| 537 | 22 | m_crtcId = crtcId; | |
| 538 | 22 | m_crtcPipe = 0; | |
| 539 |
2/2✓ Branch 0 taken 31 times.
✓ Branch 1 taken 22 times.
|
53 | for (int ci = 0; ci < res->count_crtcs; ci++) { |
| 540 |
2/2✓ Branch 0 taken 22 times.
✓ Branch 1 taken 9 times.
|
31 | if (res->crtcs[ci] == crtcId) { |
| 541 | 22 | m_crtcPipe = ci; | |
| 542 | } | ||
| 543 | } | ||
| 544 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | m_connectorName = cname; |
| 545 | 22 | m_cardFd = card->fd; | |
| 546 | 22 | m_cardInfo = card; | |
| 547 | |||
| 548 | // The panel refresh caps the sustainable output frame rate: page | ||
| 549 | // flips are vblank-paced, so a sequence faster than this will be | ||
| 550 | // limited to the refresh rate. | ||
| 551 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | LogInfo(VB_CHANNELOUT, "KMSFrameBuffer: %s mode %dx%d @ %dHz\n", |
| 552 | cname.c_str(), mode.hdisplay, mode.vdisplay, mode.vrefresh); | ||
| 553 | |||
| 554 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | card->reservedConnectors.push_back(m_connectorId); |
| 555 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | card->reservedCrtcs.push_back(m_crtcId); |
| 556 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | DrmCards::Claim(card->fd, m_connectorId); |
| 557 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | DrmCards::Claim(card->fd, m_crtcId); |
| 558 | |||
| 559 |
2/2✓ Branch 0 taken 13 times.
✓ Branch 1 taken 9 times.
|
22 | if (m_variableRefresh) { |
| 560 | // DPI Pixels: drive our own vertical timing. Keep the | ||
| 561 | // connector's horizontal timing and pixel clock (they define the | ||
| 562 | // WS281x bit timing) but set vactive to the caller's requested | ||
| 563 | // scanline count (sized to the longest string) and start at the | ||
| 564 | // maximum refresh rate. DPIPixelsOutput lowers it per-sequence | ||
| 565 | // via SetRefreshRate() by only varying the vertical blanking, so | ||
| 566 | // the framebuffer and its contents never have to be rebuilt. | ||
| 567 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | if (m_width == 0) { |
| 568 | 13 | m_width = m_mode.hdisplay; | |
| 569 | } | ||
| 570 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
|
13 | if (m_height == 0) { |
| 571 | ✗ | m_height = m_mode.vdisplay; | |
| 572 | } | ||
| 573 | 13 | m_pixelSize = 1; | |
| 574 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | ApplyVerticalTiming(m_mode, m_height, 1000000); // clamped to max fps |
| 575 | } else { | ||
| 576 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (m_width == 0) { |
| 577 | ✗ | m_width = m_mode.hdisplay; | |
| 578 | } | ||
| 579 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (m_height == 0) { |
| 580 | ✗ | m_height = m_mode.vdisplay; | |
| 581 | } | ||
| 582 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | if (m_pixelSize == 0) { |
| 583 | 9 | int mw = m_mode.hdisplay / m_width; | |
| 584 | 9 | int mh = m_mode.vdisplay / m_height; | |
| 585 | 9 | m_pixelSize = std::min(mw, mh); | |
| 586 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (m_pixelSize < 1) { |
| 587 | ✗ | m_pixelSize = 1; | |
| 588 | } | ||
| 589 | 9 | m_width *= m_pixelSize; | |
| 590 | 9 | m_height *= m_pixelSize; | |
| 591 | } | ||
| 592 | } | ||
| 593 | |||
| 594 | 22 | m_bpp = 24; | |
| 595 | 22 | uint32_t format = DRM_FORMAT_RGB888; | |
| 596 |
2/2✓ Branch 0 taken 44 times.
✓ Branch 1 taken 22 times.
|
66 | for (int x = 0; x < 2; x++) { |
| 597 |
2/4✓ Branch 0 taken 44 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 44 times.
|
44 | if (!CreateDumbBuffer(card->fd, m_width, m_height, format, m_fb[x])) { |
| 598 | ✗ | format = DRM_FORMAT_XRGB8888; | |
| 599 | ✗ | m_bpp = 32; | |
| 600 | ✗ | if (!CreateDumbBuffer(card->fd, m_width, m_height, format, m_fb[x])) { | |
| 601 | ✗ | LogErr(VB_CHANNELOUT, "Failed to create dumb buffer\n"); | |
| 602 | // Free any buffer(s) already created and undo the | ||
| 603 | // connector/CRTC reservations pushed above so we don't | ||
| 604 | // leak CMA or permanently reserve this output. | ||
| 605 | ✗ | for (int y = 0; y < x; y++) { | |
| 606 | ✗ | DestroyDumbBuffer(card->fd, m_fb[y]); | |
| 607 | ✗ | m_pageBuffers[y] = nullptr; | |
| 608 | } | ||
| 609 | ✗ | auto& rc = card->reservedConnectors; | |
| 610 | ✗ | rc.erase(std::remove(rc.begin(), rc.end(), m_connectorId), rc.end()); | |
| 611 | ✗ | auto& rcr = card->reservedCrtcs; | |
| 612 | ✗ | rcr.erase(std::remove(rcr.begin(), rcr.end(), m_crtcId), rcr.end()); | |
| 613 | ✗ | DrmCards::Unclaim(card->fd, m_connectorId); | |
| 614 | ✗ | DrmCards::Unclaim(card->fd, m_crtcId); | |
| 615 | ✗ | m_connectorId = 0; | |
| 616 | ✗ | m_crtcId = 0; | |
| 617 | ✗ | drmModeFreeConnector(conn); | |
| 618 | ✗ | drmModeFreeResources(res); | |
| 619 | ✗ | return 0; | |
| 620 | } | ||
| 621 | } | ||
| 622 | 44 | m_pageBuffers[x] = (uint8_t*)m_fb[x].mapped; | |
| 623 | } | ||
| 624 | |||
| 625 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | m_planeId = FindPlaneForCrtc(card->fd, m_crtcId, format); |
| 626 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | if (m_planeId) { |
| 627 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | card->reservedPlanes.push_back(m_planeId); |
| 628 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | DrmCards::Claim(card->fd, m_planeId); |
| 629 | } | ||
| 630 | 22 | m_pages = 2; | |
| 631 | |||
| 632 | 22 | m_cPage = 0; | |
| 633 | 22 | m_pPage = 0; | |
| 634 | 22 | m_rowStride = m_fb[0].stride; | |
| 635 | 22 | m_rowPadding = m_rowStride - (m_width * m_bpp / 8); | |
| 636 | 22 | m_pageSize = m_fb[0].size; | |
| 637 | 22 | m_bufferSize = m_pageSize; | |
| 638 | |||
| 639 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | if (m_planeId) { |
| 640 | 22 | drmModeSetPlane(m_cardFd, m_planeId, m_crtcId, m_fb[0].fb_id, 0, | |
| 641 | 22 | 0, 0, m_mode.hdisplay, m_mode.vdisplay, | |
| 642 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | 0, 0, (uint32_t)m_width << 16, (uint32_t)m_height << 16); |
| 643 | } | ||
| 644 | |||
| 645 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | drmModeFreeConnector(conn); |
| 646 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | drmModeFreeResources(res); |
| 647 | |||
| 648 | { | ||
| 649 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | std::unique_lock<std::mutex> lock(drmMasterLock); |
| 650 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | SetSinkPresentLocked(present); |
| 651 | 22 | m_hotplugReady = true; | |
| 652 | 22 | } | |
| 653 | 22 | return 1; | |
| 654 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 22 times.
|
23 | } |
| 655 | ✗ | drmModeFreeResources(res); | |
| 656 | } | ||
| 657 | 1 | return 0; | |
| 658 | } | ||
| 659 | |||
| 660 | 19 | void KMSFrameBuffer::DestroyFrameBuffer(void) { | |
| 661 | { | ||
| 662 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
19 | std::unique_lock<std::mutex> lock(drmMasterLock); |
| 663 | 19 | m_hotplugReady = false; | |
| 664 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
|
19 | if (m_noDisplayWarned) { |
| 665 | ✗ | WarningHolder::RemoveWarning(WARNING_ID_NO_DISPLAY, NoDisplayWarning(m_connectorName)); | |
| 666 | ✗ | m_noDisplayWarned = false; | |
| 667 | } | ||
| 668 | 19 | } | |
| 669 |
2/2✓ Branch 0 taken 57 times.
✓ Branch 1 taken 19 times.
|
76 | for (int x = 0; x < 3; x++) { |
| 670 |
2/2✓ Branch 0 taken 36 times.
✓ Branch 1 taken 21 times.
|
57 | if (m_fb[x].handle) { |
| 671 | 36 | DestroyDumbBuffer(m_cardFd, m_fb[x]); | |
| 672 | 36 | m_pageBuffers[x] = nullptr; | |
| 673 | } | ||
| 674 | } | ||
| 675 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
|
19 | if (m_blankFb.handle) { |
| 676 | ✗ | DestroyDumbBuffer(m_cardFd, m_blankFb); | |
| 677 | } | ||
| 678 | 19 | m_mediaBlanked = false; | |
| 679 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 1 times.
|
19 | if (m_cardInfo) { |
| 680 |
1/2✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
|
18 | if (m_planeId) { |
| 681 | 18 | auto& planes = m_cardInfo->reservedPlanes; | |
| 682 |
2/4✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
|
18 | planes.erase(std::remove(planes.begin(), planes.end(), m_planeId), planes.end()); |
| 683 | 18 | DrmCards::Unclaim(m_cardInfo->fd, m_planeId); | |
| 684 | } | ||
| 685 |
1/2✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
|
18 | if (m_crtcId) { |
| 686 | 18 | auto& crtcs = m_cardInfo->reservedCrtcs; | |
| 687 |
2/4✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
|
18 | crtcs.erase(std::remove(crtcs.begin(), crtcs.end(), m_crtcId), crtcs.end()); |
| 688 | 18 | DrmCards::Unclaim(m_cardInfo->fd, m_crtcId); | |
| 689 | } | ||
| 690 |
1/2✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
|
18 | if (m_connectorId) { |
| 691 | 18 | auto& conns = m_cardInfo->reservedConnectors; | |
| 692 |
2/4✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
|
18 | conns.erase(std::remove(conns.begin(), conns.end(), m_connectorId), conns.end()); |
| 693 | 18 | DrmCards::Unclaim(m_cardInfo->fd, m_connectorId); | |
| 694 | } | ||
| 695 | } | ||
| 696 | |||
| 697 | 19 | m_connectorId = 0; | |
| 698 | 19 | m_crtcId = 0; | |
| 699 | 19 | m_planeId = 0; | |
| 700 | 19 | m_cardInfo = nullptr; | |
| 701 | 19 | FrameBuffer::DestroyFrameBuffer(); | |
| 702 | 19 | } | |
| 703 | |||
| 704 | ✗ | void KMSFrameBuffer::SyncLoop() { | |
| 705 | ✗ | if (m_pages == 1) { | |
| 706 | ✗ | return; | |
| 707 | } | ||
| 708 | |||
| 709 | ✗ | while (m_runLoop) { | |
| 710 | // Allow the producer to catch up if needed | ||
| 711 | ✗ | if (!m_dirtyPages[m_cPage] && m_dirtyPages[(m_cPage + 1) % m_pages]) | |
| 712 | ✗ | NextPage(); | |
| 713 | |||
| 714 | ✗ | if (m_dirtyPages[m_cPage]) { | |
| 715 | // SyncDisplay() blocks on the vblank completion event, so it paces | ||
| 716 | // this loop to the panel refresh without spinning. | ||
| 717 | ✗ | SyncDisplay(true); | |
| 718 | |||
| 719 | ✗ | m_dirtyPages[m_cPage] = 0; | |
| 720 | ✗ | NextPage(); | |
| 721 | } else { | ||
| 722 | // Nothing to flip yet. Sleep briefly instead of busy-spinning so an | ||
| 723 | // idle output doesn't peg a CPU core (the original reason AUTO_SYNC | ||
| 724 | // was abandoned). | ||
| 725 | ✗ | std::this_thread::sleep_for(std::chrono::microseconds(250)); | |
| 726 | } | ||
| 727 | } | ||
| 728 | } | ||
| 729 | |||
| 730 | // Whether a media pipeline is showing on this connector, so this output must | ||
| 731 | // leave it alone. Its kmssink holds the connector through a DRM lease from | ||
| 732 | // before the pipeline starts until it is gone, whichever slot it plays on and | ||
| 733 | // whatever the output is called -- the old test compared slot 1's status to | ||
| 734 | // the connector name, which Play Media and "--HDMI--" never matched. | ||
| 735 | 2072 | bool KMSFrameBuffer::MediaHoldsConnector() const { | |
| 736 |
3/4✓ Branch 0 taken 2072 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 180 times.
✓ Branch 3 taken 1892 times.
|
2072 | return m_connectorId && DrmCards::HeldByMedia(m_cardFd, m_connectorId); |
| 737 | } | ||
| 738 | |||
| 739 | // Take our plane off the CRTC, which stays lit for the video on top of it. | ||
| 740 | // Black would hide the last frame as well, but a plane showing black still | ||
| 741 | // counts against the HVS load: on a Pi 0-3 a framebuffer scaled to the mode | ||
| 742 | // under a full-screen 1080p video is over the kernel's limit, and the video's | ||
| 743 | // first frame fails with ENOSPC. Caller holds drmMasterLock. | ||
| 744 | 91 | void KMSFrameBuffer::BlankForMediaLocked() { | |
| 745 |
8/10✓ Branch 0 taken 89 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 89 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 86 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 3 times.
✓ Branch 8 taken 88 times.
✓ Branch 9 taken 3 times.
|
91 | if (m_mediaBlanked || !m_planeId || !m_displayEnabled || !DrmCards::EnsureMaster(m_cardFd)) { |
| 746 | 88 | return; | |
| 747 | } | ||
| 748 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
|
3 | if (m_flipPending) { |
| 749 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | int vr = m_mode.vrefresh > 0 ? m_mode.vrefresh : 60; |
| 750 | 2 | WaitForPendingFlip((1000 / vr) + 20); | |
| 751 | } | ||
| 752 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (drmModeSetPlane(m_cardFd, m_planeId, m_crtcId, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) != 0) { |
| 753 | // A driver that needs a plane on a lit CRTC: black, then. | ||
| 754 | ✗ | LogDebug(VB_CHANNELOUT, "KMSFrameBuffer %s: can't take the plane off (%s), blanking it\n", | |
| 755 | m_connectorName.c_str(), strerror(errno)); | ||
| 756 | ✗ | if (!m_blankFb.handle) { | |
| 757 | ✗ | if (!CreateDumbBuffer(m_cardFd, m_fb[0].width, m_fb[0].height, m_fb[0].format, m_blankFb)) { | |
| 758 | ✗ | return; | |
| 759 | } | ||
| 760 | ✗ | memset(m_blankFb.mapped, 0, m_blankFb.size); | |
| 761 | } | ||
| 762 | ✗ | drmModeSetPlane(m_cardFd, m_planeId, m_crtcId, m_blankFb.fb_id, 0, | |
| 763 | ✗ | 0, 0, m_mode.hdisplay, m_mode.vdisplay, | |
| 764 | ✗ | 0, 0, (uint32_t)m_width << 16, (uint32_t)m_height << 16); | |
| 765 | } | ||
| 766 | 3 | m_mediaBlanked = true; | |
| 767 | 3 | LogInfo(VB_CHANNELOUT, "KMSFrameBuffer %s: media is playing here, standing aside\n", m_connectorName.c_str()); | |
| 768 | } | ||
| 769 | |||
| 770 | // The media is gone. Its kmssink may have left the CRTC in another state, so | ||
| 771 | // put our page back with a plane commit rather than trusting a flip from what | ||
| 772 | // was there before. Caller holds drmMasterLock. | ||
| 773 | 1862 | void KMSFrameBuffer::ShowAfterMediaLocked() { | |
| 774 |
2/2✓ Branch 0 taken 1859 times.
✓ Branch 1 taken 3 times.
|
1862 | if (!m_mediaBlanked) { |
| 775 | 1859 | return; | |
| 776 | } | ||
| 777 | 3 | m_mediaBlanked = false; | |
| 778 | 3 | m_flipPending = false; | |
| 779 |
4/8✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 3 times.
|
6 | if (m_displayEnabled && m_planeId && DrmCards::EnsureMaster(m_cardFd) && |
| 780 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | drmModeSetPlane(m_cardFd, m_planeId, m_crtcId, m_fb[m_cPage].fb_id, 0, |
| 781 | 3 | 0, 0, m_mode.hdisplay, m_mode.vdisplay, | |
| 782 | 3 | 0, 0, (uint32_t)m_width << 16, (uint32_t)m_height << 16) != 0) { | |
| 783 | ✗ | m_displayEnabled = false; // relit with a full modeset on the next flip | |
| 784 | } | ||
| 785 | 3 | LogInfo(VB_CHANNELOUT, "KMSFrameBuffer %s: media finished, showing output again\n", m_connectorName.c_str()); | |
| 786 | } | ||
| 787 | |||
| 788 | // At once, not on our next flip: an output nothing is sending data to has | ||
| 789 | // none, and would sit under the video at full cost. Caller (DrmCards) holds | ||
| 790 | // drmMasterLock, which also guards FLIP_TARGETS. | ||
| 791 | 15 | void KMSFrameBuffer::MediaHoldChanged(int fd, uint32_t connectorId) { | |
| 792 |
2/2✓ Branch 0 taken 15 times.
✓ Branch 1 taken 15 times.
|
30 | for (auto& [token, fb] : FLIP_TARGETS) { |
| 793 |
3/4✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 9 times.
|
15 | if (fb->m_cardFd != fd || fb->m_connectorId != connectorId) { |
| 794 | 6 | continue; | |
| 795 | } | ||
| 796 |
3/4✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 4 times.
|
9 | if (fb->MediaHoldsConnector()) { |
| 797 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | fb->BlankForMediaLocked(); |
| 798 | } else { | ||
| 799 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | fb->ShowAfterMediaLocked(); |
| 800 | } | ||
| 801 | } | ||
| 802 | 15 | } | |
| 803 | |||
| 804 | 1855 | void KMSFrameBuffer::PageFlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void* data) { | |
| 805 | // data is the flip token of the instance that queued this flip, so even if | ||
| 806 | // we drain an event that belongs to another KMSFrameBuffer sharing the same | ||
| 807 | // card fd, the correct instance's pending flag is cleared. Runs inside | ||
| 808 | // drmHandleEvent under drmMasterLock, which also guards FLIP_TARGETS. | ||
| 809 | 1855 | uint32_t token = (uint32_t)(uintptr_t)data; | |
| 810 |
1/2✓ Branch 0 taken 1855 times.
✗ Branch 1 not taken.
|
1855 | auto it = FLIP_TARGETS.find(token); |
| 811 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1855 times.
|
1855 | if (it == FLIP_TARGETS.end()) { |
| 812 | ✗ | LogWarn(VB_CHANNELOUT, "KMSFrameBuffer: dropped the flip event of a deleted framebuffer (token %u)\n", token); | |
| 813 | ✗ | return; | |
| 814 | } | ||
| 815 | 1855 | KMSFrameBuffer* self = it->second; | |
| 816 | { | ||
| 817 | 1855 | self->m_flipPending = false; | |
| 818 | // "frame" is the vblank sequence number at flip completion; track the | ||
| 819 | // cadence. Runs inside drmHandleEvent under drmMasterLock. | ||
| 820 |
3/4✓ Branch 0 taken 1826 times.
✓ Branch 1 taken 29 times.
✓ Branch 2 taken 1826 times.
✗ Branch 3 not taken.
|
1855 | if (self->m_lastFlipSeq != 0 && frame > self->m_lastFlipSeq) { |
| 821 | 1826 | uint32_t delta = frame - self->m_lastFlipSeq; | |
| 822 | 1826 | self->m_vblankDeltaCounts[delta > 4 ? 4 : delta]++; | |
| 823 |
4/4✓ Branch 0 taken 1800 times.
✓ Branch 1 taken 26 times.
✓ Branch 2 taken 64 times.
✓ Branch 3 taken 1736 times.
|
1826 | if (self->m_prevVblankDelta != 0 && delta != self->m_prevVblankDelta) { |
| 824 | 64 | self->m_vblankCadenceBreaks++; | |
| 825 | } | ||
| 826 | 1826 | self->m_prevVblankDelta = delta; | |
| 827 | } | ||
| 828 | 1855 | self->m_lastFlipSeq = frame; | |
| 829 | 1855 | self->m_lastFlipTimeUS = (uint64_t)sec * 1000000ULL + usec; | |
| 830 | } | ||
| 831 | } | ||
| 832 | |||
| 833 | 1855 | void KMSFrameBuffer::WaitForPendingFlip(int timeoutMs) { | |
| 834 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1855 times.
|
1855 | if (!m_flipPending) { |
| 835 | ✗ | return; | |
| 836 | } | ||
| 837 | 1855 | struct pollfd pfd = {}; | |
| 838 | 1855 | pfd.fd = m_cardFd; | |
| 839 | 1855 | pfd.events = POLLIN; | |
| 840 | |||
| 841 | 1855 | drmEventContext evctx = {}; | |
| 842 | 1855 | evctx.version = 2; | |
| 843 | 1855 | evctx.page_flip_handler = &KMSFrameBuffer::PageFlipHandler; | |
| 844 | |||
| 845 | // Normally the vblank event is already queued (or arrives within one refresh | ||
| 846 | // interval) so poll() returns almost immediately. The timeout is only a | ||
| 847 | // safety net so a dropped/lost event can never wedge the output thread. | ||
| 848 |
1/2✓ Branch 0 taken 1855 times.
✗ Branch 1 not taken.
|
1855 | int r = poll(&pfd, 1, timeoutMs); |
| 849 |
2/4✓ Branch 0 taken 1855 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1855 times.
✗ Branch 3 not taken.
|
1855 | if (r > 0 && (pfd.revents & POLLIN)) { |
| 850 |
1/2✓ Branch 0 taken 1855 times.
✗ Branch 1 not taken.
|
1855 | drmHandleEvent(m_cardFd, &evctx); |
| 851 | } else { | ||
| 852 | ✗ | m_flipWaitTimeouts++; | |
| 853 | } | ||
| 854 | // If we timed out (or errored) give up on this flip so the next frame can | ||
| 855 | // proceed rather than blocking forever. | ||
| 856 | 1855 | m_flipPending = false; | |
| 857 | } | ||
| 858 | |||
| 859 | 767 | void KMSFrameBuffer::WaitForPageFree() { | |
| 860 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 767 times.
|
767 | if (m_pages == 1) { |
| 861 | ✗ | return; | |
| 862 | } | ||
| 863 |
1/2✓ Branch 0 taken 767 times.
✗ Branch 1 not taken.
|
767 | std::unique_lock<std::mutex> lock(drmMasterLock); |
| 864 |
2/2✓ Branch 0 taken 671 times.
✓ Branch 1 taken 96 times.
|
767 | if (m_flipPending) { |
| 865 |
1/2✓ Branch 0 taken 671 times.
✗ Branch 1 not taken.
|
671 | int vr = m_mode.vrefresh > 0 ? m_mode.vrefresh : 60; |
| 866 | 671 | uint32_t timeoutsBefore = m_flipWaitTimeouts; | |
| 867 |
1/2✓ Branch 0 taken 671 times.
✗ Branch 1 not taken.
|
671 | WaitForPendingFlip((1000 / vr) + 20); |
| 868 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 671 times.
|
671 | if (m_flipWaitTimeouts != timeoutsBefore) { |
| 869 | // The completion event never came, so it is unknown whether the | ||
| 870 | // hardware has switched pages yet. A queued flip lands at the next | ||
| 871 | // vblank at the latest, so wait one out before the caller writes | ||
| 872 | // into the page it is about to leave. Ask for the vblank as an | ||
| 873 | // event on OUR crtc (the pipe bits select it; without them this | ||
| 874 | // would be pipe 0, which on a Pi 4 with HDMI up is not the DPI) and | ||
| 875 | // poll for it with the same bound as the flip wait, so a display | ||
| 876 | // whose vblanks have stopped cannot wedge the output thread here. | ||
| 877 | ✗ | drmVBlank vbl = {}; | |
| 878 | ✗ | unsigned int type = DRM_VBLANK_RELATIVE | DRM_VBLANK_EVENT; | |
| 879 | ✗ | if (m_crtcPipe > 0) { | |
| 880 | ✗ | type |= (unsigned int)((m_crtcPipe << _DRM_VBLANK_HIGH_CRTC_SHIFT) & _DRM_VBLANK_HIGH_CRTC_MASK); | |
| 881 | } | ||
| 882 | ✗ | vbl.request.type = (drmVBlankSeqType)type; | |
| 883 | ✗ | vbl.request.sequence = 1; | |
| 884 | ✗ | vbl.request.signal = 0; | |
| 885 | ✗ | if (drmWaitVBlank(m_cardFd, &vbl) == 0) { | |
| 886 | ✗ | struct pollfd pfd = {}; | |
| 887 | ✗ | pfd.fd = m_cardFd; | |
| 888 | ✗ | pfd.events = POLLIN; | |
| 889 | ✗ | if (poll(&pfd, 1, (1000 / vr) + 20) > 0 && (pfd.revents & POLLIN)) { | |
| 890 | ✗ | drmEventContext evctx = {}; | |
| 891 | ✗ | evctx.version = 2; | |
| 892 | ✗ | evctx.page_flip_handler = &KMSFrameBuffer::PageFlipHandler; | |
| 893 | ✗ | evctx.vblank_handler = &KMSFrameBuffer::VBlankHandler; | |
| 894 | ✗ | drmHandleEvent(m_cardFd, &evctx); | |
| 895 | } | ||
| 896 | } | ||
| 897 | } | ||
| 898 | } | ||
| 899 | 767 | } | |
| 900 | |||
| 901 | ✗ | void KMSFrameBuffer::VBlankHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void* data) { | |
| 902 | // Nothing to record; the wait in WaitForPageFree() only needs the event | ||
| 903 | // to have been delivered. | ||
| 904 | ✗ | } | |
| 905 | |||
| 906 | 1944 | void KMSFrameBuffer::SyncDisplay(bool pageChanged) { | |
| 907 |
2/4✓ Branch 0 taken 1944 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1944 times.
|
1944 | if (!pageChanged || m_pages == 1) |
| 908 | 86 | return; | |
| 909 | |||
| 910 |
1/2✓ Branch 0 taken 1944 times.
✗ Branch 1 not taken.
|
1944 | std::unique_lock<std::mutex> lock(drmMasterLock); |
| 911 |
3/4✓ Branch 0 taken 1944 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 86 times.
✓ Branch 3 taken 1858 times.
|
1944 | if (MediaHoldsConnector()) { |
| 912 |
1/2✓ Branch 0 taken 86 times.
✗ Branch 1 not taken.
|
86 | BlankForMediaLocked(); |
| 913 | 86 | return; | |
| 914 | } | ||
| 915 |
1/2✓ Branch 0 taken 1858 times.
✗ Branch 1 not taken.
|
1858 | ShowAfterMediaLocked(); |
| 916 | { | ||
| 917 |
1/2✓ Branch 0 taken 1858 times.
✗ Branch 1 not taken.
|
1858 | long long syncStart = GetMonotonicTimeMS(); |
| 918 |
2/4✓ Branch 0 taken 1858 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1858 times.
✗ Branch 3 not taken.
|
1858 | if (DrmCards::EnsureMaster(m_cardFd)) { |
| 919 | // If the previous flip hasn't retired yet, wait for its vblank | ||
| 920 | // completion event instead of letting this flip return EBUSY and | ||
| 921 | // fall into the expensive SetPlane/SetCrtc modeset path. poll() | ||
| 922 | // sleeps until the flip completes, so this paces us to the panel's | ||
| 923 | // refresh with no busy-waiting. | ||
| 924 |
2/2✓ Branch 0 taken 1155 times.
✓ Branch 1 taken 703 times.
|
1858 | if (m_flipPending) { |
| 925 |
1/2✓ Branch 0 taken 1155 times.
✗ Branch 1 not taken.
|
1155 | int vr = m_mode.vrefresh > 0 ? m_mode.vrefresh : 60; |
| 926 | 1155 | int timeoutMs = (1000 / vr) + 20; // one refresh interval + slack | |
| 927 |
1/2✓ Branch 0 taken 1155 times.
✗ Branch 1 not taken.
|
1155 | WaitForPendingFlip(timeoutMs); |
| 928 | } | ||
| 929 | |||
| 930 | // Phase alignment: fppd's software frame clock and the panel's | ||
| 931 | // vblank clock are never exactly the same rate, so the submission | ||
| 932 | // time slowly drifts across the vblank grid. When it straddles a | ||
| 933 | // boundary, scheduling jitter makes alternate frames catch/miss | ||
| 934 | // the vblank and frames display for e.g. 25ms/75ms instead of a | ||
| 935 | // steady 50ms - a visible judder burst until the drift clears the | ||
| 936 | // boundary (confirmed via the vblank-delta counters: 1,3,1,3 | ||
| 937 | // bursts exactly at observed stutters). A "nudge only near the | ||
| 938 | // boundary" zone just moves the discontinuity to the zone edge | ||
| 939 | // (confirmed in testing: crossings leaked at the edge while the | ||
| 940 | // phase transited the zone), so instead align EVERY submission to | ||
| 941 | // the middle of the vblank interval - scheduling jitter can never | ||
| 942 | // reach a boundary from there, and the clock drift shows up as | ||
| 943 | // this sleep slowly varying. When the sleep wraps, the | ||
| 944 | // unavoidable one-vblank phase slip happens once, cleanly, per | ||
| 945 | // beat cycle instead of oscillating. Align unconditionally: | ||
| 946 | // gating this on "slack since the last completion" feeds back on | ||
| 947 | // itself (an aligned flip completes later in the cycle, the gate | ||
| 948 | // then skips the next frame, which lands early - producing a | ||
| 949 | // permanent 1,3,1,3 oscillation; confirmed in testing). The | ||
| 950 | // completion timestamps are vblank-quantized, so aligning every | ||
| 951 | // frame pins submissions to the true grid midpoint and is stable | ||
| 952 | // at any frame rate at or below the refresh rate. | ||
| 953 | 1858 | long long alignSleepUS = 0; | |
| 954 |
3/4✓ Branch 0 taken 1836 times.
✓ Branch 1 taken 22 times.
✓ Branch 2 taken 1836 times.
✗ Branch 3 not taken.
|
1858 | if (m_lastFlipTimeUS != 0 && m_mode.vrefresh > 0) { |
| 955 | struct timespec tsm; | ||
| 956 | 1836 | clock_gettime(CLOCK_MONOTONIC, &tsm); | |
| 957 | 1836 | uint64_t nowUS = (uint64_t)tsm.tv_sec * 1000000ULL + tsm.tv_nsec / 1000; | |
| 958 | 1836 | uint64_t periodUS = 1000000ULL / m_mode.vrefresh; | |
| 959 | // The 60s cap also skips alignment if the driver reported | ||
| 960 | // non-monotonic timestamps - elapsed would be implausible. | ||
| 961 |
1/2✓ Branch 0 taken 1836 times.
✗ Branch 1 not taken.
|
1836 | if (nowUS > m_lastFlipTimeUS && |
| 962 |
1/2✓ Branch 0 taken 1836 times.
✗ Branch 1 not taken.
|
1836 | (nowUS - m_lastFlipTimeUS) < 60000000ULL) { |
| 963 | 1836 | uint64_t phaseUS = (nowUS - m_lastFlipTimeUS) % periodUS; | |
| 964 | 1836 | uint64_t sleepUS = (periodUS / 2 + periodUS - phaseUS) % periodUS; | |
| 965 | // Already within 2ms past mid-interval: close enough, | ||
| 966 | // don't sleep a nearly full period chasing the target. | ||
| 967 |
3/4✓ Branch 0 taken 1836 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1823 times.
✓ Branch 3 taken 13 times.
|
1836 | if (sleepUS > 0 && sleepUS < periodUS - 2000) { |
| 968 | 1823 | m_phaseNudges++; | |
| 969 | 1823 | alignSleepUS = sleepUS; | |
| 970 |
1/2✓ Branch 0 taken 1823 times.
✗ Branch 1 not taken.
|
1823 | std::this_thread::sleep_for(std::chrono::microseconds(sleepUS)); |
| 971 | } | ||
| 972 | } | ||
| 973 | } | ||
| 974 | |||
| 975 | 1858 | bool wasEnabled = m_displayEnabled; | |
| 976 | 1858 | int ret = -1; | |
| 977 |
2/2✓ Branch 0 taken 1856 times.
✓ Branch 1 taken 2 times.
|
1858 | if (m_displayEnabled) { |
| 978 | 1856 | ret = drmModePageFlip(m_cardFd, m_crtcId, m_fb[m_cPage].fb_id, | |
| 979 |
1/2✓ Branch 0 taken 1856 times.
✗ Branch 1 not taken.
|
1856 | DRM_MODE_PAGE_FLIP_EVENT, (void*)(uintptr_t)m_flipToken); |
| 980 | } | ||
| 981 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1856 times.
|
1858 | if (ret) { |
| 982 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (wasEnabled) { |
| 983 | // Flip rejected on an already-enabled display; the modeset | ||
| 984 | // fallback below costs multiple vblanks. | ||
| 985 | ✗ | m_flipRejects++; | |
| 986 | } | ||
| 987 | // First flip after enable, or the flip was rejected: bring the | ||
| 988 | // display up with a full modeset/plane commit, then queue a flip. | ||
| 989 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (!m_displayEnabled) { |
| 990 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | ModesetLocked(); |
| 991 | 2 | m_displayEnabled = true; | |
| 992 | ✗ | } else if (m_planeId) { | |
| 993 | ✗ | drmModeSetPlane(m_cardFd, m_planeId, m_crtcId, m_fb[m_cPage].fb_id, 0, | |
| 994 | ✗ | 0, 0, m_mode.hdisplay, m_mode.vdisplay, | |
| 995 | ✗ | 0, 0, (uint32_t)m_width << 16, (uint32_t)m_height << 16); | |
| 996 | } | ||
| 997 | 2 | ret = drmModePageFlip(m_cardFd, m_crtcId, m_fb[m_cPage].fb_id, | |
| 998 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | DRM_MODE_PAGE_FLIP_EVENT, (void*)(uintptr_t)m_flipToken); |
| 999 | } | ||
| 1000 | 1858 | m_flipPending = (ret == 0); | |
| 1001 | |||
| 1002 | // Track flip-path health. A single sync should normally complete | ||
| 1003 | // well inside one refresh interval; count the ones that don't and | ||
| 1004 | // surface them (rate-limited) at Warn level so output stutters can | ||
| 1005 | // be correlated with (or ruled out of) the KMS flip path without | ||
| 1006 | // needing debug logging. | ||
| 1007 |
1/2✓ Branch 0 taken 1858 times.
✗ Branch 1 not taken.
|
1858 | long long syncEnd = GetMonotonicTimeMS(); |
| 1008 | // The phase-alignment sleep is intentional; don't count it as a | ||
| 1009 | // flip-path delay. | ||
| 1010 | 1858 | int tookMS = (int)(syncEnd - syncStart) - (int)(alignSleepUS / 1000); | |
| 1011 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1858 times.
|
1858 | if (tookMS > 20) { |
| 1012 | ✗ | m_slowSyncs++; | |
| 1013 | ✗ | m_lastSlowSyncMS = tookMS; | |
| 1014 | ✗ | if (tookMS > m_maxSyncMS) { | |
| 1015 | ✗ | m_maxSyncMS = tookMS; | |
| 1016 | } | ||
| 1017 | } | ||
| 1018 | // Warn only when a new delay event has occurred since the last | ||
| 1019 | // warning (rate-limited), so each warning line timestamps actual | ||
| 1020 | // trouble rather than repeating stale totals. | ||
| 1021 | 1858 | uint64_t eventTotal = (uint64_t)m_slowSyncs + m_flipWaitTimeouts + m_flipRejects + m_vblankCadenceBreaks; | |
| 1022 |
2/2✓ Branch 0 taken 717 times.
✓ Branch 1 taken 1141 times.
|
1858 | if (eventTotal != m_warnedEventTotal && |
| 1023 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 708 times.
|
717 | (syncEnd - m_lastFlipWarnMS) > 10000) { |
| 1024 | 9 | m_lastFlipWarnMS = syncEnd; | |
| 1025 | 9 | m_warnedEventTotal = eventTotal; | |
| 1026 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | LogWarn(VB_CHANNELOUT, "KMSFrameBuffer %s flip-path event; totals since start: %u syncs >20ms (max %dms, last %dms), %u wait timeouts, %u flip rejects, %u vblank cadence breaks (deltas 1/2/3/4+: %u/%u/%u/%u), %u phase nudges\n", |
| 1027 | m_connectorName.c_str(), m_slowSyncs, m_maxSyncMS, m_lastSlowSyncMS, m_flipWaitTimeouts, m_flipRejects, | ||
| 1028 | m_vblankCadenceBreaks, | ||
| 1029 | m_vblankDeltaCounts[1], m_vblankDeltaCounts[2], m_vblankDeltaCounts[3], m_vblankDeltaCounts[4], | ||
| 1030 | m_phaseNudges); | ||
| 1031 | } | ||
| 1032 | } | ||
| 1033 | } | ||
| 1034 |
2/2✓ Branch 0 taken 1858 times.
✓ Branch 1 taken 86 times.
|
1944 | } |
| 1035 | |||
| 1036 | ✗ | void KMSFrameBuffer::DisableDisplay() { | |
| 1037 | ✗ | std::unique_lock<std::mutex> lock(drmMasterLock); | |
| 1038 | ✗ | if (MediaHoldsConnector()) { | |
| 1039 | ✗ | return; | |
| 1040 | } | ||
| 1041 | { | ||
| 1042 | ✗ | if (DrmCards::EnsureMaster(m_cardFd)) { | |
| 1043 | ✗ | if (m_crtcId && m_planeId) { | |
| 1044 | ✗ | drmModeSetPlane(m_cardFd, m_planeId, m_crtcId, m_fb[m_cPage].fb_id, 0, | |
| 1045 | 0, 0, 0, 0, 0, 0, 0, 0); | ||
| 1046 | ✗ | drmModeSetCrtc(m_cardFd, m_crtcId, 0, 0, 0, nullptr, 0, nullptr); | |
| 1047 | ✗ | m_displayEnabled = false; | |
| 1048 | ✗ | m_flipPending = false; | |
| 1049 | } | ||
| 1050 | } | ||
| 1051 | } | ||
| 1052 | ✗ | } | |
| 1053 | |||
| 1054 | 11 | int KMSFrameBuffer::ModesetLocked() { | |
| 1055 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
|
11 | if (!m_planeId) { |
| 1056 | ✗ | return drmModeSetCrtc(m_cardFd, m_crtcId, m_fb[m_cPage].fb_id, 0, 0, | |
| 1057 | ✗ | &m_connectorId, 1, &m_mode); | |
| 1058 | } | ||
| 1059 | |||
| 1060 | // Legacy SetCrtc scans the CRTC's primary plane out at the full mode size, | ||
| 1061 | // so it rejects (ENOSPC) a framebuffer smaller than the mode - and ours is | ||
| 1062 | // only the model times its pixel size, which is often short of the panel. | ||
| 1063 | // Without this, a CRTC that went off could never be turned back on: every | ||
| 1064 | // earlier "modeset" here failed and only worked because the console had | ||
| 1065 | // left the CRTC running. Light it with a blank mode-sized buffer instead, | ||
| 1066 | // put the picture on our (scaling) plane, then take the blank one away. | ||
| 1067 | 11 | DumbBuffer blank; | |
| 1068 |
4/4✓ Branch 0 taken 8 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 2 times.
|
11 | bool useBlank = m_fb[m_cPage].width < m_mode.hdisplay || m_fb[m_cPage].height < m_mode.vdisplay; |
| 1069 |
5/8✓ Branch 0 taken 9 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 9 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 11 times.
|
11 | if (useBlank && !CreateDumbBuffer(m_cardFd, m_mode.hdisplay, m_mode.vdisplay, DRM_FORMAT_RGB565, blank) && |
| 1070 | ✗ | !CreateDumbBuffer(m_cardFd, m_mode.hdisplay, m_mode.vdisplay, DRM_FORMAT_XRGB8888, blank)) { | |
| 1071 | ✗ | useBlank = false; | |
| 1072 | } | ||
| 1073 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 2 times.
|
11 | if (useBlank) { |
| 1074 | 9 | memset(blank.mapped, 0, blank.size); | |
| 1075 | } | ||
| 1076 |
3/4✓ Branch 0 taken 9 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
|
11 | int ret = drmModeSetCrtc(m_cardFd, m_crtcId, useBlank ? blank.fb_id : m_fb[m_cPage].fb_id, 0, 0, |
| 1077 | &m_connectorId, 1, &m_mode); | ||
| 1078 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
|
11 | if (ret) { |
| 1079 | // Still place the plane: that is enough when the CRTC is already | ||
| 1080 | // running this mode, which is all the old code ever managed. | ||
| 1081 | ✗ | LogWarn(VB_CHANNELOUT, "KMSFrameBuffer %s: modeset failed: %d (%s)\n", m_connectorName.c_str(), ret, strerror(errno)); | |
| 1082 | } | ||
| 1083 | 22 | ret = drmModeSetPlane(m_cardFd, m_planeId, m_crtcId, m_fb[m_cPage].fb_id, 0, | |
| 1084 | 11 | 0, 0, m_mode.hdisplay, m_mode.vdisplay, | |
| 1085 |
1/2✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
|
11 | 0, 0, (uint32_t)m_width << 16, (uint32_t)m_height << 16); |
| 1086 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 2 times.
|
11 | if (useBlank) { |
| 1087 | // Unless our plane is the primary (and has just replaced it), the blank | ||
| 1088 | // buffer is still on the primary plane. Take it off explicitly: removing | ||
| 1089 | // an in-use framebuffer can make the kernel turn the whole CRTC off. | ||
| 1090 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | drmModePlaneResPtr planes = drmModeGetPlaneResources(m_cardFd); |
| 1091 |
3/4✓ Branch 0 taken 201 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 192 times.
✓ Branch 3 taken 9 times.
|
201 | for (uint32_t i = 0; planes && i < planes->count_planes; i++) { |
| 1092 |
1/2✓ Branch 0 taken 192 times.
✗ Branch 1 not taken.
|
192 | drmModePlanePtr plane = drmModeGetPlane(m_cardFd, planes->planes[i]); |
| 1093 |
2/4✓ Branch 0 taken 192 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 192 times.
|
192 | if (plane && plane->fb_id == blank.fb_id) { |
| 1094 | ✗ | drmModeSetPlane(m_cardFd, plane->plane_id, m_crtcId, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); | |
| 1095 | } | ||
| 1096 |
1/2✓ Branch 0 taken 192 times.
✗ Branch 1 not taken.
|
192 | if (plane) { |
| 1097 |
1/2✓ Branch 0 taken 192 times.
✗ Branch 1 not taken.
|
192 | drmModeFreePlane(plane); |
| 1098 | } | ||
| 1099 | } | ||
| 1100 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | if (planes) { |
| 1101 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | drmModeFreePlaneResources(planes); |
| 1102 | } | ||
| 1103 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | DestroyDumbBuffer(m_cardFd, blank); |
| 1104 | } | ||
| 1105 | 11 | return ret; | |
| 1106 | } | ||
| 1107 | |||
| 1108 | 9 | void KMSFrameBuffer::EnableDisplay() { | |
| 1109 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | LogDebug(VB_CHANNELOUT, "KMSFrameBuffer::EnableDisplay() called for %s\n", m_connectorName.c_str()); |
| 1110 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | std::unique_lock<std::mutex> lock(drmMasterLock); |
| 1111 |
2/4✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
|
9 | if (MediaHoldsConnector()) { |
| 1112 | ✗ | LogDebug(VB_CHANNELOUT, " Skipping: media is playing on this connector\n"); | |
| 1113 | ✗ | return; | |
| 1114 | } | ||
| 1115 | { | ||
| 1116 |
2/4✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
|
9 | if (DrmCards::EnsureMaster(m_cardFd)) { |
| 1117 |
2/4✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
|
9 | if (m_crtcId && m_planeId) { |
| 1118 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | int ret = ModesetLocked(); |
| 1119 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (ret) { |
| 1120 | ✗ | LogErr(VB_CHANNELOUT, "KMSFrameBuffer::EnableDisplay set_plane failed: %d\n", ret); | |
| 1121 | } else { | ||
| 1122 | 9 | m_displayEnabled = true; | |
| 1123 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | LogInfo(VB_CHANNELOUT, "KMSFrameBuffer::EnableDisplay: SUCCESS for %s\n", m_connectorName.c_str()); |
| 1124 | } | ||
| 1125 | } | ||
| 1126 | } else { | ||
| 1127 | ✗ | LogWarn(VB_CHANNELOUT, "KMSFrameBuffer::EnableDisplay: Failed to get DRM master for %s\n", m_connectorName.c_str()); | |
| 1128 | } | ||
| 1129 | } | ||
| 1130 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | } |
| 1131 | |||
| 1132 | // Minimum vertical blanking (scanlines) to keep between vactive and vtotal. The | ||
| 1133 | // RP1 DPI generates a low signal on the data pins during blanking, so this also | ||
| 1134 | // contributes to the WS281x reset gap; the caller (DPIPixelsOutput) additionally | ||
| 1135 | // pads vactive with blank scanlines so the reset is guaranteed regardless. | ||
| 1136 | static constexpr int KMS_MIN_VBLANK_LINES = 3; | ||
| 1137 | |||
| 1138 | 13 | void KMSFrameBuffer::ApplyVerticalTiming(drmModeModeInfo& mode, int vactive, int fps) { | |
| 1139 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
|
13 | if (mode.htotal == 0) { |
| 1140 | ✗ | return; | |
| 1141 | } | ||
| 1142 | 13 | int minVtotal = vactive + KMS_MIN_VBLANK_LINES; | |
| 1143 | 13 | int vtotal = minVtotal; | |
| 1144 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | if (fps > 0) { |
| 1145 | // pixel clock is kHz; vtotal = clockHz / (htotal * fps) | ||
| 1146 | 13 | long long v = llround((double)mode.clock * 1000.0 / ((double)mode.htotal * (double)fps)); | |
| 1147 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
|
13 | if (v > minVtotal) { |
| 1148 | ✗ | vtotal = (int)v; | |
| 1149 | } | ||
| 1150 | } | ||
| 1151 | 13 | mode.vdisplay = vactive; | |
| 1152 | 13 | mode.vsync_start = vactive + 1; | |
| 1153 | 13 | mode.vsync_end = vactive + 2; | |
| 1154 | 13 | mode.vtotal = vtotal; | |
| 1155 | 13 | mode.vrefresh = (uint32_t)llround((double)mode.clock * 1000.0 / ((double)mode.htotal * (double)vtotal)); | |
| 1156 | 13 | snprintf(mode.name, sizeof(mode.name), "%dx%d", mode.hdisplay, vactive); | |
| 1157 | } | ||
| 1158 | |||
| 1159 | 13 | int KMSFrameBuffer::GetMaxRefreshRate() { | |
| 1160 |
2/4✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 13 times.
|
13 | if (!m_variableRefresh || m_mode.htotal == 0) { |
| 1161 | ✗ | return 0; | |
| 1162 | } | ||
| 1163 | 13 | int minVtotal = m_mode.vdisplay + KMS_MIN_VBLANK_LINES; | |
| 1164 | 13 | return (int)((double)m_mode.clock * 1000.0 / ((double)m_mode.htotal * (double)minVtotal)); | |
| 1165 | } | ||
| 1166 | |||
| 1167 | 110 | bool KMSFrameBuffer::SetRefreshRate(int fps) { | |
| 1168 |
3/6✓ Branch 0 taken 110 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 110 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 110 times.
|
110 | if (!m_variableRefresh || fps <= 0 || m_mode.htotal == 0) { |
| 1169 | ✗ | return false; | |
| 1170 | } | ||
| 1171 | |||
| 1172 | 110 | int minVtotal = m_mode.vdisplay + KMS_MIN_VBLANK_LINES; | |
| 1173 | 110 | long long v = llround((double)m_mode.clock * 1000.0 / ((double)m_mode.htotal * (double)fps)); | |
| 1174 |
1/2✓ Branch 0 taken 110 times.
✗ Branch 1 not taken.
|
110 | int newVtotal = (v > minVtotal) ? (int)v : minVtotal; |
| 1175 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 110 times.
|
110 | if (newVtotal == m_mode.vtotal) { |
| 1176 | ✗ | return true; // already at this rate | |
| 1177 | } | ||
| 1178 | |||
| 1179 | // Only the vertical blanking changes; horizontal timing and the pixel clock | ||
| 1180 | // (and therefore the WS bit timing and the framebuffer layout) are untouched. | ||
| 1181 | // Built in a copy: m_mode is what the CRTC is running, which the check | ||
| 1182 | // above and the flip timeouts depend on. Updating it before the modeset | ||
| 1183 | // happened made a refused attempt look applied - the next call returned | ||
| 1184 | // "already at this rate" and the new rate was never set. | ||
| 1185 | 110 | drmModeModeInfo mode = m_mode; | |
| 1186 | 110 | mode.vsync_start = mode.vdisplay + 1; | |
| 1187 | 110 | mode.vsync_end = mode.vdisplay + 2; | |
| 1188 | 110 | mode.vtotal = newVtotal; | |
| 1189 | 110 | mode.vrefresh = (uint32_t)llround((double)mode.clock * 1000.0 / ((double)mode.htotal * (double)newVtotal)); | |
| 1190 | |||
| 1191 |
1/2✓ Branch 0 taken 110 times.
✗ Branch 1 not taken.
|
110 | std::unique_lock<std::mutex> lock(drmMasterLock); |
| 1192 |
3/4✓ Branch 0 taken 110 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 89 times.
✓ Branch 3 taken 21 times.
|
110 | if (MediaHoldsConnector()) { |
| 1193 | // The connector is currently presenting media playback; don't fight it. | ||
| 1194 | // Report "not applied" so the caller's idea of the live rate stays | ||
| 1195 | // honest and it asks again next frame. | ||
| 1196 | 89 | return false; | |
| 1197 | } | ||
| 1198 |
2/4✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 21 times.
|
21 | if (!DrmCards::EnsureMaster(m_cardFd)) { |
| 1199 | // Couldn't grab master right now. As above, not applied; the caller | ||
| 1200 | // asks again. | ||
| 1201 | ✗ | return false; | |
| 1202 | } | ||
| 1203 | // Drain any in-flight flip so the modeset starts from a known state. | ||
| 1204 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 11 times.
|
21 | if (m_flipPending) { |
| 1205 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | int vr = m_mode.vrefresh > 0 ? m_mode.vrefresh : 60; |
| 1206 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | WaitForPendingFlip((1000 / vr) + 20); |
| 1207 | } | ||
| 1208 |
1/2✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
|
21 | int ret = drmModeSetCrtc(m_cardFd, m_crtcId, m_fb[m_cPage].fb_id, 0, 0, |
| 1209 | &m_connectorId, 1, &mode); | ||
| 1210 |
2/2✓ Branch 0 taken 19 times.
✓ Branch 1 taken 2 times.
|
21 | if (ret == 0) { |
| 1211 | 19 | m_mode = mode; | |
| 1212 | 19 | m_displayEnabled = true; | |
| 1213 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
19 | if (m_planeId) { |
| 1214 | 19 | drmModeSetPlane(m_cardFd, m_planeId, m_crtcId, m_fb[m_cPage].fb_id, 0, | |
| 1215 | 19 | 0, 0, m_mode.hdisplay, m_mode.vdisplay, | |
| 1216 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
19 | 0, 0, (uint32_t)m_width << 16, (uint32_t)m_height << 16); |
| 1217 | } | ||
| 1218 | // A mode change restarts the vblank stream; clear the cadence trackers so | ||
| 1219 | // the transition isn't miscounted as a stutter. | ||
| 1220 | 19 | m_flipPending = false; | |
| 1221 | 19 | m_lastFlipSeq = 0; | |
| 1222 | 19 | m_prevVblankDelta = 0; | |
| 1223 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
19 | LogInfo(VB_CHANNELOUT, "KMSFrameBuffer %s: refresh set to %dHz (vtotal=%d)\n", |
| 1224 | m_connectorName.c_str(), m_mode.vrefresh, newVtotal); | ||
| 1225 | } else { | ||
| 1226 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | LogErr(VB_CHANNELOUT, "KMSFrameBuffer::SetRefreshRate modeset failed: %d (%s)\n", ret, strerror(errno)); |
| 1227 | } | ||
| 1228 | 21 | return ret == 0; | |
| 1229 | 110 | } | |
| 1230 | |||
| 1231 | // --------------------------------------------------------------------------- | ||
| 1232 | // Display hotplug | ||
| 1233 | // --------------------------------------------------------------------------- | ||
| 1234 | |||
| 1235 | // Quiet time after the last uevent before acting on a batch. A sink powering | ||
| 1236 | // up can bounce HPD a few times, and acting once on the settled state avoids a | ||
| 1237 | // modeset per bounce. | ||
| 1238 | static constexpr int HOTPLUG_SETTLE_MS = 1000; | ||
| 1239 | // How often an output without a display re-probes for one. Events cover | ||
| 1240 | // most of it, but vc4.force_hotplug (the ForceHDMI setting) makes the kernel | ||
| 1241 | // ignore HPD: a projector switched on later sends nothing, and without this | ||
| 1242 | // the output would run the stand-in mode, under a stale warning, for good. | ||
| 1243 | static constexpr int ABSENT_RECHECK_MS = 10000; | ||
| 1244 | |||
| 1245 | static std::mutex s_hotplugLock; // guards the three below | ||
| 1246 | static std::thread* s_hotplugThread = nullptr; | ||
| 1247 | static int s_hotplugSock = -1; | ||
| 1248 | static int s_hotplugWakeFd = -1; | ||
| 1249 | |||
| 1250 | 23 | void KMSFrameBuffer::StartHotplugMonitor() { | |
| 1251 |
1/2✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
|
23 | std::unique_lock<std::mutex> lock(s_hotplugLock); |
| 1252 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 23 times.
|
23 | if (s_hotplugThread) { |
| 1253 | ✗ | return; | |
| 1254 | } | ||
| 1255 | // The kernel's own uevent multicast group (1), not udevd's re-broadcast: | ||
| 1256 | // it needs no udevd and no libudev, and fppd runs as root. | ||
| 1257 | 23 | int sock = socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, NETLINK_KOBJECT_UEVENT); | |
| 1258 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 23 times.
|
23 | if (sock < 0) { |
| 1259 | ✗ | LogWarn(VB_CHANNELOUT, "KMSFrameBuffer: no uevent socket, display hotplug will not be handled: %s\n", strerror(errno)); | |
| 1260 | ✗ | return; | |
| 1261 | } | ||
| 1262 | 23 | struct sockaddr_nl addr = {}; | |
| 1263 | 23 | addr.nl_family = AF_NETLINK; | |
| 1264 | 23 | addr.nl_groups = 1; | |
| 1265 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 23 times.
|
23 | if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { |
| 1266 | ✗ | LogWarn(VB_CHANNELOUT, "KMSFrameBuffer: could not bind the uevent socket, display hotplug will not be handled: %s\n", strerror(errno)); | |
| 1267 | ✗ | close(sock); | |
| 1268 | ✗ | return; | |
| 1269 | } | ||
| 1270 | 23 | int wakeFd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); | |
| 1271 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 23 times.
|
23 | if (wakeFd < 0) { |
| 1272 | ✗ | close(sock); | |
| 1273 | ✗ | return; | |
| 1274 | } | ||
| 1275 | 23 | s_hotplugSock = sock; | |
| 1276 | 23 | s_hotplugWakeFd = wakeFd; | |
| 1277 |
3/8✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 23 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
23 | s_hotplugThread = new std::thread(HotplugMonitorLoop, sock, wakeFd); |
| 1278 |
1/2✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
|
23 | } |
| 1279 | |||
| 1280 | 19 | void KMSFrameBuffer::StopHotplugMonitor() { | |
| 1281 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
19 | std::unique_lock<std::mutex> lock(s_hotplugLock); |
| 1282 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
|
19 | if (!s_hotplugThread) { |
| 1283 | ✗ | return; | |
| 1284 | } | ||
| 1285 | 19 | uint64_t one = 1; | |
| 1286 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
19 | write(s_hotplugWakeFd, &one, sizeof(one)); |
| 1287 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
19 | s_hotplugThread->join(); |
| 1288 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
19 | delete s_hotplugThread; |
| 1289 | 19 | s_hotplugThread = nullptr; | |
| 1290 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
19 | close(s_hotplugSock); |
| 1291 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
19 | close(s_hotplugWakeFd); |
| 1292 | 19 | s_hotplugSock = -1; | |
| 1293 | 19 | s_hotplugWakeFd = -1; | |
| 1294 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
19 | } |
| 1295 | |||
| 1296 | 23 | void KMSFrameBuffer::HotplugMonitorLoop(int sock, int wakeFd) { | |
| 1297 |
2/4✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
|
23 | SetThreadName("FPP-DRMHotplug"); |
| 1298 | |||
| 1299 | // card path ("/dev/dri/card1") -> connector ids named by the batch's | ||
| 1300 | // events; 0 for an event that named none (a sysfs status write, polled | ||
| 1301 | // connectors). Any event re-probes every output on its card. | ||
| 1302 | 23 | std::map<std::string, std::set<uint32_t>> pending; | |
| 1303 | 23 | long long deadline = 0; | |
| 1304 |
1/2✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
|
23 | long long nextRecheck = GetMonotonicTimeMS() + ABSENT_RECHECK_MS; |
| 1305 | char buf[8192]; | ||
| 1306 | |||
| 1307 | while (true) { | ||
| 1308 | 32 | struct pollfd pfds[2] = {}; | |
| 1309 | 32 | pfds[0].fd = sock; | |
| 1310 | 32 | pfds[0].events = POLLIN; | |
| 1311 | 32 | pfds[1].fd = wakeFd; | |
| 1312 | 32 | pfds[1].events = POLLIN; | |
| 1313 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 32 times.
|
32 | long long wake = deadline ? std::min(deadline, nextRecheck) : nextRecheck; |
| 1314 |
1/2✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
|
32 | int timeout = (int)std::max(0LL, wake - GetMonotonicTimeMS()); |
| 1315 |
1/2✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
|
32 | int r = poll(pfds, 2, timeout); |
| 1316 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
28 | if (r < 0 && errno != EINTR) { |
| 1317 | ✗ | LogWarn(VB_CHANNELOUT, "KMSFrameBuffer: uevent poll failed, display hotplug will no longer be handled: %s\n", strerror(errno)); | |
| 1318 | ✗ | return; | |
| 1319 | } | ||
| 1320 |
2/2✓ Branch 0 taken 19 times.
✓ Branch 1 taken 9 times.
|
28 | if (pfds[1].revents & POLLIN) { |
| 1321 | 19 | return; | |
| 1322 | } | ||
| 1323 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 5 times.
|
9 | if (pfds[0].revents & POLLIN) { |
| 1324 | ssize_t len; | ||
| 1325 |
3/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 4 times.
|
8 | while ((len = recv(sock, buf, sizeof(buf) - 1, 0)) > 0) { |
| 1326 | // "change@/devices/...\0ACTION=change\0SUBSYSTEM=drm\0HOTPLUG=1\0..." | ||
| 1327 | 4 | buf[len] = 0; | |
| 1328 | 4 | std::string subsystem, devname; | |
| 1329 | 4 | bool hotplug = false; | |
| 1330 | 4 | uint32_t connectorId = 0; | |
| 1331 |
2/2✓ Branch 0 taken 40 times.
✓ Branch 1 taken 4 times.
|
44 | for (char* p = buf; p < buf + len; p += strlen(p) + 1) { |
| 1332 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 36 times.
|
40 | if (!strncmp(p, "SUBSYSTEM=", 10)) { |
| 1333 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | subsystem = p + 10; |
| 1334 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 32 times.
|
36 | } else if (!strncmp(p, "DEVNAME=", 8)) { |
| 1335 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | devname = p + 8; |
| 1336 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 32 times.
|
32 | } else if (!strcmp(p, "HOTPLUG=1")) { |
| 1337 | ✗ | hotplug = true; | |
| 1338 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 32 times.
|
32 | } else if (!strncmp(p, "CONNECTOR=", 10)) { |
| 1339 | ✗ | connectorId = (uint32_t)strtoul(p + 10, nullptr, 10); | |
| 1340 | } | ||
| 1341 | } | ||
| 1342 |
4/10✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 4 times.
✗ Branch 9 not taken.
|
4 | if (subsystem != "drm" || !hotplug || devname.empty()) { |
| 1343 | 4 | continue; | |
| 1344 | } | ||
| 1345 | ✗ | LogDebug(VB_CHANNELOUT, "KMSFrameBuffer: hotplug uevent on %s connector %u\n", devname.c_str(), connectorId); | |
| 1346 | ✗ | pending["/dev/" + devname].insert(connectorId); | |
| 1347 | ✗ | deadline = GetMonotonicTimeMS() + HOTPLUG_SETTLE_MS; | |
| 1348 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
|
8 | } |
| 1349 | } | ||
| 1350 |
2/8✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 9 times.
|
9 | if (deadline && GetMonotonicTimeMS() >= deadline) { |
| 1351 | ✗ | for (auto& [cardPath, connectorIds] : pending) { | |
| 1352 | ✗ | HandleHotplug(cardPath, connectorIds, false); | |
| 1353 | } | ||
| 1354 | ✗ | pending.clear(); | |
| 1355 | ✗ | deadline = 0; | |
| 1356 | } | ||
| 1357 |
3/4✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 4 times.
|
9 | if (GetMonotonicTimeMS() >= nextRecheck) { |
| 1358 | 5 | std::set<std::string> cards; | |
| 1359 | { | ||
| 1360 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | std::unique_lock<std::mutex> lock(drmMasterLock); |
| 1361 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
|
10 | for (auto& [token, fb] : FLIP_TARGETS) { |
| 1362 |
3/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 3 times.
|
5 | if (fb->m_hotplugReady && !fb->m_sinkPresent) { |
| 1363 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | cards.insert(fb->m_cardInfo->path); |
| 1364 | } | ||
| 1365 | } | ||
| 1366 | 5 | } | |
| 1367 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 5 times.
|
7 | for (auto& cardPath : cards) { |
| 1368 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | HandleHotplug(cardPath, {}, true); |
| 1369 | } | ||
| 1370 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | nextRecheck = GetMonotonicTimeMS() + ABSENT_RECHECK_MS; |
| 1371 | 5 | } | |
| 1372 | 9 | } | |
| 1373 | 19 | } | |
| 1374 | |||
| 1375 | 2 | void KMSFrameBuffer::HandleHotplug(const std::string& cardPath, const std::set<uint32_t>& connectorIds, bool onlyAbsent) { | |
| 1376 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | std::unique_lock<std::mutex> lock(drmMasterLock); |
| 1377 | 2 | std::vector<std::pair<uint32_t, std::string>> outputs; | |
| 1378 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
|
4 | for (auto& [token, fb] : FLIP_TARGETS) { |
| 1379 |
5/10✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
✗ Branch 9 not taken.
|
2 | if (fb->m_hotplugReady && fb->m_cardInfo->path == cardPath && !(onlyAbsent && fb->m_sinkPresent)) { |
| 1380 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | outputs.emplace_back(token, fb->m_connectorName); |
| 1381 | } | ||
| 1382 | } | ||
| 1383 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
|
4 | for (auto& [token, name] : outputs) { |
| 1384 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | auto it = FLIP_TARGETS.find(token); |
| 1385 |
3/6✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
|
2 | if (it == FLIP_TARGETS.end() || !it->second->m_hotplugReady) { |
| 1386 | 2 | continue; | |
| 1387 | } | ||
| 1388 | 2 | KMSFrameBuffer* fb = it->second; | |
| 1389 | |||
| 1390 | // A real probe (see ProbeConnectorLocked()), so it also refills the | ||
| 1391 | // sysfs mode list that was emptied while the display was away. Every | ||
| 1392 | // output's flips wait out this EDID read, once per hotplug. | ||
| 1393 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | drmModeConnectorPtr conn = ProbeConnectorLocked(fb->m_cardFd, fb->m_connectorId); |
| 1394 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | bool present = SinkPresent(fb->m_cardFd, conn); |
| 1395 | 2 | drmModeModeInfo mode = {}; | |
| 1396 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
2 | bool haveMode = present && PickMode(conn, mode); |
| 1397 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (conn) { |
| 1398 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | drmModeFreeConnector(conn); |
| 1399 | } | ||
| 1400 | |||
| 1401 | 2 | bool wasPresent = fb->m_sinkPresent; | |
| 1402 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | fb->SetSinkPresentLocked(present); |
| 1403 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (!present) { |
| 1404 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (wasPresent) { |
| 1405 | ✗ | LogInfo(VB_CHANNELOUT, "KMSFrameBuffer %s: display disconnected\n", name.c_str()); | |
| 1406 | } | ||
| 1407 | 2 | continue; | |
| 1408 | } | ||
| 1409 | // Back after we saw it gone. An event naming this connector while it | ||
| 1410 | // looks present also counts: the sink went away and came back before | ||
| 1411 | // the batch settled, or (with force_hotplug) its EDID changed. A | ||
| 1412 | // card-wide event alone does not: it may be about another connector, | ||
| 1413 | // and a needless modeset blanks this one (or glitches DPI pixels). | ||
| 1414 | ✗ | if (wasPresent && !connectorIds.count(fb->m_connectorId)) { | |
| 1415 | ✗ | continue; | |
| 1416 | } | ||
| 1417 | ✗ | LogInfo(VB_CHANNELOUT, "KMSFrameBuffer %s: display %s\n", name.c_str(), | |
| 1418 | wasPresent ? "re-announced itself" : "reconnected"); | ||
| 1419 | ✗ | if (fb->MediaHoldsConnector()) { | |
| 1420 | ✗ | LogInfo(VB_CHANNELOUT, "KMSFrameBuffer %s: media owns the connector, not restarting the display\n", name.c_str()); | |
| 1421 | ✗ | continue; | |
| 1422 | } | ||
| 1423 | // Run the display's own mode: the one we have may have been the stand-in | ||
| 1424 | // for a display that wasn't there yet, or this may be another display. | ||
| 1425 | // The framebuffer stays as it is; the plane scales it onto whatever mode | ||
| 1426 | // the CRTC runs. (DPI Pixels sets its own timing, but is never | ||
| 1427 | // external, so never gets here.) | ||
| 1428 | ✗ | if (haveMode && !fb->m_variableRefresh && !SameTiming(mode, fb->m_mode)) { | |
| 1429 | ✗ | LogInfo(VB_CHANNELOUT, "KMSFrameBuffer %s: switching to the display's own mode %s (was %s)\n", | |
| 1430 | name.c_str(), ModeLine(mode).c_str(), ModeLine(fb->m_mode).c_str()); | ||
| 1431 | ✗ | fb->m_mode = mode; | |
| 1432 | } | ||
| 1433 | ✗ | if (fb->m_displayEnabled) { | |
| 1434 | ✗ | fb->RestartDisplayLocked(); | |
| 1435 | } | ||
| 1436 | // Otherwise it isn't lit yet, and EnableDisplay() or SyncDisplay() | ||
| 1437 | // will light it in m_mode. | ||
| 1438 | } | ||
| 1439 | 2 | } | |
| 1440 | |||
| 1441 | ✗ | void KMSFrameBuffer::RestartDisplayLocked() { | |
| 1442 | ✗ | if (!DrmCards::EnsureMaster(m_cardFd)) { | |
| 1443 | ✗ | LogWarn(VB_CHANNELOUT, "KMSFrameBuffer %s: could not get DRM master to restart the display\n", m_connectorName.c_str()); | |
| 1444 | ✗ | return; | |
| 1445 | } | ||
| 1446 | ✗ | if (m_flipPending) { | |
| 1447 | ✗ | int vr = m_mode.vrefresh > 0 ? m_mode.vrefresh : 60; | |
| 1448 | ✗ | WaitForPendingFlip((1000 / vr) + 20); | |
| 1449 | } | ||
| 1450 | // Off first. SetCrtc with the mode the CRTC already runs is not a modeset | ||
| 1451 | // to the atomic helpers - it just swaps the framebuffer - and it is the | ||
| 1452 | // modeset that restarts the HDMI link the sink has to lock onto. | ||
| 1453 | ✗ | if (m_planeId) { | |
| 1454 | ✗ | drmModeSetPlane(m_cardFd, m_planeId, m_crtcId, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); | |
| 1455 | } | ||
| 1456 | ✗ | drmModeSetCrtc(m_cardFd, m_crtcId, 0, 0, 0, nullptr, 0, nullptr); | |
| 1457 | |||
| 1458 | ✗ | int ret = ModesetLocked(); | |
| 1459 | ✗ | if (ret == 0) { | |
| 1460 | ✗ | LogInfo(VB_CHANNELOUT, "KMSFrameBuffer %s: display restarted at %dx%d @ %dHz\n", | |
| 1461 | m_connectorName.c_str(), m_mode.hdisplay, m_mode.vdisplay, m_mode.vrefresh); | ||
| 1462 | } else { | ||
| 1463 | // Leave it disabled so the next SyncDisplay() tries the modeset again. | ||
| 1464 | ✗ | LogWarn(VB_CHANNELOUT, "KMSFrameBuffer %s: display restart failed: %d (%s)\n", | |
| 1465 | m_connectorName.c_str(), ret, strerror(errno)); | ||
| 1466 | } | ||
| 1467 | ✗ | m_displayEnabled = (ret == 0); | |
| 1468 | // The modeset restarts the vblank stream; don't count it as a stutter. | ||
| 1469 | ✗ | m_lastFlipSeq = 0; | |
| 1470 | ✗ | m_prevVblankDelta = 0; | |
| 1471 | ✗ | m_lastFlipTimeUS = 0; | |
| 1472 | } | ||
| 1473 | |||
| 1474 | #endif | ||
| 1475 |