You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

509 lines
18 KiB

/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2016 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2021 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_connector.h"
#include "drm_commit.h"
#include "drm_crtc.h"
#include "drm_gpu.h"
#include "drm_logging.h"
#include "drm_output.h"
#include "drm_pipeline.h"
#include "drm_pointer.h"
#include <cerrno>
#include <cstring>
#include <libxcvt/libxcvt.h>
namespace KWin
{
static QSize resolutionForMode(const drmModeModeInfo *info)
{
return QSize(info->hdisplay, info->vdisplay);
}
static quint64 refreshRateForMode(_drmModeModeInfo *m)
{
// Calculate higher precision (mHz) refresh rate
// logic based on Weston, see compositor-drm.c
quint64 refreshRate = (m->clock * 1000000LL / m->htotal + m->vtotal / 2) / m->vtotal;
if (m->flags & DRM_MODE_FLAG_INTERLACE) {
refreshRate *= 2;
}
if (m->flags & DRM_MODE_FLAG_DBLSCAN) {
refreshRate /= 2;
}
if (m->vscan > 1) {
refreshRate /= m->vscan;
}
return refreshRate;
}
static OutputMode::Flags flagsForMode(const drmModeModeInfo *info, OutputMode::Flags additionalFlags)
{
OutputMode::Flags flags = additionalFlags;
if (info->type & DRM_MODE_TYPE_PREFERRED) {
flags |= OutputMode::Flag::Preferred;
}
return flags;
}
DrmConnectorMode::DrmConnectorMode(DrmConnector *connector, drmModeModeInfo nativeMode, Flags additionalFlags)
: OutputMode(resolutionForMode(&nativeMode), refreshRateForMode(&nativeMode), flagsForMode(&nativeMode, additionalFlags))
, m_connector(connector)
, m_nativeMode(nativeMode)
{
}
std::shared_ptr<DrmBlob> DrmConnectorMode::blob()
{
if (!m_blob) {
m_blob = DrmBlob::create(m_connector->gpu(), &m_nativeMode, sizeof(m_nativeMode));
}
return m_blob;
}
std::chrono::nanoseconds DrmConnectorMode::vblankTime() const
{
return std::chrono::nanoseconds(((m_nativeMode.vtotal - m_nativeMode.vdisplay) * m_nativeMode.htotal * 1'000'000ULL) / m_nativeMode.clock);
}
drmModeModeInfo *DrmConnectorMode::nativeMode()
{
return &m_nativeMode;
}
static inline bool checkIfEqual(const drmModeModeInfo *one, const drmModeModeInfo *two)
{
return std::memcmp(one, two, sizeof(drmModeModeInfo)) == 0;
}
bool DrmConnectorMode::operator==(const DrmConnectorMode &otherMode)
{
return checkIfEqual(&m_nativeMode, &otherMode.m_nativeMode);
}
bool DrmConnectorMode::operator==(const drmModeModeInfo &otherMode)
{
return checkIfEqual(&m_nativeMode, &otherMode);
}
DrmConnector::DrmConnector(DrmGpu *gpu, uint32_t connectorId)
: DrmObject(gpu, connectorId, DRM_MODE_OBJECT_CONNECTOR)
, crtcId(this, QByteArrayLiteral("CRTC_ID"))
, nonDesktop(this, QByteArrayLiteral("non-desktop"))
, dpms(this, QByteArrayLiteral("DPMS"))
, edidProp(this, QByteArrayLiteral("EDID"))
, overscan(this, QByteArrayLiteral("overscan"))
, vrrCapable(this, QByteArrayLiteral("vrr_capable"))
, underscan(this, QByteArrayLiteral("underscan"), {
QByteArrayLiteral("off"),
QByteArrayLiteral("on"),
QByteArrayLiteral("auto"),
})
, underscanVBorder(this, QByteArrayLiteral("underscan vborder"))
, underscanHBorder(this, QByteArrayLiteral("underscan hborder"))
, broadcastRGB(this, QByteArrayLiteral("Broadcast RGB"), {
QByteArrayLiteral("Automatic"),
QByteArrayLiteral("Full"),
QByteArrayLiteral("Limited 16:235"),
})
, maxBpc(this, QByteArrayLiteral("max bpc"))
, linkStatus(this, QByteArrayLiteral("link-status"), {
QByteArrayLiteral("Good"),
QByteArrayLiteral("Bad"),
})
, contentType(this, QByteArrayLiteral("content type"), {
QByteArrayLiteral("No Data"),
QByteArrayLiteral("Graphics"),
QByteArrayLiteral("Photo"),
QByteArrayLiteral("Cinema"),
QByteArrayLiteral("Game"),
})
, panelOrientation(this, QByteArrayLiteral("panel orientation"), {
QByteArrayLiteral("Normal"),
QByteArrayLiteral("Upside Down"),
QByteArrayLiteral("Left Side Up"),
QByteArrayLiteral("Right Side Up"),
})
, hdrMetadata(this, QByteArrayLiteral("HDR_OUTPUT_METADATA"))
, scalingMode(this, QByteArrayLiteral("scaling mode"), {
QByteArrayLiteral("None"),
QByteArrayLiteral("Full"),
QByteArrayLiteral("Center"),
QByteArrayLiteral("Full aspect"),
})
, colorspace(this, QByteArrayLiteral("Colorspace"), {
QByteArrayLiteral("Default"),
QByteArrayLiteral("BT709_YCC"),
QByteArrayLiteral("opRGB"),
QByteArrayLiteral("BT2020_RGB"),
QByteArrayLiteral("BT2020_YCC"),
})
, path(this, QByteArrayLiteral("PATH"))
, m_conn(drmModeGetConnector(gpu->fd(), connectorId))
, m_pipeline(m_conn ? std::make_unique<DrmPipeline>(this) : nullptr)
{
if (m_conn) {
for (int i = 0; i < m_conn->count_encoders; ++i) {
DrmUniquePtr<drmModeEncoder> enc(drmModeGetEncoder(gpu->fd(), m_conn->encoders[i]));
if (!enc) {
qCWarning(KWIN_DRM) << "failed to get encoder" << m_conn->encoders[i];
continue;
}
m_possibleCrtcs |= enc->possible_crtcs;
}
} else {
qCWarning(KWIN_DRM) << "drmModeGetConnector failed!" << strerror(errno);
}
}
bool DrmConnector::isConnected() const
{
return !m_driverModes.empty() && m_conn && m_conn->connection == DRM_MODE_CONNECTED;
}
QString DrmConnector::connectorName() const
{
const char *connectorName = drmModeGetConnectorTypeName(m_conn->connector_type);
if (!connectorName) {
connectorName = "Unknown";
}
return QStringLiteral("%1-%2").arg(connectorName).arg(m_conn->connector_type_id);
}
QString DrmConnector::modelName() const
{
if (m_edid.serialNumber().isEmpty()) {
return connectorName() + QLatin1Char('-') + m_edid.nameString();
} else {
return m_edid.nameString();
}
}
bool DrmConnector::isInternal() const
{
return m_conn->connector_type == DRM_MODE_CONNECTOR_LVDS || m_conn->connector_type == DRM_MODE_CONNECTOR_eDP
|| m_conn->connector_type == DRM_MODE_CONNECTOR_DSI;
}
QSize DrmConnector::physicalSize() const
{
return m_physicalSize;
}
QByteArray DrmConnector::mstPath() const
{
return m_mstPath;
}
QList<std::shared_ptr<DrmConnectorMode>> DrmConnector::modes() const
{
return m_modes;
}
std::shared_ptr<DrmConnectorMode> DrmConnector::findMode(const drmModeModeInfo &modeInfo) const
{
const auto it = std::ranges::find_if(m_modes, [&modeInfo](const auto &mode) {
return checkIfEqual(mode->nativeMode(), &modeInfo);
});
return it == m_modes.constEnd() ? nullptr : *it;
}
Output::SubPixel DrmConnector::subpixel() const
{
switch (m_conn->subpixel) {
case DRM_MODE_SUBPIXEL_UNKNOWN:
return Output::SubPixel::Unknown;
case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB:
return Output::SubPixel::Horizontal_RGB;
case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR:
return Output::SubPixel::Horizontal_BGR;
case DRM_MODE_SUBPIXEL_VERTICAL_RGB:
return Output::SubPixel::Vertical_RGB;
case DRM_MODE_SUBPIXEL_VERTICAL_BGR:
return Output::SubPixel::Vertical_BGR;
case DRM_MODE_SUBPIXEL_NONE:
return Output::SubPixel::None;
default:
return Output::SubPixel::Unknown;
}
}
bool DrmConnector::updateProperties()
{
if (auto connector = drmModeGetConnector(gpu()->fd(), id())) {
m_conn.reset(connector);
} else if (!m_conn) {
return false;
}
DrmPropertyList props = queryProperties();
crtcId.update(props);
nonDesktop.update(props);
dpms.update(props);
edidProp.update(props);
overscan.update(props);
vrrCapable.update(props);
underscan.update(props);
underscanVBorder.update(props);
underscanHBorder.update(props);
broadcastRGB.update(props);
maxBpc.update(props);
linkStatus.update(props);
contentType.update(props);
panelOrientation.update(props);
hdrMetadata.update(props);
scalingMode.update(props);
colorspace.update(props);
path.update(props);
if (gpu()->atomicModeSetting() && !crtcId.isValid()) {
qCWarning(KWIN_DRM) << "Failed to update the basic connector properties (CRTC_ID)";
return false;
}
// parse edid
if (edidProp.immutableBlob()) {
m_edid = Edid(edidProp.immutableBlob()->data, edidProp.immutableBlob()->length);
if (!m_edid.isValid()) {
qCWarning(KWIN_DRM) << "Couldn't parse EDID for connector" << this;
}
} else {
m_edid = Edid{};
if (m_conn->connection == DRM_MODE_CONNECTED) {
qCDebug(KWIN_DRM) << "Could not find edid for connector" << this;
}
}
// check the physical size
if (m_edid.physicalSize().isEmpty()) {
m_physicalSize = QSize(m_conn->mmWidth, m_conn->mmHeight);
} else {
m_physicalSize = m_edid.physicalSize();
}
// update modes
bool equal = m_conn->count_modes == m_driverModes.count();
for (int i = 0; equal && i < m_conn->count_modes; i++) {
equal &= checkIfEqual(m_driverModes[i]->nativeMode(), &m_conn->modes[i]);
}
if (!equal && m_conn->count_modes > 0) {
// reload modes
m_driverModes.clear();
for (int i = 0; i < m_conn->count_modes; i++) {
m_driverModes.append(std::make_shared<DrmConnectorMode>(this, m_conn->modes[i], OutputMode::Flags()));
}
m_modes.clear();
m_modes.append(m_driverModes);
if (scalingMode.isValid() && scalingMode.hasEnum(ScalingMode::Full_Aspect)) {
m_modes.append(generateCommonModes());
}
}
m_mstPath.clear();
if (auto blob = path.immutableBlob()) {
QByteArray value = QByteArray(static_cast<const char *>(blob->data), blob->length);
if (value.startsWith("mst:")) {
// for backwards compatibility reasons the string also contains the drm connector id
// remove that to get a more stable identifier
const ssize_t firstHyphen = value.indexOf('-');
if (firstHyphen > 0) {
m_mstPath = value.mid(firstHyphen);
} else {
qCWarning(KWIN_DRM) << "Unexpected format in path property:" << value;
}
} else {
qCWarning(KWIN_DRM) << "Unknown path type detected:" << value;
}
}
return true;
}
bool DrmConnector::isCrtcSupported(DrmCrtc *crtc) const
{
return (m_possibleCrtcs & (1 << crtc->pipeIndex()));
}
bool DrmConnector::isNonDesktop() const
{
return nonDesktop.isValid() && nonDesktop.value() == 1;
}
const Edid *DrmConnector::edid() const
{
return &m_edid;
}
DrmPipeline *DrmConnector::pipeline() const
{
return m_pipeline.get();
}
void DrmConnector::disable(DrmAtomicCommit *commit)
{
commit->addProperty(crtcId, 0);
}
static const QList<QSize> s_commonModes = {
/* 4:3 (1.33) */
QSize(1600, 1200),
QSize(1280, 1024), /* 5:4 (1.25) */
QSize(1024, 768),
/* 16:10 (1.6) */
QSize(2560, 1600),
QSize(1920, 1200),
QSize(1280, 800),
/* 16:9 (1.77) */
QSize(5120, 2880),
QSize(3840, 2160),
QSize(3200, 1800),
QSize(2880, 1620),
QSize(2560, 1440),
QSize(1920, 1080),
QSize(1600, 900),
QSize(1368, 768),
QSize(1280, 720),
};
QList<std::shared_ptr<DrmConnectorMode>> DrmConnector::generateCommonModes()
{
QList<std::shared_ptr<DrmConnectorMode>> ret;
QSize maxSize;
uint32_t maxSizeRefreshRate = 0;
for (const auto &mode : std::as_const(m_driverModes)) {
if (mode->size().width() >= maxSize.width() && mode->size().height() >= maxSize.height() && mode->refreshRate() >= maxSizeRefreshRate) {
maxSize = mode->size();
maxSizeRefreshRate = mode->refreshRate();
}
}
const uint64_t maxBandwidthEstimation = maxSize.width() * maxSize.height() * uint64_t(maxSizeRefreshRate);
for (const auto &size : s_commonModes) {
const uint64_t bandwidthEstimation = size.width() * size.height() * 60000ull;
if (size.width() > maxSize.width() || size.height() > maxSize.height() || bandwidthEstimation > maxBandwidthEstimation) {
continue;
}
const auto generatedMode = generateMode(size, 60);
const bool alreadyExists = std::ranges::any_of(m_driverModes, [generatedMode](const auto &mode) {
return mode->size() == generatedMode->size() && mode->refreshRate() == generatedMode->refreshRate();
});
if (alreadyExists) {
continue;
}
ret << generatedMode;
}
return ret;
}
std::shared_ptr<DrmConnectorMode> DrmConnector::generateMode(const QSize &size, float refreshRate)
{
auto modeInfo = libxcvt_gen_mode_info(size.width(), size.height(), refreshRate, false, false);
drmModeModeInfo mode{
.clock = uint32_t(modeInfo->dot_clock),
.hdisplay = uint16_t(modeInfo->hdisplay),
.hsync_start = modeInfo->hsync_start,
.hsync_end = modeInfo->hsync_end,
.htotal = modeInfo->htotal,
.vdisplay = uint16_t(modeInfo->vdisplay),
.vsync_start = modeInfo->vsync_start,
.vsync_end = modeInfo->vsync_end,
.vtotal = modeInfo->vtotal,
.vscan = 1,
.vrefresh = uint32_t(modeInfo->vrefresh),
.flags = modeInfo->mode_flags,
.type = DRM_MODE_TYPE_USERDEF,
};
sprintf(mode.name, "%dx%d@%d", size.width(), size.height(), mode.vrefresh);
free(modeInfo);
return std::make_shared<DrmConnectorMode>(this, mode, OutputMode::Flag::Generated);
}
QDebug &operator<<(QDebug &s, const KWin::DrmConnector *obj)
{
QDebugStateSaver saver(s);
if (obj) {
QString connState = QStringLiteral("Disconnected");
if (!obj->m_conn || obj->m_conn->connection == DRM_MODE_UNKNOWNCONNECTION) {
connState = QStringLiteral("Unknown Connection");
} else if (obj->m_conn->connection == DRM_MODE_CONNECTED) {
connState = QStringLiteral("Connected");
}
s.nospace() << "DrmConnector(id=" << obj->id() << ", gpu=" << obj->gpu() << ", name=" << obj->modelName() << ", connection=" << connState << ", countMode=" << (obj->m_conn ? obj->m_conn->count_modes : 0)
<< ')';
} else {
s << "DrmConnector(0x0)";
}
return s;
}
DrmConnector::DrmContentType DrmConnector::kwinToDrmContentType(ContentType type)
{
switch (type) {
case ContentType::None:
return DrmContentType::Graphics;
case ContentType::Photo:
return DrmContentType::Photo;
case ContentType::Video:
return DrmContentType::Cinema;
case ContentType::Game:
return DrmContentType::Game;
default:
Q_UNREACHABLE();
}
}
OutputTransform DrmConnector::toKWinTransform(PanelOrientation orientation)
{
switch (orientation) {
case PanelOrientation::Normal:
return KWin::OutputTransform::Normal;
case PanelOrientation::RightUp:
return KWin::OutputTransform::Rotate270;
case PanelOrientation::LeftUp:
return KWin::OutputTransform::Rotate90;
case PanelOrientation::UpsideDown:
return KWin::OutputTransform::Rotate180;
default:
Q_UNREACHABLE();
}
}
DrmConnector::BroadcastRgbOptions DrmConnector::rgbRangeToBroadcastRgb(Output::RgbRange rgbRange)
{
switch (rgbRange) {
case Output::RgbRange::Automatic:
return BroadcastRgbOptions::Automatic;
case Output::RgbRange::Full:
return BroadcastRgbOptions::Full;
case Output::RgbRange::Limited:
return BroadcastRgbOptions::Limited;
default:
Q_UNREACHABLE();
}
}
Output::RgbRange DrmConnector::broadcastRgbToRgbRange(BroadcastRgbOptions rgbRange)
{
switch (rgbRange) {
case BroadcastRgbOptions::Automatic:
return Output::RgbRange::Automatic;
case BroadcastRgbOptions::Full:
return Output::RgbRange::Full;
case BroadcastRgbOptions::Limited:
return Output::RgbRange::Limited;
default:
Q_UNREACHABLE();
}
}
}