KWin
Loading...
Searching...
No Matches
drm_connector.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2016 Roman Gilg <subdiff@gmail.com>
6 SPDX-FileCopyrightText: 2021 Xaver Hugl <xaver.hugl@gmail.com>
7
8 SPDX-License-Identifier: GPL-2.0-or-later
9*/
10#include "drm_connector.h"
11#include "drm_commit.h"
12#include "drm_crtc.h"
13#include "drm_gpu.h"
14#include "drm_logging.h"
15#include "drm_output.h"
16#include "drm_pipeline.h"
17#include "drm_pointer.h"
18
19#include <cerrno>
20#include <cstring>
21#include <libxcvt/libxcvt.h>
22
23namespace KWin
24{
25
26static QSize resolutionForMode(const drmModeModeInfo *info)
27{
28 return QSize(info->hdisplay, info->vdisplay);
29}
30
31static quint64 refreshRateForMode(_drmModeModeInfo *m)
32{
33 // Calculate higher precision (mHz) refresh rate
34 // logic based on Weston, see compositor-drm.c
35 quint64 refreshRate = (m->clock * 1000000LL / m->htotal + m->vtotal / 2) / m->vtotal;
36 if (m->flags & DRM_MODE_FLAG_INTERLACE) {
37 refreshRate *= 2;
38 }
39 if (m->flags & DRM_MODE_FLAG_DBLSCAN) {
40 refreshRate /= 2;
41 }
42 if (m->vscan > 1) {
43 refreshRate /= m->vscan;
44 }
45 return refreshRate;
46}
47
48static OutputMode::Flags flagsForMode(const drmModeModeInfo *info, OutputMode::Flags additionalFlags)
49{
50 OutputMode::Flags flags = additionalFlags;
51 if (info->type & DRM_MODE_TYPE_PREFERRED) {
53 }
54 return flags;
55}
56
57DrmConnectorMode::DrmConnectorMode(DrmConnector *connector, drmModeModeInfo nativeMode, Flags additionalFlags)
58 : OutputMode(resolutionForMode(&nativeMode), refreshRateForMode(&nativeMode), flagsForMode(&nativeMode, additionalFlags))
59 , m_connector(connector)
60 , m_nativeMode(nativeMode)
61{
62}
63
64std::shared_ptr<DrmBlob> DrmConnectorMode::blob()
65{
66 if (!m_blob) {
67 m_blob = DrmBlob::create(m_connector->gpu(), &m_nativeMode, sizeof(m_nativeMode));
68 }
69 return m_blob;
70}
71
72std::chrono::nanoseconds DrmConnectorMode::vblankTime() const
73{
74 return std::chrono::nanoseconds(((m_nativeMode.vsync_end - m_nativeMode.vsync_start) * m_nativeMode.htotal * 1'000'000ULL) / m_nativeMode.clock);
75}
76
78{
79 return &m_nativeMode;
80}
81
82static inline bool checkIfEqual(const drmModeModeInfo *one, const drmModeModeInfo *two)
83{
84 return std::memcmp(one, two, sizeof(drmModeModeInfo)) == 0;
85}
86
88{
89 return checkIfEqual(&m_nativeMode, &otherMode.m_nativeMode);
90}
91
92bool DrmConnectorMode::operator==(const drmModeModeInfo &otherMode)
93{
94 return checkIfEqual(&m_nativeMode, &otherMode);
95}
96
97DrmConnector::DrmConnector(DrmGpu *gpu, uint32_t connectorId)
98 : DrmObject(gpu, connectorId, DRM_MODE_OBJECT_CONNECTOR)
99 , crtcId(this, QByteArrayLiteral("CRTC_ID"))
100 , nonDesktop(this, QByteArrayLiteral("non-desktop"))
101 , dpms(this, QByteArrayLiteral("DPMS"))
102 , edidProp(this, QByteArrayLiteral("EDID"))
103 , overscan(this, QByteArrayLiteral("overscan"))
104 , vrrCapable(this, QByteArrayLiteral("vrr_capable"))
105 , underscan(this, QByteArrayLiteral("underscan"), {
106 QByteArrayLiteral("off"),
107 QByteArrayLiteral("on"),
108 QByteArrayLiteral("auto"),
109 })
110 , underscanVBorder(this, QByteArrayLiteral("underscan vborder"))
111 , underscanHBorder(this, QByteArrayLiteral("underscan hborder"))
112 , broadcastRGB(this, QByteArrayLiteral("Broadcast RGB"), {
113 QByteArrayLiteral("Automatic"),
114 QByteArrayLiteral("Full"),
115 QByteArrayLiteral("Limited 16:235"),
116 })
117 , maxBpc(this, QByteArrayLiteral("max bpc"))
118 , linkStatus(this, QByteArrayLiteral("link-status"), {
119 QByteArrayLiteral("Good"),
120 QByteArrayLiteral("Bad"),
121 })
122 , contentType(this, QByteArrayLiteral("content type"), {
123 QByteArrayLiteral("No Data"),
124 QByteArrayLiteral("Graphics"),
125 QByteArrayLiteral("Photo"),
126 QByteArrayLiteral("Cinema"),
127 QByteArrayLiteral("Game"),
128 })
129 , panelOrientation(this, QByteArrayLiteral("panel orientation"), {
130 QByteArrayLiteral("Normal"),
131 QByteArrayLiteral("Upside Down"),
132 QByteArrayLiteral("Left Side Up"),
133 QByteArrayLiteral("Right Side Up"),
134 })
135 , hdrMetadata(this, QByteArrayLiteral("HDR_OUTPUT_METADATA"))
136 , scalingMode(this, QByteArrayLiteral("scaling mode"), {
137 QByteArrayLiteral("None"),
138 QByteArrayLiteral("Full"),
139 QByteArrayLiteral("Center"),
140 QByteArrayLiteral("Full aspect"),
141 })
142 , colorspace(this, QByteArrayLiteral("Colorspace"), {
143 QByteArrayLiteral("Default"),
144 QByteArrayLiteral("BT709_YCC"),
145 QByteArrayLiteral("opRGB"),
146 QByteArrayLiteral("BT2020_RGB"),
147 QByteArrayLiteral("BT2020_YCC"),
148 })
149 , path(this, QByteArrayLiteral("PATH"))
150 , m_conn(drmModeGetConnector(gpu->fd(), connectorId))
151 , m_pipeline(m_conn ? std::make_unique<DrmPipeline>(this) : nullptr)
152{
153 if (m_conn) {
154 for (int i = 0; i < m_conn->count_encoders; ++i) {
155 DrmUniquePtr<drmModeEncoder> enc(drmModeGetEncoder(gpu->fd(), m_conn->encoders[i]));
156 if (!enc) {
157 qCWarning(KWIN_DRM) << "failed to get encoder" << m_conn->encoders[i];
158 continue;
159 }
160 m_possibleCrtcs |= enc->possible_crtcs;
161 }
162 } else {
163 qCWarning(KWIN_DRM) << "drmModeGetConnector failed!" << strerror(errno);
164 }
165}
166
168{
169 return !m_driverModes.empty() && m_conn && m_conn->connection == DRM_MODE_CONNECTED;
170}
171
173{
174 const char *connectorName = drmModeGetConnectorTypeName(m_conn->connector_type);
175 if (!connectorName) {
176 connectorName = "Unknown";
177 }
178 return QStringLiteral("%1-%2").arg(connectorName).arg(m_conn->connector_type_id);
179}
180
182{
183 if (m_edid.serialNumber().isEmpty()) {
184 return connectorName() + QLatin1Char('-') + m_edid.nameString();
185 } else {
186 return m_edid.nameString();
187 }
188}
189
191{
192 return m_conn->connector_type == DRM_MODE_CONNECTOR_LVDS || m_conn->connector_type == DRM_MODE_CONNECTOR_eDP
193 || m_conn->connector_type == DRM_MODE_CONNECTOR_DSI;
194}
195
197{
198 return m_physicalSize;
199}
200
201QByteArray DrmConnector::mstPath() const
202{
203 return m_mstPath;
204}
205
206QList<std::shared_ptr<DrmConnectorMode>> DrmConnector::modes() const
207{
208 return m_modes;
209}
210
211std::shared_ptr<DrmConnectorMode> DrmConnector::findMode(const drmModeModeInfo &modeInfo) const
212{
213 const auto it = std::find_if(m_modes.constBegin(), m_modes.constEnd(), [&modeInfo](const auto &mode) {
214 return checkIfEqual(mode->nativeMode(), &modeInfo);
215 });
216 return it == m_modes.constEnd() ? nullptr : *it;
217}
218
220{
221 switch (m_conn->subpixel) {
222 case DRM_MODE_SUBPIXEL_UNKNOWN:
224 case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB:
226 case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR:
228 case DRM_MODE_SUBPIXEL_VERTICAL_RGB:
230 case DRM_MODE_SUBPIXEL_VERTICAL_BGR:
232 case DRM_MODE_SUBPIXEL_NONE:
234 default:
236 }
237}
238
240{
241 if (auto connector = drmModeGetConnector(gpu()->fd(), id())) {
242 m_conn.reset(connector);
243 } else if (!m_conn) {
244 return false;
245 }
247 crtcId.update(props);
248 nonDesktop.update(props);
249 dpms.update(props);
250 edidProp.update(props);
251 overscan.update(props);
252 vrrCapable.update(props);
253 underscan.update(props);
256 broadcastRGB.update(props);
257 maxBpc.update(props);
258 linkStatus.update(props);
259 contentType.update(props);
261 hdrMetadata.update(props);
262 scalingMode.update(props);
263 colorspace.update(props);
264 path.update(props);
265
266 if (gpu()->atomicModeSetting() && !crtcId.isValid()) {
267 return false;
268 }
269
270 // parse edid
271 if (edidProp.immutableBlob()) {
272 m_edid = Edid(edidProp.immutableBlob()->data, edidProp.immutableBlob()->length);
273 if (!m_edid.isValid()) {
274 qCWarning(KWIN_DRM) << "Couldn't parse EDID for connector" << this;
275 }
276 } else if (m_conn->connection == DRM_MODE_CONNECTED) {
277 qCDebug(KWIN_DRM) << "Could not find edid for connector" << this;
278 }
279
280 // check the physical size
281 if (m_edid.physicalSize().isEmpty()) {
282 m_physicalSize = QSize(m_conn->mmWidth, m_conn->mmHeight);
283 } else {
284 m_physicalSize = m_edid.physicalSize();
285 }
286
287 // update modes
288 bool equal = m_conn->count_modes == m_driverModes.count();
289 for (int i = 0; equal && i < m_conn->count_modes; i++) {
290 equal &= checkIfEqual(m_driverModes[i]->nativeMode(), &m_conn->modes[i]);
291 }
292 if (!equal && m_conn->count_modes > 0) {
293 // reload modes
294 m_driverModes.clear();
295 for (int i = 0; i < m_conn->count_modes; i++) {
296 m_driverModes.append(std::make_shared<DrmConnectorMode>(this, m_conn->modes[i], OutputMode::Flags()));
297 }
298 m_modes.clear();
299 m_modes.append(m_driverModes);
301 m_modes.append(generateCommonModes());
302 }
303 if (m_pipeline->mode()) {
304 if (const auto mode = findMode(*m_pipeline->mode()->nativeMode())) {
305 m_pipeline->setMode(mode);
306 } else {
307 m_pipeline->setMode(m_modes.constFirst());
308 }
309 } else {
310 m_pipeline->setMode(m_modes.constFirst());
311 }
312 m_pipeline->applyPendingChanges();
313 if (m_pipeline->output()) {
314 m_pipeline->output()->updateModes();
315 }
316 }
317
318 m_mstPath.clear();
319 if (auto blob = path.immutableBlob()) {
320 QByteArray value = QByteArray(static_cast<const char *>(blob->data), blob->length);
321 if (value.startsWith("mst:")) {
322 // for backwards compatibility reasons the string also contains the drm connector id
323 // remove that to get a more stable identifier
324 const ssize_t firstHyphen = value.indexOf('-');
325 if (firstHyphen > 0) {
326 m_mstPath = value.mid(firstHyphen);
327 } else {
328 qCWarning(KWIN_DRM) << "Unexpected format in path property:" << value;
329 }
330 } else {
331 qCWarning(KWIN_DRM) << "Unknown path type detected:" << value;
332 }
333 }
334
335 return true;
336}
337
339{
340 return (m_possibleCrtcs & (1 << crtc->pipeIndex()));
341}
342
344{
345 return nonDesktop.isValid() && nonDesktop.value() == 1;
346}
347
349{
350 return &m_edid;
351}
352
354{
355 return m_pipeline.get();
356}
357
359{
360 commit->addProperty(crtcId, 0);
361}
362
363static const QList<QSize> s_commonModes = {
364 /* 4:3 (1.33) */
365 QSize(1600, 1200),
366 QSize(1280, 1024), /* 5:4 (1.25) */
367 QSize(1024, 768),
368 /* 16:10 (1.6) */
369 QSize(2560, 1600),
370 QSize(1920, 1200),
371 QSize(1280, 800),
372 /* 16:9 (1.77) */
373 QSize(5120, 2880),
374 QSize(3840, 2160),
375 QSize(3200, 1800),
376 QSize(2880, 1620),
377 QSize(2560, 1440),
378 QSize(1920, 1080),
379 QSize(1600, 900),
380 QSize(1368, 768),
381 QSize(1280, 720),
382};
383
384QList<std::shared_ptr<DrmConnectorMode>> DrmConnector::generateCommonModes()
385{
386 QList<std::shared_ptr<DrmConnectorMode>> ret;
387 QSize maxSize;
388 uint32_t maxSizeRefreshRate = 0;
389 for (const auto &mode : std::as_const(m_driverModes)) {
390 if (mode->size().width() >= maxSize.width() && mode->size().height() >= maxSize.height() && mode->refreshRate() >= maxSizeRefreshRate) {
391 maxSize = mode->size();
392 maxSizeRefreshRate = mode->refreshRate();
393 }
394 }
395 const uint64_t maxBandwidthEstimation = maxSize.width() * maxSize.height() * uint64_t(maxSizeRefreshRate);
396 for (const auto &size : s_commonModes) {
397 const uint64_t bandwidthEstimation = size.width() * size.height() * 60000ull;
398 if (size.width() > maxSize.width() || size.height() > maxSize.height() || bandwidthEstimation > maxBandwidthEstimation) {
399 continue;
400 }
401 const auto generatedMode = generateMode(size, 60);
402 if (std::any_of(m_driverModes.cbegin(), m_driverModes.cend(), [generatedMode](const auto &mode) {
403 return mode->size() == generatedMode->size() && mode->refreshRate() == generatedMode->refreshRate();
404 })) {
405 continue;
406 }
407 ret << generatedMode;
408 }
409 return ret;
410}
411
412std::shared_ptr<DrmConnectorMode> DrmConnector::generateMode(const QSize &size, float refreshRate)
413{
414 auto modeInfo = libxcvt_gen_mode_info(size.width(), size.height(), refreshRate, false, false);
415
416 drmModeModeInfo mode{
417 .clock = uint32_t(modeInfo->dot_clock),
418 .hdisplay = uint16_t(modeInfo->hdisplay),
419 .hsync_start = modeInfo->hsync_start,
420 .hsync_end = modeInfo->hsync_end,
421 .htotal = modeInfo->htotal,
422 .vdisplay = uint16_t(modeInfo->vdisplay),
423 .vsync_start = modeInfo->vsync_start,
424 .vsync_end = modeInfo->vsync_end,
425 .vtotal = modeInfo->vtotal,
426 .vscan = 1,
427 .vrefresh = uint32_t(modeInfo->vrefresh),
428 .flags = modeInfo->mode_flags,
429 .type = DRM_MODE_TYPE_USERDEF,
430 };
431
432 sprintf(mode.name, "%dx%d@%d", size.width(), size.height(), mode.vrefresh);
433
434 free(modeInfo);
435 return std::make_shared<DrmConnectorMode>(this, mode, OutputMode::Flag::Generated);
436}
437
438QDebug &operator<<(QDebug &s, const KWin::DrmConnector *obj)
439{
440 QDebugStateSaver saver(s);
441 if (obj) {
442
443 QString connState = QStringLiteral("Disconnected");
444 if (!obj->m_conn || obj->m_conn->connection == DRM_MODE_UNKNOWNCONNECTION) {
445 connState = QStringLiteral("Unknown Connection");
446 } else if (obj->m_conn->connection == DRM_MODE_CONNECTED) {
447 connState = QStringLiteral("Connected");
448 }
449
450 s.nospace() << "DrmConnector(id=" << obj->id() << ", gpu=" << obj->gpu() << ", name=" << obj->modelName() << ", connection=" << connState << ", countMode=" << (obj->m_conn ? obj->m_conn->count_modes : 0)
451 << ')';
452 } else {
453 s << "DrmConnector(0x0)";
454 }
455 return s;
456}
457
459{
460 switch (type) {
469 default:
470 Q_UNREACHABLE();
471 }
472}
473
489
503
505{
506 switch (rgbRange) {
513 default:
514 Q_UNREACHABLE();
515 }
516}
517}
void addProperty(const DrmProperty &prop, uint64_t value)
static std::shared_ptr< DrmBlob > create(DrmGpu *gpu, const void *data, uint32_t dataSize)
Definition drm_blob.cpp:33
static DrmContentType kwinToDrmContentType(ContentType type)
DrmEnumProperty< BroadcastRgbOptions > broadcastRGB
DrmProperty underscanHBorder
bool isNonDesktop() const
Output::SubPixel subpixel() const
DrmConnector(DrmGpu *gpu, uint32_t connectorId)
static BroadcastRgbOptions rgbRangeToBroadcastRgb(Output::RgbRange rgbRange)
DrmEnumProperty< DrmContentType > contentType
DrmEnumProperty< LinkStatus > linkStatus
static Output::RgbRange broadcastRgbToRgbRange(BroadcastRgbOptions rgbRange)
std::shared_ptr< DrmConnectorMode > findMode(const drmModeModeInfo &modeInfo) const
bool isConnected() const
QByteArray mstPath() const
DrmEnumProperty< ScalingMode > scalingMode
void disable(DrmAtomicCommit *commit) override
DrmProperty nonDesktop
QString connectorName() const
bool isCrtcSupported(DrmCrtc *crtc) const
DrmProperty vrrCapable
bool updateProperties() override
QSize physicalSize() const
DrmPipeline * pipeline() const
const Edid * edid() const
bool isInternal() const
DrmProperty hdrMetadata
static OutputTransform toKWinTransform(PanelOrientation orientation)
QString modelName() const
DrmEnumProperty< Colorspace > colorspace
QList< std::shared_ptr< DrmConnectorMode > > modes() const
DrmEnumProperty< PanelOrientation > panelOrientation
DrmProperty underscanVBorder
DrmEnumProperty< UnderscanOptions > underscan
std::shared_ptr< DrmBlob > blob()
bool operator==(const DrmConnectorMode &otherMode)
DrmConnectorMode(DrmConnector *connector, drmModeModeInfo nativeMode, Flags additionalFlags)
drmModeModeInfo * nativeMode()
std::chrono::nanoseconds vblankTime() const
int pipeIndex() const
Definition drm_crtc.cpp:61
bool hasEnum(Enum value) const
int fd() const
Definition drm_gpu.cpp:648
DrmPropertyList queryProperties() const
uint32_t id() const
DrmGpu * gpu() const
uint32_t type() const
void update(DrmPropertyList &propertyList)
drmModePropertyBlobRes * immutableBlob() const
uint64_t value() const
bool isValid() const
QSize physicalSize() const
Definition edid.cpp:180
QByteArray serialNumber() const
Definition edid.cpp:195
QString nameString() const
Definition edid.cpp:221
bool isValid() const
Definition edid.cpp:175
drmModeConnectorPtr drmModeGetConnector(int fd, uint32_t connectorId)
Definition mock_drm.cpp:622
drmModeEncoderPtr drmModeGetEncoder(int fd, uint32_t encoder_id)
Definition mock_drm.cpp:592
Session::Type type
Definition session.cpp:17
QDebug & operator<<(QDebug &s, const KWin::DrmConnector *obj)
ContentType
Definition globals.h:284