2
* Copyright Ā© 2013 Canonical Ltd.
4
* This program is free software: you can redistribute it and/or modify
5
* it under the terms of the GNU General Public License version 3 as
6
* published by the Free Software Foundation.
8
* This program is distributed in the hope that it will be useful,
9
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
* GNU General Public License for more details.
13
* You should have received a copy of the GNU General Public License
14
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16
* Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
19
#include "src/platform/graphics/mesa/kms_page_flipper.h"
21
#include "mir_test_doubles/mock_drm.h"
22
#include "mir_test_doubles/mock_display_report.h"
23
#include "mir/graphics/null_display_report.h"
24
#include "mir_test/fake_shared.h"
26
#include <gtest/gtest.h>
27
#include <gmock/gmock.h>
32
#include <unordered_set>
36
namespace mg = mir::graphics;
37
namespace mgm = mir::graphics::mesa;
38
namespace mt = mir::test;
39
namespace mtd = mir::test::doubles;
44
class KMSPageFlipperTest : public ::testing::Test
48
: page_flipper{mock_drm.fake_drm.fd()}
52
testing::NiceMock<mtd::MockDRM> mock_drm;
53
mgm::KMSPageFlipper page_flipper;
56
ACTION_P(InvokePageFlipHandler, param)
58
int const dont_care{0};
61
arg1->page_flip_handler(dont_care, dont_care, dont_care, dont_care, *param);
62
ASSERT_EQ(1, read(arg0, &dummy, 1));
67
TEST_F(KMSPageFlipperTest, schedule_flip_calls_drm_page_flip)
69
using namespace testing;
71
uint32_t const crtc_id{10};
72
uint32_t const fb_id{101};
74
EXPECT_CALL(mock_drm, drmModePageFlip(mock_drm.fake_drm.fd(),
75
crtc_id, fb_id, _, _))
78
page_flipper.schedule_flip(crtc_id, fb_id);
81
TEST_F(KMSPageFlipperTest, double_schedule_flip_throws)
83
using namespace testing;
85
uint32_t const crtc_id{10};
86
uint32_t const fb_id{101};
88
EXPECT_CALL(mock_drm, drmModePageFlip(mock_drm.fake_drm.fd(),
89
crtc_id, fb_id, _, _))
92
page_flipper.schedule_flip(crtc_id, fb_id);
95
page_flipper.schedule_flip(crtc_id, fb_id);
99
TEST_F(KMSPageFlipperTest, wait_for_flip_handles_drm_event)
101
using namespace testing;
103
uint32_t const crtc_id{10};
104
uint32_t const fb_id{101};
105
void* user_data{nullptr};
107
EXPECT_CALL(mock_drm, drmModePageFlip(mock_drm.fake_drm.fd(),
108
crtc_id, fb_id, _, _))
110
.WillOnce(DoAll(SaveArg<4>(&user_data), Return(0)));
112
EXPECT_CALL(mock_drm, drmHandleEvent(mock_drm.fake_drm.fd(), _))
114
.WillOnce(DoAll(InvokePageFlipHandler(&user_data), Return(0)));
116
page_flipper.schedule_flip(crtc_id, fb_id);
118
/* Fake a DRM event */
119
EXPECT_EQ(1, write(mock_drm.fake_drm.write_fd(), "a", 1));
121
page_flipper.wait_for_flip(crtc_id);
124
TEST_F(KMSPageFlipperTest, wait_for_non_scheduled_page_flip_doesnt_block)
126
using namespace testing;
128
uint32_t const crtc_id{10};
130
EXPECT_CALL(mock_drm, drmModePageFlip(_, _, _, _, _))
133
EXPECT_CALL(mock_drm, drmHandleEvent(_, _))
136
page_flipper.wait_for_flip(crtc_id);
139
TEST_F(KMSPageFlipperTest, failure_in_wait_for_flip_throws)
141
using namespace testing;
143
uint32_t const crtc_id{10};
144
uint32_t const fb_id{101};
145
void* user_data{nullptr};
147
EXPECT_CALL(mock_drm, drmModePageFlip(mock_drm.fake_drm.fd(),
148
crtc_id, fb_id, _, _))
150
.WillOnce(DoAll(SaveArg<4>(&user_data), Return(0)));
152
EXPECT_CALL(mock_drm, drmHandleEvent(_, _))
155
page_flipper.schedule_flip(crtc_id, fb_id);
157
/* Cause a failure in wait_for_flip */
158
close(mock_drm.fake_drm.fd());
161
page_flipper.wait_for_flip(crtc_id);
162
}, std::runtime_error);
165
TEST_F(KMSPageFlipperTest, wait_for_flips_interleaved)
167
using namespace testing;
169
uint32_t const fb_id{101};
170
std::vector<uint32_t> const crtc_ids{10, 11, 12};
171
std::vector<void*> user_data{nullptr, nullptr, nullptr};
173
EXPECT_CALL(mock_drm, drmModePageFlip(mock_drm.fake_drm.fd(),
176
.WillOnce(DoAll(SaveArg<4>(&user_data[0]), Return(0)))
177
.WillOnce(DoAll(SaveArg<4>(&user_data[1]), Return(0)))
178
.WillOnce(DoAll(SaveArg<4>(&user_data[2]), Return(0)));
180
EXPECT_CALL(mock_drm, drmHandleEvent(mock_drm.fake_drm.fd(), _))
182
.WillOnce(DoAll(InvokePageFlipHandler(&user_data[1]), Return(0)))
183
.WillOnce(DoAll(InvokePageFlipHandler(&user_data[2]), Return(0)))
184
.WillOnce(DoAll(InvokePageFlipHandler(&user_data[0]), Return(0)));
186
for (auto crtc_id : crtc_ids)
187
page_flipper.schedule_flip(crtc_id, fb_id);
189
/* Fake 3 DRM events */
190
EXPECT_EQ(3, write(mock_drm.fake_drm.write_fd(), "abc", 3));
192
for (auto crtc_id : crtc_ids)
193
page_flipper.wait_for_flip(crtc_id);
199
class PageFlippingFunctor
202
PageFlippingFunctor(mgm::KMSPageFlipper& page_flipper,
204
: page_flipper(page_flipper), crtc_id{crtc_id}, done{false},
205
num_page_flips{0}, num_waits{0}
213
page_flipper.schedule_flip(crtc_id, 0);
215
std::this_thread::sleep_for(std::chrono::milliseconds{1});
216
page_flipper.wait_for_flip(crtc_id);
221
int page_flip_count()
223
return num_page_flips;
237
mgm::KMSPageFlipper& page_flipper;
238
uint32_t const crtc_id;
239
std::atomic<bool> done;
240
std::atomic<int> num_page_flips;
241
std::atomic<int> num_waits;
246
TEST_F(KMSPageFlipperTest, threads_switch_worker)
248
using namespace testing;
250
size_t const worker_index{0};
251
size_t const other_index{1};
252
std::vector<uint32_t> const crtc_ids{10, 11};
253
std::vector<void*> user_data{nullptr, nullptr};
254
std::vector<std::unique_ptr<PageFlippingFunctor>> page_flipping_functors;
255
std::vector<std::thread> page_flipping_threads;
258
EXPECT_CALL(mock_drm, drmModePageFlip(mock_drm.fake_drm.fd(), _, _, _, _))
260
.WillOnce(DoAll(SaveArg<4>(&user_data[worker_index]), Return(0)))
261
.WillOnce(DoAll(SaveArg<4>(&user_data[other_index]), Return(0)));
264
* The first event releases the original worker, hence we expect that
265
* then the other thread will become the worker.
267
EXPECT_CALL(mock_drm, drmHandleEvent(mock_drm.fake_drm.fd(), _))
269
.WillOnce(DoAll(InvokePageFlipHandler(&user_data[worker_index]), Return(0)))
270
.WillOnce(DoAll(InvokePageFlipHandler(&user_data[other_index]), Return(0)));
272
/* Start the page-flipping threads */
273
for (auto crtc_id : crtc_ids)
275
auto pf = std::unique_ptr<PageFlippingFunctor>(new PageFlippingFunctor{page_flipper, crtc_id});
276
page_flipping_functors.push_back(std::move(pf));
277
page_flipping_threads.push_back(std::thread{std::ref(*page_flipping_functors.back())});
279
/* Wait for page-flip request and tell flipper to stop after this iteration */
280
while (page_flipping_functors.back()->page_flip_count() == 0)
281
std::this_thread::sleep_for(std::chrono::milliseconds{1});
282
page_flipping_functors.back()->stop();
284
/* Wait until the (first) thread has become the worker */
285
while (tid == std::thread::id())
287
std::this_thread::sleep_for(std::chrono::milliseconds{1});
288
tid = page_flipper.debug_get_worker_tid();
292
EXPECT_EQ(page_flipping_threads[worker_index].get_id(), tid);
294
/* Fake a DRM event */
295
EXPECT_EQ(1, write(mock_drm.fake_drm.write_fd(), "a", 1));
297
page_flipping_threads[worker_index].join();
299
/* Wait for the worker to switch */
300
while (tid != page_flipping_threads[other_index].get_id())
302
std::this_thread::sleep_for(std::chrono::milliseconds{1});
303
tid = page_flipper.debug_get_worker_tid();
306
/* Fake another DRM event to unblock the remaining thread */
307
EXPECT_EQ(1, write(mock_drm.fake_drm.write_fd(), "a", 1));
309
page_flipping_threads[other_index].join();
312
TEST_F(KMSPageFlipperTest, threads_worker_notifies_non_worker)
314
using namespace testing;
316
size_t const worker_index{0};
317
size_t const other_index{1};
318
std::vector<uint32_t> const crtc_ids{10, 11};
319
std::vector<void*> user_data{nullptr, nullptr};
320
std::vector<std::unique_ptr<PageFlippingFunctor>> page_flipping_functors;
321
std::vector<std::thread> page_flipping_threads;
324
EXPECT_CALL(mock_drm, drmModePageFlip(mock_drm.fake_drm.fd(), _, _, _, _))
326
.WillOnce(DoAll(SaveArg<4>(&user_data[worker_index]), Return(0)))
327
.WillOnce(DoAll(SaveArg<4>(&user_data[other_index]), Return(0)));
330
* The first event releases the non-worker thread, hence we expect that
331
* original worker not change.
333
EXPECT_CALL(mock_drm, drmHandleEvent(mock_drm.fake_drm.fd(), _))
335
.WillOnce(DoAll(InvokePageFlipHandler(&user_data[other_index]), Return(0)))
336
.WillOnce(DoAll(InvokePageFlipHandler(&user_data[worker_index]), Return(0)));
338
/* Start the page-flipping threads */
339
for (auto crtc_id : crtc_ids)
341
auto pf = std::unique_ptr<PageFlippingFunctor>(new PageFlippingFunctor{page_flipper, crtc_id});
342
page_flipping_functors.push_back(std::move(pf));
343
page_flipping_threads.push_back(std::thread{std::ref(*page_flipping_functors.back())});
345
/* Wait for page-flip request and tell flipper to stop after this iteration */
346
while (page_flipping_functors.back()->page_flip_count() == 0)
347
std::this_thread::sleep_for(std::chrono::milliseconds{1});
348
page_flipping_functors.back()->stop();
350
/* Wait until the (first) thread has become the worker */
351
while (tid == std::thread::id())
353
std::this_thread::sleep_for(std::chrono::milliseconds{1});
354
tid = page_flipper.debug_get_worker_tid();
358
EXPECT_EQ(page_flipping_threads[worker_index].get_id(), tid);
360
/* Fake a DRM event */
361
EXPECT_EQ(1, write(mock_drm.fake_drm.write_fd(), "a", 1));
363
/* Wait for the non-worker thread to exit */
364
page_flipping_threads[other_index].join();
366
/* Check that the worker hasn't changed */
367
EXPECT_EQ(page_flipping_threads[worker_index].get_id(),
368
page_flipper.debug_get_worker_tid());
370
/* Fake another DRM event to unblock the remaining thread */
371
EXPECT_EQ(1, write(mock_drm.fake_drm.write_fd(), "a", 1));
373
page_flipping_threads[worker_index].join();
379
class PageFlipCounter
382
void add_flip(uint32_t crtc_id, void* user_data)
384
std::lock_guard<std::mutex> lock{data_mutex};
386
data.push_back({CountType::flip, crtc_id});
387
pending_flips.insert(user_data);
390
void add_handle_event(uint32_t crtc_id)
392
std::lock_guard<std::mutex> lock{data_mutex};
394
data.push_back({CountType::handle_event, crtc_id});
399
std::lock_guard<std::mutex> lock{data_mutex};
401
return std::count_if(begin(data), end(data), [](CountElement& e) -> bool
403
return e.type == CountType::flip;
407
int count_handle_events()
409
std::lock_guard<std::mutex> lock{data_mutex};
411
return std::count_if(begin(data), end(data), [](CountElement& e) -> bool
413
return e.type == CountType::handle_event;
417
bool no_consecutive_flips_for_same_crtc_id()
419
std::lock_guard<std::mutex> lock{data_mutex};
421
std::unordered_set<uint32_t> pending_crtc_ids;
425
if (e.type == CountType::flip)
427
if (pending_crtc_ids.find(e.crtc_id) != pending_crtc_ids.end())
430
pending_crtc_ids.insert(e.crtc_id);
432
else if (e.type == CountType::handle_event)
434
if (pending_crtc_ids.find(e.crtc_id) == pending_crtc_ids.end())
436
pending_crtc_ids.erase(e.crtc_id);
443
void* get_pending_flip_data()
445
std::lock_guard<std::mutex> lock{data_mutex};
447
auto iter = pending_flips.begin();
448
if (iter == pending_flips.end())
455
pending_flips.erase(iter);
461
enum class CountType {flip, handle_event};
468
std::vector<CountElement> data;
469
std::unordered_set<void*> pending_flips;
470
std::mutex data_mutex;
473
ACTION_P(InvokePageFlipHandlerWithPendingData, counter)
475
int const dont_care{0};
476
int const drm_fd{arg0};
479
auto user_data = static_cast<mgm::PageFlipEventData*>(counter->get_pending_flip_data());
480
uint32_t const crtc_id{user_data->crtc_id};
482
/* Remove the event from the drm event queue */
483
ASSERT_EQ(1, read(drm_fd, &dummy, 1));
484
/* Call the page flip handler */
485
arg1->page_flip_handler(dont_care, dont_care, dont_care, dont_care, user_data);
486
/* Record this call */
487
counter->add_handle_event(crtc_id);
490
ACTION_P2(AddPageFlipEvent, counter, write_drm_fd)
492
uint32_t const crtc_id{arg0};
493
void* const user_data{arg1};
495
/* Record this call */
496
counter->add_flip(crtc_id, user_data);
497
/* Add an event to the drm event queue */
498
EXPECT_EQ(1, write(write_drm_fd, "a", 1));
503
TEST_F(KMSPageFlipperTest, threads_concurrent_page_flips_dont_deadlock)
505
using namespace testing;
507
std::vector<uint32_t> const crtc_ids{10, 11, 12};
508
std::vector<std::unique_ptr<PageFlippingFunctor>> page_flipping_functors;
509
std::vector<std::thread> page_flipping_threads;
510
PageFlipCounter counter;
512
EXPECT_CALL(mock_drm, drmModePageFlip(mock_drm.fake_drm.fd(), _, _, _, _))
513
.WillRepeatedly(DoAll(WithArgs<1,4>(AddPageFlipEvent(&counter, mock_drm.fake_drm.write_fd())),
516
EXPECT_CALL(mock_drm, drmHandleEvent(mock_drm.fake_drm.fd(), _))
517
.WillRepeatedly(DoAll(InvokePageFlipHandlerWithPendingData(&counter),
521
for (auto crtc_id : crtc_ids)
523
auto pf = std::unique_ptr<PageFlippingFunctor>(new PageFlippingFunctor{page_flipper, crtc_id});
524
page_flipping_functors.push_back(std::move(pf));
525
page_flipping_threads.push_back(std::thread{std::ref(*page_flipping_functors.back())});
528
/* Wait for at least min_flips page-flips to be processed */
529
int const min_flips{100};
531
while (counter.count_flips() < min_flips)
532
std::this_thread::sleep_for(std::chrono::milliseconds{1});
534
/* Tell the flippers to stop and wait for them to finish */
535
for (auto& pf : page_flipping_functors)
538
for (auto& pf_thread : page_flipping_threads)
542
EXPECT_EQ(counter.count_flips(), counter.count_handle_events());
543
EXPECT_TRUE(counter.no_consecutive_flips_for_same_crtc_id());