1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/ozone/platform/drm/gpu/hardware_display_plane_manager_atomic.h"
6
7 #include <sync/sync.h>
8 #include <xf86drm.h>
9 #include <xf86drmMode.h>
10 #include <memory>
11 #include <utility>
12
13 #include "base/bind.h"
14 #include "base/containers/flat_set.h"
15 #include "base/files/platform_file.h"
16 #include "base/logging.h"
17 #include "base/stl_util.h"
18 #include "base/threading/sequenced_task_runner_handle.h"
19 #include "ui/gfx/gpu_fence.h"
20 #include "ui/gfx/gpu_fence_handle.h"
21 #include "ui/ozone/platform/drm/common/drm_util.h"
22 #include "ui/ozone/platform/drm/gpu/crtc_controller.h"
23 #include "ui/ozone/platform/drm/gpu/drm_device.h"
24 #include "ui/ozone/platform/drm/gpu/drm_framebuffer.h"
25 #include "ui/ozone/platform/drm/gpu/drm_gpu_util.h"
26 #include "ui/ozone/platform/drm/gpu/hardware_display_plane_atomic.h"
27 #include "ui/ozone/platform/drm/gpu/page_flip_request.h"
28
29 namespace ui {
30
31 namespace {
32
CreateMergedGpuFenceFromFDs(std::vector<base::ScopedFD> fence_fds)33 std::unique_ptr<gfx::GpuFence> CreateMergedGpuFenceFromFDs(
34 std::vector<base::ScopedFD> fence_fds) {
35 base::ScopedFD merged_fd;
36
37 for (auto& fd : fence_fds) {
38 if (merged_fd.is_valid()) {
39 merged_fd.reset(sync_merge("", merged_fd.get(), fd.get()));
40 DCHECK(merged_fd.is_valid());
41 } else {
42 merged_fd = std::move(fd);
43 }
44 }
45
46 if (merged_fd.is_valid()) {
47 gfx::GpuFenceHandle handle;
48 handle.owned_fd = std::move(merged_fd);
49 return std::make_unique<gfx::GpuFence>(std::move(handle));
50 }
51
52 return nullptr;
53 }
54
GetCrtcIdsOfPlanes(const HardwareDisplayPlaneList & plane_list)55 std::vector<uint32_t> GetCrtcIdsOfPlanes(
56 const HardwareDisplayPlaneList& plane_list) {
57 std::vector<uint32_t> crtcs;
58 for (HardwareDisplayPlane* plane : plane_list.plane_list) {
59 HardwareDisplayPlaneAtomic* atomic_plane =
60 static_cast<HardwareDisplayPlaneAtomic*>(plane);
61 if (crtcs.empty() || crtcs.back() != atomic_plane->AssignedCrtcId())
62 crtcs.push_back(atomic_plane->AssignedCrtcId());
63 }
64 return crtcs;
65 }
66
67 } // namespace
68
HardwareDisplayPlaneManagerAtomic(DrmDevice * drm)69 HardwareDisplayPlaneManagerAtomic::HardwareDisplayPlaneManagerAtomic(
70 DrmDevice* drm)
71 : HardwareDisplayPlaneManager(drm) {}
72
73 HardwareDisplayPlaneManagerAtomic::~HardwareDisplayPlaneManagerAtomic() =
74 default;
75
SetCrtcProps(drmModeAtomicReq * atomic_request,uint32_t crtc_id,bool set_active,uint32_t mode_id)76 bool HardwareDisplayPlaneManagerAtomic::SetCrtcProps(
77 drmModeAtomicReq* atomic_request,
78 uint32_t crtc_id,
79 bool set_active,
80 uint32_t mode_id) {
81 // Only making a copy here to retrieve the the props IDs. The state will be
82 // updated only after a successful modeset.
83 CrtcProperties modeset_props = GetCrtcStateForCrtcId(crtc_id).properties;
84 modeset_props.active.value = static_cast<uint64_t>(set_active);
85 modeset_props.mode_id.value = mode_id;
86
87 bool status =
88 AddPropertyIfValid(atomic_request, crtc_id, modeset_props.active);
89 status &= AddPropertyIfValid(atomic_request, crtc_id, modeset_props.mode_id);
90 return status;
91 }
92
SetConnectorProps(drmModeAtomicReq * atomic_request,uint32_t connector_id,uint32_t crtc_id)93 bool HardwareDisplayPlaneManagerAtomic::SetConnectorProps(
94 drmModeAtomicReq* atomic_request,
95 uint32_t connector_id,
96 uint32_t crtc_id) {
97 int connector_index = LookupConnectorIndex(connector_id);
98 DCHECK_GE(connector_index, 0);
99 // Only making a copy here to retrieve the the props IDs. The state will be
100 // updated only after a successful modeset.
101 ConnectorProperties connector_props = connectors_props_[connector_index];
102 connector_props.crtc_id.value = crtc_id;
103
104 return AddPropertyIfValid(atomic_request, connector_id,
105 connector_props.crtc_id);
106 }
107
Commit(CommitRequest commit_request,uint32_t flags)108 bool HardwareDisplayPlaneManagerAtomic::Commit(CommitRequest commit_request,
109 uint32_t flags) {
110 bool is_testing = flags & DRM_MODE_ATOMIC_TEST_ONLY;
111 bool status = true;
112
113 std::vector<ScopedDrmPropertyBlob> scoped_blobs;
114
115 base::flat_set<HardwareDisplayPlaneList*> enable_planes_lists;
116 base::flat_set<HardwareDisplayPlaneList*> all_planes_lists;
117
118 ScopedDrmAtomicReqPtr atomic_request(drmModeAtomicAlloc());
119
120 for (const auto& crtc_request : commit_request) {
121 if (crtc_request.plane_list())
122 all_planes_lists.insert(crtc_request.plane_list());
123
124 uint32_t mode_id = 0;
125 if (crtc_request.should_enable()) {
126 auto mode_blob = drm_->CreatePropertyBlob(&crtc_request.mode(),
127 sizeof(crtc_request.mode()));
128 status &= (mode_blob != nullptr);
129 if (mode_blob) {
130 scoped_blobs.push_back(std::move(mode_blob));
131 mode_id = scoped_blobs.back()->id();
132 }
133 }
134
135 uint32_t crtc_id = crtc_request.crtc_id();
136
137 status &= SetCrtcProps(atomic_request.get(), crtc_id,
138 crtc_request.should_enable(), mode_id);
139 status &=
140 SetConnectorProps(atomic_request.get(), crtc_request.connector_id(),
141 crtc_request.should_enable() * crtc_id);
142
143 if (crtc_request.should_enable()) {
144 DCHECK(crtc_request.plane_list());
145 status &= AssignOverlayPlanes(crtc_request.plane_list(),
146 crtc_request.overlays(), crtc_id);
147 enable_planes_lists.insert(crtc_request.plane_list());
148 }
149 }
150
151 // TODO(markyacoub): Ideally this doesn't need to be a separate step. It
152 // should all be handled in Set{Crtc,Connector,Plane}Props() modulo some state
153 // tracking changes that should be done post commit. Break it apart when both
154 // Commit() are consolidated.
155 for (HardwareDisplayPlaneList* list : enable_planes_lists) {
156 SetAtomicPropsForCommit(atomic_request.get(), list,
157 GetCrtcIdsOfPlanes(*list), is_testing);
158 }
159
160 // TODO(markyacoub): failed |status|'s should be made as DCHECKs. The only
161 // reason some of these would be failing is OOM. If we OOM-ed there's no point
162 // in trying to recover.
163 if (!status || !drm_->CommitProperties(atomic_request.get(), flags,
164 commit_request.size(), nullptr)) {
165 if (is_testing)
166 VPLOG(2) << "Modeset Test is rejected.";
167 else
168 PLOG(ERROR) << "Failed to commit properties for modeset.";
169
170 for (HardwareDisplayPlaneList* list : all_planes_lists)
171 ResetCurrentPlaneList(list);
172
173 return false;
174 }
175
176 if (!is_testing)
177 UpdateCrtcAndPlaneStatesAfterModeset(commit_request);
178
179 for (HardwareDisplayPlaneList* list : enable_planes_lists)
180 list->plane_list.clear();
181
182 return true;
183 }
184
SetAtomicPropsForCommit(drmModeAtomicReq * atomic_request,HardwareDisplayPlaneList * plane_list,const std::vector<uint32_t> & crtcs,bool test_only)185 void HardwareDisplayPlaneManagerAtomic::SetAtomicPropsForCommit(
186 drmModeAtomicReq* atomic_request,
187 HardwareDisplayPlaneList* plane_list,
188 const std::vector<uint32_t>& crtcs,
189 bool test_only) {
190 for (HardwareDisplayPlane* plane : plane_list->plane_list) {
191 HardwareDisplayPlaneAtomic* atomic_plane =
192 static_cast<HardwareDisplayPlaneAtomic*>(plane);
193 atomic_plane->SetPlaneProps(atomic_request);
194 }
195
196 for (HardwareDisplayPlane* plane : plane_list->old_plane_list) {
197 if (!base::Contains(plane_list->plane_list, plane)) {
198 // |plane| is shared state between |old_plane_list| and |plane_list|.
199 // When we call BeginFrame(), we reset in_use since we need to be able to
200 // allocate the planes as needed. The current frame might not need to use
201 // |plane|, thus |plane->in_use()| would be false even though the previous
202 // frame used it. It's existence in |old_plane_list| is sufficient to
203 // signal that |plane| was in use previously.
204 plane->set_in_use(false);
205 HardwareDisplayPlaneAtomic* atomic_plane =
206 static_cast<HardwareDisplayPlaneAtomic*>(plane);
207 atomic_plane->AssignPlaneProps(0, 0, gfx::Rect(), gfx::Rect(),
208 gfx::OVERLAY_TRANSFORM_NONE,
209 base::kInvalidPlatformFile);
210 atomic_plane->SetPlaneProps(atomic_request);
211 }
212 }
213
214 for (uint32_t crtc : crtcs) {
215 int idx = LookupCrtcIndex(crtc);
216
217 #if defined(COMMIT_PROPERTIES_ON_PAGE_FLIP)
218 // Apply all CRTC properties in the page-flip so we don't block the
219 // swap chain for a vsync.
220 // TODO(dnicoara): See if we can apply these properties async using
221 // DRM_MODE_ATOMIC_ASYNC_UPDATE flag when committing.
222 AddPropertyIfValid(atomic_request, crtc,
223 crtc_state_[idx].properties.degamma_lut);
224 AddPropertyIfValid(atomic_request, crtc,
225 crtc_state_[idx].properties.gamma_lut);
226 AddPropertyIfValid(atomic_request, crtc, crtc_state_[idx].properties.ctm);
227 #endif
228
229 AddPropertyIfValid(atomic_request, crtc,
230 crtc_state_[idx].properties.background_color);
231 }
232
233 if (test_only) {
234 for (auto* plane : plane_list->plane_list) {
235 plane->set_in_use(false);
236 }
237 for (auto* plane : plane_list->old_plane_list) {
238 plane->set_in_use(true);
239 }
240 } else {
241 plane_list->plane_list.swap(plane_list->old_plane_list);
242 }
243 }
244
Commit(HardwareDisplayPlaneList * plane_list,scoped_refptr<PageFlipRequest> page_flip_request,std::unique_ptr<gfx::GpuFence> * out_fence)245 bool HardwareDisplayPlaneManagerAtomic::Commit(
246 HardwareDisplayPlaneList* plane_list,
247 scoped_refptr<PageFlipRequest> page_flip_request,
248 std::unique_ptr<gfx::GpuFence>* out_fence) {
249 bool test_only = !page_flip_request;
250
251 std::vector<uint32_t> crtcs = GetCrtcIdsOfPlanes(*plane_list);
252
253 SetAtomicPropsForCommit(plane_list->atomic_property_set.get(), plane_list,
254 crtcs, test_only);
255
256 // After we perform the atomic commit, and if the caller has requested an
257 // out-fence, the out_fence_fds vector will contain any provided out-fence
258 // fds for the crtcs, therefore the scope of out_fence_fds needs to outlive
259 // the CommitProperties call. CommitProperties will write into the Receiver,
260 // and the Receiver's destructor writes into ScopedFD, so we need to ensure
261 // that the Receivers are destructed before we attempt to use their
262 // corresponding ScopedFDs.
263 std::vector<base::ScopedFD> out_fence_fds;
264 {
265 std::vector<base::ScopedFD::Receiver> out_fence_fd_receivers;
266 if (out_fence) {
267 if (!AddOutFencePtrProperties(plane_list->atomic_property_set.get(),
268 crtcs, &out_fence_fds,
269 &out_fence_fd_receivers)) {
270 ResetCurrentPlaneList(plane_list);
271 return false;
272 }
273 }
274
275 uint32_t flags =
276 test_only ? DRM_MODE_ATOMIC_TEST_ONLY : DRM_MODE_ATOMIC_NONBLOCK;
277
278 if (!drm_->CommitProperties(plane_list->atomic_property_set.get(), flags,
279 crtcs.size(), page_flip_request)) {
280 if (!test_only) {
281 PLOG(ERROR) << "Failed to commit properties for page flip.";
282 } else {
283 VPLOG(2) << "Failed to commit properties for MODE_ATOMIC_TEST_ONLY.";
284 }
285
286 ResetCurrentPlaneList(plane_list);
287 return false;
288 }
289 }
290
291 if (out_fence)
292 *out_fence = CreateMergedGpuFenceFromFDs(std::move(out_fence_fds));
293
294 plane_list->plane_list.clear();
295 plane_list->atomic_property_set.reset(drmModeAtomicAlloc());
296 return true;
297 }
298
DisableOverlayPlanes(HardwareDisplayPlaneList * plane_list)299 bool HardwareDisplayPlaneManagerAtomic::DisableOverlayPlanes(
300 HardwareDisplayPlaneList* plane_list) {
301 bool ret = true;
302
303 if (!plane_list->old_plane_list.empty()) {
304 for (HardwareDisplayPlane* plane : plane_list->old_plane_list) {
305 plane->set_in_use(false);
306 plane->set_owning_crtc(0);
307
308 HardwareDisplayPlaneAtomic* atomic_plane =
309 static_cast<HardwareDisplayPlaneAtomic*>(plane);
310 atomic_plane->AssignPlaneProps(0, 0, gfx::Rect(), gfx::Rect(),
311 gfx::OVERLAY_TRANSFORM_NONE,
312 base::kInvalidPlatformFile);
313 atomic_plane->SetPlaneProps(plane_list->atomic_property_set.get());
314 }
315 ret = drm_->CommitProperties(plane_list->atomic_property_set.get(),
316 /*flags=*/0, 0 /*unused*/, nullptr);
317 PLOG_IF(ERROR, !ret) << "Failed to commit properties for page flip.";
318 }
319
320 plane_list->atomic_property_set.reset(drmModeAtomicAlloc());
321 return ret;
322 }
323
SetColorCorrectionOnAllCrtcPlanes(uint32_t crtc_id,ScopedDrmColorCtmPtr ctm_blob_data)324 bool HardwareDisplayPlaneManagerAtomic::SetColorCorrectionOnAllCrtcPlanes(
325 uint32_t crtc_id,
326 ScopedDrmColorCtmPtr ctm_blob_data) {
327 ScopedDrmAtomicReqPtr property_set(drmModeAtomicAlloc());
328 ScopedDrmPropertyBlob property_blob(
329 drm_->CreatePropertyBlob(ctm_blob_data.get(), sizeof(drm_color_ctm)));
330
331 const int crtc_index = LookupCrtcIndex(crtc_id);
332 DCHECK_GE(crtc_index, 0);
333
334 for (auto& plane : planes_) {
335 HardwareDisplayPlaneAtomic* atomic_plane =
336 static_cast<HardwareDisplayPlaneAtomic*>(plane.get());
337
338 // This assumes planes can only belong to one crtc.
339 if (!atomic_plane->CanUseForCrtc(crtc_index))
340 continue;
341
342 if (!atomic_plane->SetPlaneCtm(property_set.get(), property_blob->id())) {
343 LOG(ERROR) << "Failed to set PLANE_CTM for plane=" << atomic_plane->id();
344 return false;
345 }
346 }
347
348 return drm_->CommitProperties(property_set.get(), DRM_MODE_ATOMIC_NONBLOCK, 0,
349 nullptr);
350 }
351
ValidatePrimarySize(const DrmOverlayPlane & primary,const drmModeModeInfo & mode)352 bool HardwareDisplayPlaneManagerAtomic::ValidatePrimarySize(
353 const DrmOverlayPlane& primary,
354 const drmModeModeInfo& mode) {
355 // Atomic KMS allows for primary planes that don't match the size of
356 // the current mode.
357 return true;
358 }
359
RequestPlanesReadyCallback(DrmOverlayPlaneList planes,base::OnceCallback<void (DrmOverlayPlaneList planes)> callback)360 void HardwareDisplayPlaneManagerAtomic::RequestPlanesReadyCallback(
361 DrmOverlayPlaneList planes,
362 base::OnceCallback<void(DrmOverlayPlaneList planes)> callback) {
363 base::SequencedTaskRunnerHandle::Get()->PostTask(
364 FROM_HERE, base::BindOnce(std::move(callback), std::move(planes)));
365 }
366
SetPlaneData(HardwareDisplayPlaneList * plane_list,HardwareDisplayPlane * hw_plane,const DrmOverlayPlane & overlay,uint32_t crtc_id,const gfx::Rect & src_rect)367 bool HardwareDisplayPlaneManagerAtomic::SetPlaneData(
368 HardwareDisplayPlaneList* plane_list,
369 HardwareDisplayPlane* hw_plane,
370 const DrmOverlayPlane& overlay,
371 uint32_t crtc_id,
372 const gfx::Rect& src_rect) {
373 HardwareDisplayPlaneAtomic* atomic_plane =
374 static_cast<HardwareDisplayPlaneAtomic*>(hw_plane);
375 uint32_t framebuffer_id = overlay.enable_blend
376 ? overlay.buffer->framebuffer_id()
377 : overlay.buffer->opaque_framebuffer_id();
378 int fence_fd = base::kInvalidPlatformFile;
379
380 if (overlay.gpu_fence) {
381 const auto& gpu_fence_handle = overlay.gpu_fence->GetGpuFenceHandle();
382 fence_fd = gpu_fence_handle.owned_fd.get();
383 }
384
385 if (!atomic_plane->AssignPlaneProps(crtc_id, framebuffer_id,
386 overlay.display_bounds, src_rect,
387 overlay.plane_transform, fence_fd)) {
388 return false;
389 }
390 return true;
391 }
392
InitializePlanes()393 bool HardwareDisplayPlaneManagerAtomic::InitializePlanes() {
394 ScopedDrmPlaneResPtr plane_resources = drm_->GetPlaneResources();
395 if (!plane_resources) {
396 PLOG(ERROR) << "Failed to get plane resources.";
397 return false;
398 }
399
400 for (uint32_t i = 0; i < plane_resources->count_planes; ++i) {
401 std::unique_ptr<HardwareDisplayPlane> plane(
402 CreatePlane(plane_resources->planes[i]));
403
404 if (plane->Initialize(drm_))
405 planes_.push_back(std::move(plane));
406 }
407
408 return true;
409 }
410
411 std::unique_ptr<HardwareDisplayPlane>
CreatePlane(uint32_t plane_id)412 HardwareDisplayPlaneManagerAtomic::CreatePlane(uint32_t plane_id) {
413 return std::make_unique<HardwareDisplayPlaneAtomic>(plane_id);
414 }
415
CommitColorMatrix(const CrtcProperties & crtc_props)416 bool HardwareDisplayPlaneManagerAtomic::CommitColorMatrix(
417 const CrtcProperties& crtc_props) {
418 DCHECK(crtc_props.ctm.id);
419 #if !defined(COMMIT_PROPERTIES_ON_PAGE_FLIP)
420 ScopedDrmAtomicReqPtr property_set(drmModeAtomicAlloc());
421 int ret = drmModeAtomicAddProperty(property_set.get(), crtc_props.id,
422 crtc_props.ctm.id, crtc_props.ctm.value);
423 if (ret < 0) {
424 LOG(ERROR) << "Failed to set CTM property for crtc=" << crtc_props.id;
425 return false;
426 }
427
428 // If we try to do this in a non-blocking fashion this can return EBUSY since
429 // there is a pending page flip. Do a blocking commit (the same as the legacy
430 // API) to ensure the properties are applied.
431 // TODO(dnicoara): Should cache these values locally and aggregate them with
432 // the page flip event otherwise this "steals" a vsync to apply the property.
433 return drm_->CommitProperties(property_set.get(), 0, 0, nullptr);
434 #else
435 return true;
436 #endif
437 }
438
CommitGammaCorrection(const CrtcProperties & crtc_props)439 bool HardwareDisplayPlaneManagerAtomic::CommitGammaCorrection(
440 const CrtcProperties& crtc_props) {
441 DCHECK(crtc_props.degamma_lut.id || crtc_props.gamma_lut.id);
442 #if !defined(COMMIT_PROPERTIES_ON_PAGE_FLIP)
443 ScopedDrmAtomicReqPtr property_set(drmModeAtomicAlloc());
444 if (crtc_props.degamma_lut.id) {
445 int ret = drmModeAtomicAddProperty(property_set.get(), crtc_props.id,
446 crtc_props.degamma_lut.id,
447 crtc_props.degamma_lut.value);
448 if (ret < 0) {
449 LOG(ERROR) << "Failed to set DEGAMMA_LUT property for crtc="
450 << crtc_props.id;
451 return false;
452 }
453 }
454
455 if (crtc_props.gamma_lut.id) {
456 int ret = drmModeAtomicAddProperty(property_set.get(), crtc_props.id,
457 crtc_props.gamma_lut.id,
458 crtc_props.gamma_lut.value);
459 if (ret < 0) {
460 LOG(ERROR) << "Failed to set GAMMA_LUT property for crtc="
461 << crtc_props.id;
462 return false;
463 }
464 }
465
466 // If we try to do this in a non-blocking fashion this can return EBUSY since
467 // there is a pending page flip. Do a blocking commit (the same as the legacy
468 // API) to ensure the properties are applied.
469 // TODO(dnicoara): Should cache these values locally and aggregate them with
470 // the page flip event otherwise this "steals" a vsync to apply the property.
471 return drm_->CommitProperties(property_set.get(), 0, 0, nullptr);
472 #else
473 return true;
474 #endif
475 }
476
AddOutFencePtrProperties(drmModeAtomicReq * property_set,const std::vector<uint32_t> & crtcs,std::vector<base::ScopedFD> * out_fence_fds,std::vector<base::ScopedFD::Receiver> * out_fence_fd_receivers)477 bool HardwareDisplayPlaneManagerAtomic::AddOutFencePtrProperties(
478 drmModeAtomicReq* property_set,
479 const std::vector<uint32_t>& crtcs,
480 std::vector<base::ScopedFD>* out_fence_fds,
481 std::vector<base::ScopedFD::Receiver>* out_fence_fd_receivers) {
482 // Reserve space in vector to ensure no reallocation will take place
483 // and thus all pointers to elements will remain valid
484 DCHECK(out_fence_fds->empty());
485 DCHECK(out_fence_fd_receivers->empty());
486 out_fence_fds->reserve(crtcs.size());
487 out_fence_fd_receivers->reserve(crtcs.size());
488
489 for (uint32_t crtc : crtcs) {
490 const auto crtc_index = LookupCrtcIndex(crtc);
491 DCHECK_GE(crtc_index, 0);
492 const auto out_fence_ptr_id =
493 crtc_state_[crtc_index].properties.out_fence_ptr.id;
494
495 if (out_fence_ptr_id > 0) {
496 out_fence_fds->push_back(base::ScopedFD());
497 out_fence_fd_receivers->emplace_back(out_fence_fds->back());
498 // Add the OUT_FENCE_PTR property pointing to the memory location
499 // to save the out-fence fd into for this crtc. Note that
500 // the out-fence fd is produced only after we perform the atomic
501 // commit, so we need to ensure that the pointer remains valid
502 // until then.
503 int ret = drmModeAtomicAddProperty(
504 property_set, crtc, out_fence_ptr_id,
505 reinterpret_cast<uint64_t>(out_fence_fd_receivers->back().get()));
506 if (ret < 0) {
507 LOG(ERROR) << "Failed to set OUT_FENCE_PTR property for crtc=" << crtc
508 << " error=" << -ret;
509 out_fence_fd_receivers->pop_back();
510 out_fence_fds->pop_back();
511 return false;
512 }
513 }
514 }
515
516 return true;
517 }
518
519 } // namespace ui
520