File indexing completed on 2024-11-10 04:56:27
0001 /* 0002 KWin - the KDE window manager 0003 This file is part of the KDE project. 0004 0005 SPDX-FileCopyrightText: 2016 Roman Gilg <subdiff@gmail.com> 0006 SPDX-FileCopyrightText: 2021 Xaver Hugl <xaver.hugl@gmail.com> 0007 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 #include "drm_connector.h" 0011 #include "drm_commit.h" 0012 #include "drm_crtc.h" 0013 #include "drm_gpu.h" 0014 #include "drm_logging.h" 0015 #include "drm_output.h" 0016 #include "drm_pipeline.h" 0017 #include "drm_pointer.h" 0018 0019 #include <cerrno> 0020 #include <cstring> 0021 #include <libxcvt/libxcvt.h> 0022 0023 namespace KWin 0024 { 0025 0026 static QSize resolutionForMode(const drmModeModeInfo *info) 0027 { 0028 return QSize(info->hdisplay, info->vdisplay); 0029 } 0030 0031 static quint64 refreshRateForMode(_drmModeModeInfo *m) 0032 { 0033 // Calculate higher precision (mHz) refresh rate 0034 // logic based on Weston, see compositor-drm.c 0035 quint64 refreshRate = (m->clock * 1000000LL / m->htotal + m->vtotal / 2) / m->vtotal; 0036 if (m->flags & DRM_MODE_FLAG_INTERLACE) { 0037 refreshRate *= 2; 0038 } 0039 if (m->flags & DRM_MODE_FLAG_DBLSCAN) { 0040 refreshRate /= 2; 0041 } 0042 if (m->vscan > 1) { 0043 refreshRate /= m->vscan; 0044 } 0045 return refreshRate; 0046 } 0047 0048 static OutputMode::Flags flagsForMode(const drmModeModeInfo *info, OutputMode::Flags additionalFlags) 0049 { 0050 OutputMode::Flags flags = additionalFlags; 0051 if (info->type & DRM_MODE_TYPE_PREFERRED) { 0052 flags |= OutputMode::Flag::Preferred; 0053 } 0054 return flags; 0055 } 0056 0057 DrmConnectorMode::DrmConnectorMode(DrmConnector *connector, drmModeModeInfo nativeMode, Flags additionalFlags) 0058 : OutputMode(resolutionForMode(&nativeMode), refreshRateForMode(&nativeMode), flagsForMode(&nativeMode, additionalFlags)) 0059 , m_connector(connector) 0060 , m_nativeMode(nativeMode) 0061 { 0062 } 0063 0064 std::shared_ptr<DrmBlob> DrmConnectorMode::blob() 0065 { 0066 if (!m_blob) { 0067 m_blob = DrmBlob::create(m_connector->gpu(), &m_nativeMode, sizeof(m_nativeMode)); 0068 } 0069 return m_blob; 0070 } 0071 0072 std::chrono::nanoseconds DrmConnectorMode::vblankTime() const 0073 { 0074 return std::chrono::nanoseconds(((m_nativeMode.vsync_end - m_nativeMode.vsync_start) * m_nativeMode.htotal * 1'000'000ULL) / m_nativeMode.clock); 0075 } 0076 0077 drmModeModeInfo *DrmConnectorMode::nativeMode() 0078 { 0079 return &m_nativeMode; 0080 } 0081 0082 static inline bool checkIfEqual(const drmModeModeInfo *one, const drmModeModeInfo *two) 0083 { 0084 return std::memcmp(one, two, sizeof(drmModeModeInfo)) == 0; 0085 } 0086 0087 bool DrmConnectorMode::operator==(const DrmConnectorMode &otherMode) 0088 { 0089 return checkIfEqual(&m_nativeMode, &otherMode.m_nativeMode); 0090 } 0091 0092 bool DrmConnectorMode::operator==(const drmModeModeInfo &otherMode) 0093 { 0094 return checkIfEqual(&m_nativeMode, &otherMode); 0095 } 0096 0097 DrmConnector::DrmConnector(DrmGpu *gpu, uint32_t connectorId) 0098 : DrmObject(gpu, connectorId, DRM_MODE_OBJECT_CONNECTOR) 0099 , crtcId(this, QByteArrayLiteral("CRTC_ID")) 0100 , nonDesktop(this, QByteArrayLiteral("non-desktop")) 0101 , dpms(this, QByteArrayLiteral("DPMS")) 0102 , edidProp(this, QByteArrayLiteral("EDID")) 0103 , overscan(this, QByteArrayLiteral("overscan")) 0104 , vrrCapable(this, QByteArrayLiteral("vrr_capable")) 0105 , underscan(this, QByteArrayLiteral("underscan"), { 0106 QByteArrayLiteral("off"), 0107 QByteArrayLiteral("on"), 0108 QByteArrayLiteral("auto"), 0109 }) 0110 , underscanVBorder(this, QByteArrayLiteral("underscan vborder")) 0111 , underscanHBorder(this, QByteArrayLiteral("underscan hborder")) 0112 , broadcastRGB(this, QByteArrayLiteral("Broadcast RGB"), { 0113 QByteArrayLiteral("Automatic"), 0114 QByteArrayLiteral("Full"), 0115 QByteArrayLiteral("Limited 16:235"), 0116 }) 0117 , maxBpc(this, QByteArrayLiteral("max bpc")) 0118 , linkStatus(this, QByteArrayLiteral("link-status"), { 0119 QByteArrayLiteral("Good"), 0120 QByteArrayLiteral("Bad"), 0121 }) 0122 , contentType(this, QByteArrayLiteral("content type"), { 0123 QByteArrayLiteral("No Data"), 0124 QByteArrayLiteral("Graphics"), 0125 QByteArrayLiteral("Photo"), 0126 QByteArrayLiteral("Cinema"), 0127 QByteArrayLiteral("Game"), 0128 }) 0129 , panelOrientation(this, QByteArrayLiteral("panel orientation"), { 0130 QByteArrayLiteral("Normal"), 0131 QByteArrayLiteral("Upside Down"), 0132 QByteArrayLiteral("Left Side Up"), 0133 QByteArrayLiteral("Right Side Up"), 0134 }) 0135 , hdrMetadata(this, QByteArrayLiteral("HDR_OUTPUT_METADATA")) 0136 , scalingMode(this, QByteArrayLiteral("scaling mode"), { 0137 QByteArrayLiteral("None"), 0138 QByteArrayLiteral("Full"), 0139 QByteArrayLiteral("Center"), 0140 QByteArrayLiteral("Full aspect"), 0141 }) 0142 , colorspace(this, QByteArrayLiteral("Colorspace"), { 0143 QByteArrayLiteral("Default"), 0144 QByteArrayLiteral("BT709_YCC"), 0145 QByteArrayLiteral("opRGB"), 0146 QByteArrayLiteral("BT2020_RGB"), 0147 QByteArrayLiteral("BT2020_YCC"), 0148 }) 0149 , path(this, QByteArrayLiteral("PATH")) 0150 , m_conn(drmModeGetConnector(gpu->fd(), connectorId)) 0151 , m_pipeline(m_conn ? std::make_unique<DrmPipeline>(this) : nullptr) 0152 { 0153 if (m_conn) { 0154 for (int i = 0; i < m_conn->count_encoders; ++i) { 0155 DrmUniquePtr<drmModeEncoder> enc(drmModeGetEncoder(gpu->fd(), m_conn->encoders[i])); 0156 if (!enc) { 0157 qCWarning(KWIN_DRM) << "failed to get encoder" << m_conn->encoders[i]; 0158 continue; 0159 } 0160 m_possibleCrtcs |= enc->possible_crtcs; 0161 } 0162 } else { 0163 qCWarning(KWIN_DRM) << "drmModeGetConnector failed!" << strerror(errno); 0164 } 0165 } 0166 0167 bool DrmConnector::isConnected() const 0168 { 0169 return !m_driverModes.empty() && m_conn && m_conn->connection == DRM_MODE_CONNECTED; 0170 } 0171 0172 QString DrmConnector::connectorName() const 0173 { 0174 const char *connectorName = drmModeGetConnectorTypeName(m_conn->connector_type); 0175 if (!connectorName) { 0176 connectorName = "Unknown"; 0177 } 0178 return QStringLiteral("%1-%2").arg(connectorName).arg(m_conn->connector_type_id); 0179 } 0180 0181 QString DrmConnector::modelName() const 0182 { 0183 if (m_edid.serialNumber().isEmpty()) { 0184 return connectorName() + QLatin1Char('-') + m_edid.nameString(); 0185 } else { 0186 return m_edid.nameString(); 0187 } 0188 } 0189 0190 bool DrmConnector::isInternal() const 0191 { 0192 return m_conn->connector_type == DRM_MODE_CONNECTOR_LVDS || m_conn->connector_type == DRM_MODE_CONNECTOR_eDP 0193 || m_conn->connector_type == DRM_MODE_CONNECTOR_DSI; 0194 } 0195 0196 QSize DrmConnector::physicalSize() const 0197 { 0198 return m_physicalSize; 0199 } 0200 0201 QByteArray DrmConnector::mstPath() const 0202 { 0203 return m_mstPath; 0204 } 0205 0206 QList<std::shared_ptr<DrmConnectorMode>> DrmConnector::modes() const 0207 { 0208 return m_modes; 0209 } 0210 0211 std::shared_ptr<DrmConnectorMode> DrmConnector::findMode(const drmModeModeInfo &modeInfo) const 0212 { 0213 const auto it = std::find_if(m_modes.constBegin(), m_modes.constEnd(), [&modeInfo](const auto &mode) { 0214 return checkIfEqual(mode->nativeMode(), &modeInfo); 0215 }); 0216 return it == m_modes.constEnd() ? nullptr : *it; 0217 } 0218 0219 Output::SubPixel DrmConnector::subpixel() const 0220 { 0221 switch (m_conn->subpixel) { 0222 case DRM_MODE_SUBPIXEL_UNKNOWN: 0223 return Output::SubPixel::Unknown; 0224 case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB: 0225 return Output::SubPixel::Horizontal_RGB; 0226 case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR: 0227 return Output::SubPixel::Horizontal_BGR; 0228 case DRM_MODE_SUBPIXEL_VERTICAL_RGB: 0229 return Output::SubPixel::Vertical_RGB; 0230 case DRM_MODE_SUBPIXEL_VERTICAL_BGR: 0231 return Output::SubPixel::Vertical_BGR; 0232 case DRM_MODE_SUBPIXEL_NONE: 0233 return Output::SubPixel::None; 0234 default: 0235 return Output::SubPixel::Unknown; 0236 } 0237 } 0238 0239 bool DrmConnector::updateProperties() 0240 { 0241 if (auto connector = drmModeGetConnector(gpu()->fd(), id())) { 0242 m_conn.reset(connector); 0243 } else if (!m_conn) { 0244 return false; 0245 } 0246 DrmPropertyList props = queryProperties(); 0247 crtcId.update(props); 0248 nonDesktop.update(props); 0249 dpms.update(props); 0250 edidProp.update(props); 0251 overscan.update(props); 0252 vrrCapable.update(props); 0253 underscan.update(props); 0254 underscanVBorder.update(props); 0255 underscanHBorder.update(props); 0256 broadcastRGB.update(props); 0257 maxBpc.update(props); 0258 linkStatus.update(props); 0259 contentType.update(props); 0260 panelOrientation.update(props); 0261 hdrMetadata.update(props); 0262 scalingMode.update(props); 0263 colorspace.update(props); 0264 path.update(props); 0265 0266 if (gpu()->atomicModeSetting() && !crtcId.isValid()) { 0267 return false; 0268 } 0269 0270 // parse edid 0271 if (edidProp.immutableBlob()) { 0272 m_edid = Edid(edidProp.immutableBlob()->data, edidProp.immutableBlob()->length); 0273 if (!m_edid.isValid()) { 0274 qCWarning(KWIN_DRM) << "Couldn't parse EDID for connector" << this; 0275 } 0276 } else if (m_conn->connection == DRM_MODE_CONNECTED) { 0277 qCDebug(KWIN_DRM) << "Could not find edid for connector" << this; 0278 } 0279 0280 // check the physical size 0281 if (m_edid.physicalSize().isEmpty()) { 0282 m_physicalSize = QSize(m_conn->mmWidth, m_conn->mmHeight); 0283 } else { 0284 m_physicalSize = m_edid.physicalSize(); 0285 } 0286 0287 // update modes 0288 bool equal = m_conn->count_modes == m_driverModes.count(); 0289 for (int i = 0; equal && i < m_conn->count_modes; i++) { 0290 equal &= checkIfEqual(m_driverModes[i]->nativeMode(), &m_conn->modes[i]); 0291 } 0292 if (!equal && m_conn->count_modes > 0) { 0293 // reload modes 0294 m_driverModes.clear(); 0295 for (int i = 0; i < m_conn->count_modes; i++) { 0296 m_driverModes.append(std::make_shared<DrmConnectorMode>(this, m_conn->modes[i], OutputMode::Flags())); 0297 } 0298 m_modes.clear(); 0299 m_modes.append(m_driverModes); 0300 if (scalingMode.isValid() && scalingMode.hasEnum(ScalingMode::Full_Aspect)) { 0301 m_modes.append(generateCommonModes()); 0302 } 0303 if (m_pipeline->mode()) { 0304 if (const auto mode = findMode(*m_pipeline->mode()->nativeMode())) { 0305 m_pipeline->setMode(mode); 0306 } else { 0307 m_pipeline->setMode(m_modes.constFirst()); 0308 } 0309 } else { 0310 m_pipeline->setMode(m_modes.constFirst()); 0311 } 0312 m_pipeline->applyPendingChanges(); 0313 if (m_pipeline->output()) { 0314 m_pipeline->output()->updateModes(); 0315 } 0316 } 0317 0318 m_mstPath.clear(); 0319 if (auto blob = path.immutableBlob()) { 0320 QByteArray value = QByteArray(static_cast<const char *>(blob->data), blob->length); 0321 if (value.startsWith("mst:")) { 0322 // for backwards compatibility reasons the string also contains the drm connector id 0323 // remove that to get a more stable identifier 0324 const ssize_t firstHyphen = value.indexOf('-'); 0325 if (firstHyphen > 0) { 0326 m_mstPath = value.mid(firstHyphen); 0327 } else { 0328 qCWarning(KWIN_DRM) << "Unexpected format in path property:" << value; 0329 } 0330 } else { 0331 qCWarning(KWIN_DRM) << "Unknown path type detected:" << value; 0332 } 0333 } 0334 0335 return true; 0336 } 0337 0338 bool DrmConnector::isCrtcSupported(DrmCrtc *crtc) const 0339 { 0340 return (m_possibleCrtcs & (1 << crtc->pipeIndex())); 0341 } 0342 0343 bool DrmConnector::isNonDesktop() const 0344 { 0345 return nonDesktop.isValid() && nonDesktop.value() == 1; 0346 } 0347 0348 const Edid *DrmConnector::edid() const 0349 { 0350 return &m_edid; 0351 } 0352 0353 DrmPipeline *DrmConnector::pipeline() const 0354 { 0355 return m_pipeline.get(); 0356 } 0357 0358 void DrmConnector::disable(DrmAtomicCommit *commit) 0359 { 0360 commit->addProperty(crtcId, 0); 0361 } 0362 0363 static const QList<QSize> s_commonModes = { 0364 /* 4:3 (1.33) */ 0365 QSize(1600, 1200), 0366 QSize(1280, 1024), /* 5:4 (1.25) */ 0367 QSize(1024, 768), 0368 /* 16:10 (1.6) */ 0369 QSize(2560, 1600), 0370 QSize(1920, 1200), 0371 QSize(1280, 800), 0372 /* 16:9 (1.77) */ 0373 QSize(5120, 2880), 0374 QSize(3840, 2160), 0375 QSize(3200, 1800), 0376 QSize(2880, 1620), 0377 QSize(2560, 1440), 0378 QSize(1920, 1080), 0379 QSize(1600, 900), 0380 QSize(1368, 768), 0381 QSize(1280, 720), 0382 }; 0383 0384 QList<std::shared_ptr<DrmConnectorMode>> DrmConnector::generateCommonModes() 0385 { 0386 QList<std::shared_ptr<DrmConnectorMode>> ret; 0387 QSize maxSize; 0388 uint32_t maxSizeRefreshRate = 0; 0389 for (const auto &mode : std::as_const(m_driverModes)) { 0390 if (mode->size().width() >= maxSize.width() && mode->size().height() >= maxSize.height() && mode->refreshRate() >= maxSizeRefreshRate) { 0391 maxSize = mode->size(); 0392 maxSizeRefreshRate = mode->refreshRate(); 0393 } 0394 } 0395 const uint64_t maxBandwidthEstimation = maxSize.width() * maxSize.height() * uint64_t(maxSizeRefreshRate); 0396 for (const auto &size : s_commonModes) { 0397 const uint64_t bandwidthEstimation = size.width() * size.height() * 60000ull; 0398 if (size.width() > maxSize.width() || size.height() > maxSize.height() || bandwidthEstimation > maxBandwidthEstimation) { 0399 continue; 0400 } 0401 const auto generatedMode = generateMode(size, 60); 0402 if (std::any_of(m_driverModes.cbegin(), m_driverModes.cend(), [generatedMode](const auto &mode) { 0403 return mode->size() == generatedMode->size() && mode->refreshRate() == generatedMode->refreshRate(); 0404 })) { 0405 continue; 0406 } 0407 ret << generatedMode; 0408 } 0409 return ret; 0410 } 0411 0412 std::shared_ptr<DrmConnectorMode> DrmConnector::generateMode(const QSize &size, float refreshRate) 0413 { 0414 auto modeInfo = libxcvt_gen_mode_info(size.width(), size.height(), refreshRate, false, false); 0415 0416 drmModeModeInfo mode{ 0417 .clock = uint32_t(modeInfo->dot_clock), 0418 .hdisplay = uint16_t(modeInfo->hdisplay), 0419 .hsync_start = modeInfo->hsync_start, 0420 .hsync_end = modeInfo->hsync_end, 0421 .htotal = modeInfo->htotal, 0422 .vdisplay = uint16_t(modeInfo->vdisplay), 0423 .vsync_start = modeInfo->vsync_start, 0424 .vsync_end = modeInfo->vsync_end, 0425 .vtotal = modeInfo->vtotal, 0426 .vscan = 1, 0427 .vrefresh = uint32_t(modeInfo->vrefresh), 0428 .flags = modeInfo->mode_flags, 0429 .type = DRM_MODE_TYPE_USERDEF, 0430 }; 0431 0432 sprintf(mode.name, "%dx%d@%d", size.width(), size.height(), mode.vrefresh); 0433 0434 free(modeInfo); 0435 return std::make_shared<DrmConnectorMode>(this, mode, OutputMode::Flag::Generated); 0436 } 0437 0438 QDebug &operator<<(QDebug &s, const KWin::DrmConnector *obj) 0439 { 0440 QDebugStateSaver saver(s); 0441 if (obj) { 0442 0443 QString connState = QStringLiteral("Disconnected"); 0444 if (!obj->m_conn || obj->m_conn->connection == DRM_MODE_UNKNOWNCONNECTION) { 0445 connState = QStringLiteral("Unknown Connection"); 0446 } else if (obj->m_conn->connection == DRM_MODE_CONNECTED) { 0447 connState = QStringLiteral("Connected"); 0448 } 0449 0450 s.nospace() << "DrmConnector(id=" << obj->id() << ", gpu=" << obj->gpu() << ", name=" << obj->modelName() << ", connection=" << connState << ", countMode=" << (obj->m_conn ? obj->m_conn->count_modes : 0) 0451 << ')'; 0452 } else { 0453 s << "DrmConnector(0x0)"; 0454 } 0455 return s; 0456 } 0457 0458 DrmConnector::DrmContentType DrmConnector::kwinToDrmContentType(ContentType type) 0459 { 0460 switch (type) { 0461 case ContentType::None: 0462 return DrmContentType::Graphics; 0463 case ContentType::Photo: 0464 return DrmContentType::Photo; 0465 case ContentType::Video: 0466 return DrmContentType::Cinema; 0467 case ContentType::Game: 0468 return DrmContentType::Game; 0469 default: 0470 Q_UNREACHABLE(); 0471 } 0472 } 0473 0474 OutputTransform DrmConnector::toKWinTransform(PanelOrientation orientation) 0475 { 0476 switch (orientation) { 0477 case PanelOrientation::Normal: 0478 return KWin::OutputTransform::Normal; 0479 case PanelOrientation::RightUp: 0480 return KWin::OutputTransform::Rotate270; 0481 case PanelOrientation::LeftUp: 0482 return KWin::OutputTransform::Rotate90; 0483 case PanelOrientation::UpsideDown: 0484 return KWin::OutputTransform::Rotate180; 0485 default: 0486 Q_UNREACHABLE(); 0487 } 0488 } 0489 0490 DrmConnector::BroadcastRgbOptions DrmConnector::rgbRangeToBroadcastRgb(Output::RgbRange rgbRange) 0491 { 0492 switch (rgbRange) { 0493 case Output::RgbRange::Automatic: 0494 return BroadcastRgbOptions::Automatic; 0495 case Output::RgbRange::Full: 0496 return BroadcastRgbOptions::Full; 0497 case Output::RgbRange::Limited: 0498 return BroadcastRgbOptions::Limited; 0499 default: 0500 Q_UNREACHABLE(); 0501 } 0502 } 0503 0504 Output::RgbRange DrmConnector::broadcastRgbToRgbRange(BroadcastRgbOptions rgbRange) 0505 { 0506 switch (rgbRange) { 0507 case BroadcastRgbOptions::Automatic: 0508 return Output::RgbRange::Automatic; 0509 case BroadcastRgbOptions::Full: 0510 return Output::RgbRange::Full; 0511 case BroadcastRgbOptions::Limited: 0512 return Output::RgbRange::Limited; 0513 default: 0514 Q_UNREACHABLE(); 0515 } 0516 } 0517 }