diff options
author | Louai Al-Khanji <louai.al-khanji@theqtcompany.com> | 2014-10-22 13:17:56 +0300 |
---|---|---|
committer | Louai Al-Khanji <louai.al-khanji@theqtcompany.com> | 2014-10-24 05:46:58 +0200 |
commit | 55be75c9696d38951afcf16ee8198bb86a7f2f38 (patch) | |
tree | 6ed199a9ffe92d08f9ac3509b3878e210d17b84d /src/plugins | |
parent | 70b96c525d6c9efcdd639ab050744735619b3420 (diff) |
eglfs: Add better configuration support to KMS hooks
This patch improves the configurability of the KMS hooks. Support is added
for a json config file. The file can be used to specify the device to use,
whether pbuffers are supported, whether HW cursors should be used and the
mode to use per output.
The path to the configuration file is specified through the
QT_QPA_EGLFS_KMS_CONFIG environment variable.
The output initialization code is significantly changed and now closely
matches Weston's drm compositor setup code. We now track which crtc's and
which connectors are already in use for more robust output setup.
Importantly, it is now possible to turn off undesired outputs as well as
select the mode to use on outputs. The configuration syntax is similar
to Weston's config file, and the following settings are supported per
output:
off, preferred, current, WIDTHxHEIGHT, modeline
Unless the output mode configuration matches "off", "preferred" or "current"
exactly, it is first attempted to parse the mode as WIDTHxHEIGHT, after
which it is attempted to parse the mode as a modeline. The modeline parsing
code is very closely modeled after the parsing code in Weston.
If an output mode cannot be parsed the default fallback is preferred mode.
The defaults for all settings are as follows:
device: unless specified, the first device found through QDeviceDiscovery
hwcursor: true
pbuffers: false
outputs: empty
An example configuration file might look as follows:
{
"device": "/dev/dri/card1",
"hwcursor": false,
"pbuffers": true,
"outputs": [
{
"name": "VGA1",
"mode": "off"
},
{
"name": "HDMI1",
"mode": "1024x768"
}
]
}
Change-Id: Ibe1e446c44a014ae8c7cbd8173a060ca862c2bc8
Reviewed-by: Risto Avila <risto.avila@theqtcompany.com>
Reviewed-by: Laszlo Agocs <laszlo.agocs@digia.com>
Diffstat (limited to 'src/plugins')
-rw-r--r-- | src/plugins/platforms/eglfs/qeglfshooks_kms.cpp | 471 |
1 files changed, 378 insertions, 93 deletions
diff --git a/src/plugins/platforms/eglfs/qeglfshooks_kms.cpp b/src/plugins/platforms/eglfs/qeglfshooks_kms.cpp index 4f751992bb..4c430964a3 100644 --- a/src/plugins/platforms/eglfs/qeglfshooks_kms.cpp +++ b/src/plugins/platforms/eglfs/qeglfshooks_kms.cpp @@ -43,6 +43,7 @@ #include "qeglfsintegration.h" #include "qeglfsscreen.h" +#include <QtPlatformSupport/private/qeglplatformcursor_p.h> #include <QtPlatformSupport/private/qdevicediscovery_p.h> #include <QtCore/private/qcore_unix_p.h> #include <QtCore/QScopedPointer> @@ -67,17 +68,32 @@ #define DRM_CAP_CURSOR_HEIGHT 0x9 #endif +#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0]) + QT_USE_NAMESPACE Q_LOGGING_CATEGORY(qLcEglfsKmsDebug, "qt.qpa.eglfs.kms") +class QEglFSKmsCursor; +class QEglFSKmsScreen; + +enum OutputConfiguration { + OutputConfigOff, + OutputConfigPreferred, + OutputConfigCurrent, + OutputConfigMode, + OutputConfigModeline +}; + struct QEglFSKmsOutput { - uint32_t conn_id; + QString name; + uint32_t connector_id; uint32_t crtc_id; QSizeF physical_size; - drmModeModeInfo mode; + int mode; // index of selected mode in list below bool mode_set; - drmModeCrtc *saved_crtc; + drmModeCrtcPtr saved_crtc; + QList<drmModeModeInfo> modes; }; class QEglFSKmsDevice @@ -88,9 +104,12 @@ class QEglFSKmsDevice int m_dri_fd; gbm_device *m_gbm_device; - QList<QEglFSKmsOutput> m_validOutputs; + quint32 m_crtc_allocator; + quint32 m_connector_allocator; + + int crtcForConnector(drmModeResPtr resources, drmModeConnectorPtr connector); + QEglFSKmsScreen *screenForConnector(drmModeResPtr resources, drmModeConnectorPtr connector, QPoint pos); - bool setup_kms(); static void pageFlipHandler(int fd, unsigned int sequence, unsigned int tv_sec, @@ -110,7 +129,6 @@ public: void handleDrmEvent(); }; -class QEglFSKmsCursor; class QEglFSKmsScreen : public QEglFSScreen { QEglFSKmsDevice *m_device; @@ -119,8 +137,6 @@ class QEglFSKmsScreen : public QEglFSScreen gbm_bo *m_gbm_bo_current; gbm_bo *m_gbm_bo_next; - bool m_mode_set; - QEglFSKmsOutput m_output; QPoint m_pos; QScopedPointer<QEglFSKmsCursor> m_cursor; @@ -147,6 +163,8 @@ public: Qt::ScreenOrientation nativeOrientation() const Q_DECL_OVERRIDE; Qt::ScreenOrientation orientation() const Q_DECL_OVERRIDE; + QString name() const Q_DECL_OVERRIDE; + QPlatformCursor *cursor() const Q_DECL_OVERRIDE; QEglFSKmsDevice *device() const { return m_device; } @@ -222,8 +240,18 @@ public: void presentBuffer(QPlatformSurface *surface) Q_DECL_OVERRIDE; bool supportsPBuffers() const Q_DECL_OVERRIDE; + bool hwCursor() const; + QMap<QString, QVariantMap> outputSettings() const; + private: + void loadConfig(); + QEglFSKmsDevice *m_device; + + bool m_hwCursor; + bool m_pbuffers; + QString m_devicePath; + QMap<QString, QVariantMap> m_outputSettings; }; static QEglKmsHooks kms_hooks; @@ -231,22 +259,33 @@ QEglFSHooks *platformHooks = &kms_hooks; QEglKmsHooks::QEglKmsHooks() : m_device(Q_NULLPTR) + , m_hwCursor(true) + , m_pbuffers(false) {} void QEglKmsHooks::platformInit() { - QDeviceDiscovery *d = QDeviceDiscovery::create(QDeviceDiscovery::Device_VideoMask); - QStringList devices = d->scanConnectedDevices(); - qCDebug(qLcEglfsKmsDebug) << "Found the following video devices:" << devices; - d->deleteLater(); + loadConfig(); + + if (!m_devicePath.isEmpty()) { + qCDebug(qLcEglfsKmsDebug) << "Using DRM device" << m_devicePath << "specified in config file"; + } else { - if (devices.isEmpty()) - qFatal("Could not find DRM device!"); + QDeviceDiscovery *d = QDeviceDiscovery::create(QDeviceDiscovery::Device_VideoMask); + QStringList devices = d->scanConnectedDevices(); + qCDebug(qLcEglfsKmsDebug) << "Found the following video devices:" << devices; + d->deleteLater(); - qCDebug(qLcEglfsKmsDebug) << "Using" << devices.first(); - m_device = new QEglFSKmsDevice(devices.first()); + if (devices.isEmpty()) + qFatal("Could not find DRM device!"); + + m_devicePath = devices.first(); + qCDebug(qLcEglfsKmsDebug) << "Using" << m_devicePath; + } + + m_device = new QEglFSKmsDevice(m_devicePath); if (!m_device->open()) - qFatal("DRM device required, aborting"); + qFatal("Could not open device %s - aborting!", qPrintable(m_devicePath)); } void QEglKmsHooks::platformDestroy() @@ -328,8 +367,10 @@ bool QEglKmsHooks::hasCapability(QPlatformIntegration::Capability cap) const QPlatformCursor *QEglKmsHooks::createCursor(QPlatformScreen *screen) const { - Q_UNUSED(screen); - return Q_NULLPTR; + if (m_hwCursor) + return Q_NULLPTR; + else + return new QEGLPlatformCursor(screen); } void QEglKmsHooks::waitForVSync(QPlatformSurface *surface) const @@ -350,7 +391,66 @@ void QEglKmsHooks::presentBuffer(QPlatformSurface *surface) bool QEglKmsHooks::supportsPBuffers() const { - return false; + return m_pbuffers; +} + +bool QEglKmsHooks::hwCursor() const +{ + return m_hwCursor; +} + +QMap<QString, QVariantMap> QEglKmsHooks::outputSettings() const +{ + return m_outputSettings; +} + +void QEglKmsHooks::loadConfig() +{ + static QByteArray json = qgetenv("QT_QPA_EGLFS_KMS_CONFIG"); + if (json.isEmpty()) + return; + + qCDebug(qLcEglfsKmsDebug) << "Loading KMS setup from" << json; + + QFile file(QString::fromUtf8(json)); + if (!file.open(QFile::ReadOnly)) { + qCDebug(qLcEglfsKmsDebug) << "Could not open config file" + << json << "for reading"; + return; + } + + QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); + if (!doc.isObject()) { + qCDebug(qLcEglfsKmsDebug) << "Invalid config file" << json + << "- no top-level JSON object"; + return; + } + + QJsonObject object = doc.object(); + + m_hwCursor = object.value("hwcursor").toBool(m_hwCursor); + m_pbuffers = object.value("pbuffers").toBool(m_pbuffers); + m_devicePath = object.value("device").toString(); + + QJsonArray outputs = object.value("outputs").toArray(); + for (int i = 0; i < outputs.size(); i++) { + QVariantMap outputSettings = outputs.at(i).toObject().toVariantMap(); + + if (outputSettings.contains("name")) { + QString name = outputSettings.value("name").toString(); + + if (m_outputSettings.contains(name)) { + qCDebug(qLcEglfsKmsDebug) << "Output" << name << "configured multiple times!"; + } + + m_outputSettings.insert(name, outputSettings); + } + } + + qCDebug(qLcEglfsKmsDebug) << "Configuration:\n" + << "\thwcursor:" << m_hwCursor << "\n" + << "\tpbuffers:" << m_pbuffers << "\n" + << "\toutputs:" << m_outputSettings; } QEglFSKmsCursor::QEglFSKmsCursor(QEglFSKmsScreen *screen) @@ -548,7 +648,7 @@ QEglFSKmsScreen::QEglFSKmsScreen(QEglFSKmsDevice *device, QEglFSKmsOutput output , m_gbm_bo_next(Q_NULLPTR) , m_output(output) , m_pos(position) - , m_cursor(new QEglFSKmsCursor(this)) + , m_cursor(Q_NULLPTR) { } @@ -559,9 +659,10 @@ QEglFSKmsScreen::~QEglFSKmsScreen() QRect QEglFSKmsScreen::geometry() const { + const int mode = m_output.mode; return QRect(m_pos.x(), m_pos.y(), - m_output.mode.hdisplay, - m_output.mode.vdisplay); + m_output.modes[mode].hdisplay, + m_output.modes[mode].vdisplay); } int QEglFSKmsScreen::depth() const @@ -601,15 +702,29 @@ Qt::ScreenOrientation QEglFSKmsScreen::orientation() const return Qt::PrimaryOrientation; } +QString QEglFSKmsScreen::name() const +{ + return m_output.name; +} + QPlatformCursor *QEglFSKmsScreen::cursor() const { - return m_cursor.data(); + if (kms_hooks.hwCursor()) { + if (m_cursor.isNull()) { + QEglFSKmsScreen *that = const_cast<QEglFSKmsScreen *>(this); + that->m_cursor.reset(new QEglFSKmsCursor(that)); + } + + return m_cursor.data(); + } else { + return QEglFSScreen::cursor(); + } } gbm_surface *QEglFSKmsScreen::createSurface() { if (!m_gbm_surface) { - qCDebug(qLcEglfsKmsDebug) << "Creating window for screen" << geometry(); + qCDebug(qLcEglfsKmsDebug) << "Creating window for screen" << name(); m_gbm_surface = gbm_surface_create(m_device->device(), geometry().width(), geometry().height(), @@ -663,19 +778,18 @@ void QEglFSKmsScreen::flip() FrameBuffer *fb = framebufferForBufferObject(m_gbm_bo_next); - if (!m_mode_set) { - m_output.saved_crtc = drmModeGetCrtc(m_device->fd(), m_output.crtc_id); + if (!m_output.mode_set) { int ret = drmModeSetCrtc(m_device->fd(), m_output.crtc_id, fb->fb, 0, 0, - &m_output.conn_id, 1, - &m_output.mode); - if (ret) { + &m_output.connector_id, 1, + &m_output.modes[m_output.mode]); + + if (ret) qErrnoWarning("Could not set DRM mode!"); - } else { - m_mode_set = true; - } + else + m_output.mode_set = true; } int ret = drmModePageFlip(m_device->fd(), @@ -702,98 +816,254 @@ void QEglFSKmsScreen::flipFinished() void QEglFSKmsScreen::restoreMode() { - if (m_mode_set && m_output.saved_crtc) { + if (m_output.mode_set && m_output.saved_crtc) { drmModeSetCrtc(m_device->fd(), m_output.saved_crtc->crtc_id, m_output.saved_crtc->buffer_id, 0, 0, - &m_output.conn_id, 1, + &m_output.connector_id, 1, &m_output.saved_crtc->mode); + drmModeFreeCrtc(m_output.saved_crtc); m_output.saved_crtc = Q_NULLPTR; - m_mode_set = false; + + m_output.mode_set = false; } } -static QList<drmModeConnector *> findConnectors(int dri_fd, drmModeRes *resources) +int QEglFSKmsDevice::crtcForConnector(drmModeResPtr resources, drmModeConnectorPtr connector) { - QList<drmModeConnector *> connectors; - - for (int i = 0; i < resources->count_connectors; i++) { - drmModeConnector *connector = drmModeGetConnector(dri_fd, resources->connectors[i]); - if (!connector) - continue; - - if (connector->connection == DRM_MODE_CONNECTED && connector->count_modes > 0) { - connectors.append(connector); + for (int i = 0; i < connector->count_encoders; i++) { + drmModeEncoderPtr encoder = drmModeGetEncoder(m_dri_fd, connector->encoders[i]); + if (!encoder) { + qWarning("Failed to get encoder"); continue; } - drmModeFreeConnector(connector); + quint32 possibleCrtcs = encoder->possible_crtcs; + drmModeFreeEncoder(encoder); + + for (int j = 0; j < resources->count_crtcs; j++) { + bool isPossible = possibleCrtcs & (1 << j); + bool isAvailable = !(m_crtc_allocator & 1 << resources->crtcs[j]); + + if (isPossible && isAvailable) + return j; + } } - return connectors; + return -1; } -static drmModeEncoder *findEncoder(int dri_fd, drmModeRes *resources, uint32_t encoder_id) +static const char * const connector_type_names[] = { + "None", + "VGA", + "DVI", + "DVI", + "DVI", + "Composite", + "TV", + "LVDS", + "CTV", + "DIN", + "DP", + "HDMI", + "HDMI", + "TV", + "eDP", +}; + +static QString nameForConnector(const drmModeConnectorPtr connector) { - for (int i = 0; i < resources->count_encoders; i++) { - drmModeEncoder *encoder = drmModeGetEncoder(dri_fd, resources->encoders[i]); + QString connectorName = "UNKNOWN"; - if (!encoder) - continue; + if (connector->connector_type < ARRAY_LENGTH(connector_type_names)) + connectorName = connector_type_names[connector->connector_type]; - if (encoder->encoder_id == encoder_id) - return encoder; + connectorName += QString::number(connector->connector_type_id); - drmModeFreeEncoder(encoder); - } + return connectorName; +} + +static bool parseModeline(const QString &s, drmModeModeInfoPtr mode) +{ + char hsync[16]; + char vsync[16]; + float fclock; + + mode->type = DRM_MODE_TYPE_USERDEF; + mode->hskew = 0; + mode->vscan = 0; + mode->vrefresh = 0; + mode->flags = 0; + + if (sscanf(qPrintable(s), "%f %hd %hd %hd %hd %hd %hd %hd %hd %15s %15s", + &fclock, + &mode->hdisplay, + &mode->hsync_start, + &mode->hsync_end, + &mode->htotal, + &mode->vdisplay, + &mode->vsync_start, + &mode->vsync_end, + &mode->vtotal, hsync, vsync) != 11) + return false; + + mode->clock = fclock * 1000; + + if (strcmp(hsync, "+hsync") == 0) + mode->flags |= DRM_MODE_FLAG_PHSYNC; + else if (strcmp(hsync, "-hsync") == 0) + mode->flags |= DRM_MODE_FLAG_NHSYNC; + else + return false; + + if (strcmp(vsync, "+vsync") == 0) + mode->flags |= DRM_MODE_FLAG_PVSYNC; + else if (strcmp(vsync, "-vsync") == 0) + mode->flags |= DRM_MODE_FLAG_NVSYNC; + else + return false; - return Q_NULLPTR; + return true; } -bool QEglFSKmsDevice::setup_kms() +QEglFSKmsScreen *QEglFSKmsDevice::screenForConnector(drmModeResPtr resources, drmModeConnectorPtr connector, QPoint pos) { - drmModeRes *resources = drmModeGetResources(m_dri_fd); - if (!resources) { - qWarning("drmModeGetResources failed"); - return false; + const QString connectorName = nameForConnector(connector); + + const int crtc = crtcForConnector(resources, connector); + if (crtc < 0) { + qWarning() << "No usable crtc/encoder pair for connector" << connectorName; + return Q_NULLPTR; } - QList<drmModeConnector *> connectors = findConnectors(m_dri_fd, resources); - if (connectors.isEmpty()) { - qWarning("No currently active connectors found"); - return false; + OutputConfiguration configuration; + QSize configurationSize; + drmModeModeInfo configurationModeline; + + const QString mode = kms_hooks.outputSettings().value(connectorName).value("mode", "preferred").toString().toLower(); + if (mode == "off") { + configuration = OutputConfigOff; + } else if (mode == "preferred") { + configuration = OutputConfigPreferred; + } else if (mode == "current") { + configuration = OutputConfigCurrent; + } else if (sscanf(qPrintable(mode), "%dx%d", &configurationSize.rwidth(), &configurationSize.rheight()) == 2) { + configuration = OutputConfigMode; + } else if (parseModeline(mode, &configurationModeline)) { + configuration = OutputConfigModeline; + } else { + qWarning("Invalid mode \"%s\" for output %s", qPrintable(mode), qPrintable(connectorName)); + configuration = OutputConfigPreferred; } - while (!connectors.isEmpty()) { - drmModeConnector *connector = connectors.takeFirst(); - drmModeEncoder *encoder = findEncoder(m_dri_fd, resources, connector->encoder_id); + const uint32_t crtc_id = resources->crtcs[crtc]; - if (!encoder) { - drmModeFreeConnector(connector); - continue; + if (configuration == OutputConfigOff) { + qCDebug(qLcEglfsKmsDebug) << "Turning off output" << connectorName; + drmModeSetCrtc(m_dri_fd, crtc_id, 0, 0, 0, 0, 0, Q_NULLPTR); + return Q_NULLPTR; + } + + // Get the current mode on the current crtc + drmModeModeInfo crtc_mode; + memset(&crtc_mode, 0, sizeof crtc_mode); + if (drmModeEncoderPtr encoder = drmModeGetEncoder(m_dri_fd, connector->connector_id)) { + drmModeCrtcPtr crtc = drmModeGetCrtc(m_dri_fd, encoder->crtc_id); + drmModeFreeEncoder(encoder); + + if (!crtc) + return Q_NULLPTR; + + if (crtc->mode_valid) + crtc_mode = crtc->mode; + + drmModeFreeCrtc(crtc); + } + + QList<drmModeModeInfo> modes; + qCDebug(qLcEglfsKmsDebug) << connectorName << "mode count:" << connector->count_modes; + for (int i = 0; i < connector->count_modes; i++) { + const drmModeModeInfo &mode = connector->modes[i]; + qCDebug(qLcEglfsKmsDebug) << "mode" << i << mode.hdisplay << "x" << mode.vdisplay + << "@" << mode.vrefresh << "hz"; + modes << connector->modes[i]; + } + + int preferred = -1; + int current = -1; + int configured = -1; + int best = -1; + + for (int i = modes.size() - 1; i >= 0; i--) { + const drmModeModeInfo &m = modes.at(i); + + if (configuration == OutputConfigMode && + m.hdisplay == configurationSize.width() && + m.vdisplay == configurationSize.height()) { + configured = i; } - QEglFSKmsOutput output = { - connector->connector_id, - encoder->crtc_id, - QSizeF(connector->mmWidth, connector->mmHeight), - connector->modes[0], - false, - Q_NULLPTR - }; + if (!memcmp(&crtc_mode, &m, sizeof m)) + current = i; - drmModeFreeEncoder(encoder); - drmModeFreeConnector(connector); + if (m.type & DRM_MODE_TYPE_PREFERRED) + preferred = i; - m_validOutputs.append(output); + best = i; } - drmModeFreeResources(resources); + if (configuration == OutputConfigModeline) { + modes << configurationModeline; + configured = modes.size() - 1; + } + + if (current < 0 && crtc_mode.clock != 0) { + modes << crtc_mode; + current = mode.size() - 1; + } + + if (configuration == OutputConfigCurrent) + configured = current; - qCDebug(qLcEglfsKmsDebug) << "Found" << m_validOutputs.size() << "outputs"; - return m_validOutputs.size() > 0; + int selected_mode = -1; + + if (configured >= 0) + selected_mode = configured; + else if (preferred >= 0) + selected_mode = preferred; + else if (current >= 0) + selected_mode = current; + else if (best >= 0) + selected_mode = best; + + if (selected_mode < 0) { + qWarning() << "No modes available for output" << connectorName; + return Q_NULLPTR; + } else { + int width = modes[selected_mode].hdisplay; + int height = modes[selected_mode].vdisplay; + int refresh = modes[selected_mode].vrefresh; + qCDebug(qLcEglfsKmsDebug) << "Selected mode" << selected_mode << ":" << width << "x" << height + << "@" << refresh << "hz for output" << connectorName; + } + + QEglFSKmsOutput output = { + connectorName, + connector->connector_id, + crtc_id, + QSizeF(connector->mmWidth, connector->mmHeight), + selected_mode, + false, + drmModeGetCrtc(m_dri_fd, crtc_id), + modes + }; + + m_crtc_allocator |= (1 << output.crtc_id); + m_connector_allocator |= (1 << output.connector_id); + + return new QEglFSKmsScreen(this, output, pos); } void QEglFSKmsDevice::pageFlipHandler(int fd, unsigned int sequence, unsigned int tv_sec, unsigned int tv_usec, void *user_data) @@ -811,6 +1081,8 @@ QEglFSKmsDevice::QEglFSKmsDevice(const QString &path) : m_path(path) , m_dri_fd(-1) , m_gbm_device(Q_NULLPTR) + , m_crtc_allocator(0) + , m_connector_allocator(0) { } @@ -854,17 +1126,30 @@ void QEglFSKmsDevice::close() void QEglFSKmsDevice::createScreens() { - if (!setup_kms()) { - qCDebug(qLcEglfsKmsDebug) << "KMS setup failed!"; + drmModeResPtr resources = drmModeGetResources(m_dri_fd); + if (!resources) { + qWarning("drmModeGetResources failed"); return; } + QPoint pos(0, 0); QEglFSIntegration *integration = static_cast<QEglFSIntegration *>(QGuiApplicationPrivate::platformIntegration()); - QPoint pos; - foreach (const QEglFSKmsOutput &output, m_validOutputs) { - integration->addScreen(new QEglFSKmsScreen(this, output, pos)); - pos.rx() += output.mode.hdisplay; + + for (int i = 0; i < resources->count_connectors; i++) { + drmModeConnectorPtr connector = drmModeGetConnector(m_dri_fd, resources->connectors[i]); + if (!connector) + continue; + + QEglFSKmsScreen *screen = screenForConnector(resources, connector, pos); + if (screen) { + integration->addScreen(screen); + pos.rx() += screen->geometry().width(); + } + + drmModeFreeConnector(connector); } + + drmModeFreeResources(resources); } gbm_device *QEglFSKmsDevice::device() const |