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