drm_gpu.cpp 14.1 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
    KWin - the KDE window manager
    This file is part of the KDE project.

    SPDX-FileCopyrightText: 2020 Xaver Hugl <xaver.hugl@gmail.com>

    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "drm_gpu.h"

#include "drm_backend.h"
#include "drm_output.h"
#include "drm_object_connector.h"
#include "drm_object_crtc.h"
#include "abstract_egl_backend.h"
#include "logging.h"
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
18
#include "session.h"
Xaver Hugl's avatar
Xaver Hugl committed
19
#include "renderloop_p.h"
20
21
22
23
24
25
26
27

#if HAVE_GBM
#include "egl_gbm_backend.h"
#include <gbm.h>
#include "gbm_dmabuf.h"
#endif
// system
#include <algorithm>
28
#include <errno.h>
29
#include <poll.h>
30
31
32
33
34
35
36
37
38
#include <unistd.h>
// drm
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <libdrm/drm_mode.h>

namespace KWin
{

39
DrmGpu::DrmGpu(DrmBackend *backend, const QString &devNode, int fd, dev_t deviceId)
40
41
42
    : m_backend(backend)
    , m_devNode(devNode)
    , m_fd(fd)
43
    , m_deviceId(deviceId)
44
45
    , m_atomicModeSetting(false)
    , m_gbmDevice(nullptr)
46
{
47
48
49
50
51
52
53
54
55
56
57
58
59
    uint64_t capability = 0;

    if (drmGetCap(fd, DRM_CAP_CURSOR_WIDTH, &capability) == 0) {
        m_cursorSize.setWidth(capability);
    } else {
        m_cursorSize.setWidth(64);
    }

    if (drmGetCap(fd, DRM_CAP_CURSOR_HEIGHT, &capability) == 0) {
        m_cursorSize.setHeight(capability);
    } else {
        m_cursorSize.setHeight(64);
    }
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
60

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
61
62
63
64
65
66
67
    int ret = drmGetCap(fd, DRM_CAP_TIMESTAMP_MONOTONIC, &capability);
    if (ret == 0 && capability == 1) {
        m_presentationClock = CLOCK_MONOTONIC;
    } else {
        m_presentationClock = CLOCK_REALTIME;
    }

68
69
70
71
72
    if (!qEnvironmentVariableIsSet("KWIN_DRM_NO_MODIFIERS")) {
        m_addFB2ModifiersSupported = drmGetCap(fd, DRM_CAP_ADDFB2_MODIFIERS, &capability) && capability == 1;
        qCDebug(KWIN_DRM) << "drmModeAddFB2WithModifiers is" << (m_addFB2ModifiersSupported ? "supported" : "not supported");
    }

73
74
75
76
    // find out if this GPU is using the NVidia proprietary driver
    DrmScopedPointer<drmVersion> version(drmGetVersion(fd));
    m_useEglStreams = strstr(version->name, "nvidia-drm");

Xaver Hugl's avatar
Xaver Hugl committed
77
    m_socketNotifier = new QSocketNotifier(fd, QSocketNotifier::Read, this);
78
    connect(m_socketNotifier, &QSocketNotifier::activated, this, &DrmGpu::dispatchEvents);
79
80
81
82
83
84

    // trying to activate Atomic Mode Setting (this means also Universal Planes)
    static const bool atomicModesetting = !qEnvironmentVariableIsSet("KWIN_DRM_NO_AMS");
    if (atomicModesetting) {
        tryAMS();
    }
85
86
87
88
}

DrmGpu::~DrmGpu()
{
Xaver Hugl's avatar
Xaver Hugl committed
89
    waitIdle();
90
91
92
93
    const auto outputs = m_outputs;
    for (const auto &output : outputs) {
        removeOutput(output);
    }
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
94
95
96
    if (m_eglDisplay != EGL_NO_DISPLAY) {
        eglTerminate(m_eglDisplay);
    }
97
98
99
    qDeleteAll(m_crtcs);
    qDeleteAll(m_connectors);
    qDeleteAll(m_planes);
Xaver Hugl's avatar
Xaver Hugl committed
100
    delete m_socketNotifier;
101
102
103
104
105
#if HAVE_GBM
    if (m_gbmDevice) {
        gbm_device_destroy(m_gbmDevice);
    }
#endif
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
106
    m_backend->session()->closeRestricted(m_fd);
107
108
}

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
109
110
111
112
113
clockid_t DrmGpu::presentationClock() const
{
    return m_presentationClock;
}

114
115
116
117
void DrmGpu::tryAMS()
{
    m_atomicModeSetting = false;
    if (drmSetClientCap(m_fd, DRM_CLIENT_CAP_ATOMIC, 1) == 0) {
Méven Car's avatar
Méven Car committed
118
        m_atomicModeSetting = true;
119
120
121
        DrmScopedPointer<drmModePlaneRes> planeResources(drmModeGetPlaneResources(m_fd));
        if (!planeResources) {
            qCWarning(KWIN_DRM) << "Failed to get plane resources. Falling back to legacy mode on GPU " << m_devNode;
122
123
            m_atomicModeSetting = false;
            return;
124
        }
125
126
127
128
129
        qCDebug(KWIN_DRM) << "Using Atomic Mode Setting on gpu" << m_devNode;
        qCDebug(KWIN_DRM) << "Number of planes on GPU" << m_devNode << ":" << planeResources->count_planes;
        // create the plane objects
        for (unsigned int i = 0; i < planeResources->count_planes; ++i) {
            DrmScopedPointer<drmModePlane> kplane(drmModeGetPlane(m_fd, planeResources->planes[i]));
130
            DrmPlane *p = new DrmPlane(this, kplane->plane_id);
131
132
133
            if (p->init()) {
                m_planes << p;
            } else {
134
135
136
                delete p;
            }
        }
137
138
139
140
141
        if (m_planes.isEmpty()) {
            qCWarning(KWIN_DRM) << "Failed to create any plane. Falling back to legacy mode on GPU " << m_devNode;
            m_atomicModeSetting = false;
        }
        m_unusedPlanes = m_planes;
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
    } else {
        qCWarning(KWIN_DRM) << "drmSetClientCap for Atomic Mode Setting failed. Using legacy mode on GPU" << m_devNode;
    }
}

bool DrmGpu::updateOutputs()
{
    auto oldConnectors = m_connectors;
    auto oldCrtcs = m_crtcs;
    DrmScopedPointer<drmModeRes> resources(drmModeGetResources(m_fd));
    if (!resources) {
        qCWarning(KWIN_DRM) << "drmModeGetResources failed";
        return false;
    }

    for (int i = 0; i < resources->count_connectors; ++i) {
        const uint32_t currentConnector = resources->connectors[i];
        auto it = std::find_if(m_connectors.constBegin(), m_connectors.constEnd(), [currentConnector] (DrmConnector *c) { return c->id() == currentConnector; });
        if (it == m_connectors.constEnd()) {
161
            auto c = new DrmConnector(this, currentConnector);
162
163
164
165
166
167
168
            if (!c->init()) {
                delete c;
                continue;
            }
            if (c->isNonDesktop()) {
                delete c;
                continue;
169
            }
Xaver Hugl's avatar
Xaver Hugl committed
170
171
172
173
            if (!c->isConnected()) {
                delete c;
                continue;
            }
174
175
176
177
178
179
180
181
182
183
            m_connectors << c;
        } else {
            oldConnectors.removeOne(*it);
        }
    }

    for (int i = 0; i < resources->count_crtcs; ++i) {
        const uint32_t currentCrtc = resources->crtcs[i];
        auto it = std::find_if(m_crtcs.constBegin(), m_crtcs.constEnd(), [currentCrtc] (DrmCrtc *c) { return c->id() == currentCrtc; });
        if (it == m_crtcs.constEnd()) {
184
            auto c = new DrmCrtc(this, currentCrtc, i);
185
            if (!c->init()) {
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
                delete c;
                continue;
            }
            m_crtcs << c;
        } else {
            oldCrtcs.removeOne(*it);
        }
    }

    for (auto c : qAsConst(oldConnectors)) {
        m_connectors.removeOne(c);
    }
    for (auto c : qAsConst(oldCrtcs)) {
        m_crtcs.removeOne(c);
    }
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
201

202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
    QVector<DrmOutput*> connectedOutputs;
    QVector<DrmConnector*> pendingConnectors;

    // split up connected connectors in already or not yet assigned ones
    for (DrmConnector *con : qAsConst(m_connectors)) {
        if (!con->isConnected()) {
            continue;
        }

        if (DrmOutput *o = findOutput(con->id())) {
            connectedOutputs << o;
        } else {
            pendingConnectors << con;
        }
    }

    // check for outputs which got removed
    QVector<DrmOutput*> removedOutputs;
    auto it = m_outputs.begin();
    while (it != m_outputs.end()) {
        if (connectedOutputs.contains(*it)) {
            it++;
            continue;
        }
        DrmOutput *removed = *it;
        it = m_outputs.erase(it);
        removedOutputs.append(removed);
    }
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
230

231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
    for (DrmConnector *con : qAsConst(pendingConnectors)) {
        DrmScopedPointer<drmModeConnector> connector(drmModeGetConnector(m_fd, con->id()));
        if (!connector) {
            continue;
        }
        if (connector->count_modes == 0) {
            continue;
        }
        bool outputDone = false;

        QVector<uint32_t> encoders = con->encoders();
        for (auto encId : qAsConst(encoders)) {
            DrmScopedPointer<drmModeEncoder> encoder(drmModeGetEncoder(m_fd, encId));
            if (!encoder) {
                continue;
            }
            for (DrmCrtc *crtc : qAsConst(m_crtcs)) {
248
                if (!(encoder->possible_crtcs & (1 << crtc->pipeIndex()))) {
249
250
                    continue;
                }
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
251

252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
                // check if crtc isn't used yet -- currently we don't allow multiple outputs on one crtc (cloned mode)
                auto it = std::find_if(connectedOutputs.constBegin(), connectedOutputs.constEnd(),
                    [crtc] (DrmOutput *o) {
                        return o->m_crtc == crtc;
                    }
                );
                if (it != connectedOutputs.constEnd()) {
                    continue;
                }

                // we found a suitable encoder+crtc
                // TODO: we could avoid these lib drm calls if we store all struct data in DrmCrtc and DrmConnector in the beginning
                DrmScopedPointer<drmModeCrtc> modeCrtc(drmModeGetCrtc(m_fd, crtc->id()));
                if (!modeCrtc) {
                    continue;
                }

269
270
271
272
273
                auto primary = getCompatiblePlane(DrmPlane::TypeIndex::Primary, crtc);
                if (m_atomicModeSetting && !primary) {
                    continue;
                }

274
275
276
                DrmOutput *output = new DrmOutput(this->m_backend, this);
                output->m_conn = con;
                output->m_crtc = crtc;
277
                output->m_primaryPlane = primary;
278
                output->m_mode = connector->modes[0];
279

280
281
                qCDebug(KWIN_DRM) << "For new output use mode " << output->m_mode.name << output->m_mode.hdisplay << output->m_mode.vdisplay;
                if (!output->init(connector.data())) {
282
283
284
285
                    qCWarning(KWIN_DRM) << "Failed to create output for connector " << con->id();
                    delete output;
                    continue;
                }
286
                if (!output->initCursor(m_cursorSize)) {
287
                    m_backend->setSoftwareCursorForced(true);
288
                }
289
                qCDebug(KWIN_DRM) << "Found new output with uuid" << output->uuid() << "on gpu" << m_devNode;
290
291
292
293
294
295
296
297
298
299
300
301
302

                connectedOutputs << output;
                emit outputAdded(output);
                outputDone = true;
                break;
            }
            if (outputDone) {
                break;
            }
        }
    }
    std::sort(connectedOutputs.begin(), connectedOutputs.end(), [] (DrmOutput *a, DrmOutput *b) { return a->m_conn->id() < b->m_conn->id(); });
    m_outputs = connectedOutputs;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
303

304
    for(DrmOutput *removedOutput : removedOutputs) {
305
        removeOutput(removedOutput);
306
    }
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
307

308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
    qDeleteAll(oldConnectors);
    qDeleteAll(oldCrtcs);
    return true;
}

DrmOutput *DrmGpu::findOutput(quint32 connector)
{
    auto it = std::find_if(m_outputs.constBegin(), m_outputs.constEnd(), [connector] (DrmOutput *o) {
        return o->m_conn->id() == connector;
    });
    if (it != m_outputs.constEnd()) {
        return *it;
    }
    return nullptr;
}

324
325
326
327
328
329
DrmPlane *DrmGpu::getCompatiblePlane(DrmPlane::TypeIndex typeIndex, DrmCrtc *crtc)
{
    for (auto plane : m_unusedPlanes) {
        if (plane->type() != typeIndex) {
            continue;
        }
330
        if (plane->isCrtcSupported(crtc->pipeIndex())) {
331
332
333
334
335
336
337
            m_unusedPlanes.removeOne(plane);
            return plane;
        }
    }
    return nullptr;
}

Xaver Hugl's avatar
Xaver Hugl committed
338
339
340
341
342
343
344
345
346
347
void DrmGpu::waitIdle()
{
    m_socketNotifier->setEnabled(false);
    while (true) {
        const bool idle = std::all_of(m_outputs.constBegin(), m_outputs.constEnd(), [](DrmOutput *output){
            return !output->m_pageFlipPending;
        });
        if (idle) {
            break;
        }
348
349
350
351
352
353
354
355
356
357
358
        pollfd pfds[1];
        pfds[0].fd = m_fd;
        pfds[0].events = POLLIN;

        const int ready = poll(pfds, 1, 30000);
        if (ready < 0) {
            if (errno != EINTR) {
                qCWarning(KWIN_DRM) << Q_FUNC_INFO << "poll() failed:" << strerror(errno);
                break;
            }
        } else if (ready == 0) {
Xaver Hugl's avatar
Xaver Hugl committed
359
360
            qCWarning(KWIN_DRM) << "No drm events for gpu" << m_devNode << "within last 30 seconds";
            break;
361
362
        } else {
            dispatchEvents();
Xaver Hugl's avatar
Xaver Hugl committed
363
364
365
366
367
        }
    };
    m_socketNotifier->setEnabled(true);
}

Xaver Hugl's avatar
Xaver Hugl committed
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
static std::chrono::nanoseconds convertTimestamp(const timespec &timestamp)
{
    return std::chrono::seconds(timestamp.tv_sec) + std::chrono::nanoseconds(timestamp.tv_nsec);
}

static std::chrono::nanoseconds convertTimestamp(clockid_t sourceClock, clockid_t targetClock,
                                                 const timespec &timestamp)
{
    if (sourceClock == targetClock) {
        return convertTimestamp(timestamp);
    }

    timespec sourceCurrentTime = {};
    timespec targetCurrentTime = {};

    clock_gettime(sourceClock, &sourceCurrentTime);
    clock_gettime(targetClock, &targetCurrentTime);

    const auto delta = convertTimestamp(sourceCurrentTime) - convertTimestamp(timestamp);
    return convertTimestamp(targetCurrentTime) - delta;
}

static void pageFlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data)
{
    Q_UNUSED(fd)
    Q_UNUSED(frame)

    auto output = static_cast<DrmOutput *>(data);

Adriaan de Groot's avatar
Adriaan de Groot committed
397
398
399
400
401
    // The static_cast<> here are for a 32-bit environment where 
    // sizeof(time_t) == sizeof(unsigned int) == 4 . Putting @p sec
    // into a time_t cuts off the most-significant bit (after the
    // year 2038), similarly long can't hold all the bits of an
    // unsigned multiplication.
Xaver Hugl's avatar
Xaver Hugl committed
402
403
    std::chrono::nanoseconds timestamp = convertTimestamp(output->gpu()->presentationClock(),
                                                          CLOCK_MONOTONIC,
Adriaan de Groot's avatar
Adriaan de Groot committed
404
                                                          { static_cast<time_t>(sec), static_cast<long>(usec * 1000) });
Xaver Hugl's avatar
Xaver Hugl committed
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
    if (timestamp == std::chrono::nanoseconds::zero()) {
        qCDebug(KWIN_DRM, "Got invalid timestamp (sec: %u, usec: %u) on output %s",
                sec, usec, qPrintable(output->name()));
        timestamp = std::chrono::steady_clock::now().time_since_epoch();
    }

    output->pageFlipped();
    RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(output->renderLoop());
    renderLoopPrivate->notifyFrameCompleted(timestamp);
}

void DrmGpu::dispatchEvents()
{
    if (!m_backend->session()->isActive()) {
        return;
    }
    drmEventContext context = {};
    context.version = 2;
    context.page_flip_handler = pageFlipHandler;
    drmHandleEvent(m_fd, &context);
}

427
428
429
430
431
432
433
434
435
436
437
438
439
440
void DrmGpu::removeOutput(DrmOutput *output)
{
    m_outputs.removeOne(output);
    emit outputRemoved(output);
    output->teardown();
    output->m_crtc = nullptr;
    m_connectors.removeOne(output->m_conn);
    delete output->m_conn;
    output->m_conn = nullptr;
    if (output->m_primaryPlane) {
        m_unusedPlanes << output->m_primaryPlane;
    }
}

441
}