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 }