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 }