Shared state is protected by a combination of the local mutex_ and the global lock. This change attempts to document and enforce that more clearly using clang-tidy thread annotations. Change unique_lock usage to lock_guard for cases that do not require release of the mutex such as when using a condition variable. Temporarily add a main_lock_ mutex which mirrors how the global lock is being used for synchronization. Lock this at the entry point to all public methods. This lock will be removed as a part of subsequent refactoring and it used to more cleanly break up the following changes in this series. Some state is not properly protected by any lock, so temporarily disable the warnings around access to that state until the synchronization is resolved in subsequent changes. Change-Id: I6f4f30c4e97f266146b0879c5255d6f2432f12a3
406 lines
12 KiB
C++
406 lines
12 KiB
C++
/*
|
|
* Copyright (C) 2015 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#undef NDEBUG /* Required for assert to work */
|
|
|
|
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
|
|
#define LOG_TAG "drmhwc"
|
|
|
|
#include "DrmAtomicStateManager.h"
|
|
|
|
#include <drm/drm_mode.h>
|
|
#include <sync/sync.h>
|
|
#include <utils/Trace.h>
|
|
|
|
#include <cassert>
|
|
|
|
#include "drm/DrmCrtc.h"
|
|
#include "drm/DrmDevice.h"
|
|
#include "drm/DrmPlane.h"
|
|
#include "drm/DrmUnique.h"
|
|
#include "utils/log.h"
|
|
|
|
namespace android {
|
|
|
|
auto DrmAtomicStateManager::CreateInstance(DrmDisplayPipeline *pipe)
|
|
-> std::shared_ptr<DrmAtomicStateManager> {
|
|
auto dasm = std::shared_ptr<DrmAtomicStateManager>(
|
|
new DrmAtomicStateManager());
|
|
|
|
dasm->pipe_ = pipe;
|
|
std::thread(&DrmAtomicStateManager::ThreadFn, dasm.get(), dasm).detach();
|
|
|
|
return dasm;
|
|
}
|
|
|
|
// NOLINTNEXTLINE (readability-function-cognitive-complexity): Fixme
|
|
auto DrmAtomicStateManager::CommitFrame(AtomicCommitArgs &args) -> int {
|
|
// NOLINTNEXTLINE(misc-const-correctness)
|
|
ATRACE_CALL();
|
|
|
|
if (args.active && *args.active == active_frame_state_.crtc_active_state) {
|
|
/* Don't set the same state twice */
|
|
args.active.reset();
|
|
}
|
|
|
|
if (!args.HasInputs()) {
|
|
/* nothing to do */
|
|
return 0;
|
|
}
|
|
|
|
if (!active_frame_state_.crtc_active_state) {
|
|
/* Force activate display */
|
|
args.active = true;
|
|
}
|
|
|
|
auto new_frame_state = NewFrameState();
|
|
|
|
auto *crtc = pipe_->crtc->Get();
|
|
|
|
auto pset = MakeDrmModeAtomicReqUnique();
|
|
if (!pset) {
|
|
ALOGE("Failed to allocate property set");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
int wb_fence = -1;
|
|
|
|
if (pipe_->writeback_connector && args.writeback_fb) {
|
|
if (!pipe_->writeback_connector->Get()
|
|
->GetCrtcIdProperty()
|
|
.AtomicSet(*pset, crtc->GetId())) {
|
|
ALOGE("DrmAtomicStateManager: Failed to set writeback CRTC_ID property");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!pipe_->writeback_connector->Get()
|
|
->GetWritebackFbIdProperty()
|
|
.AtomicSet(*pset, args.writeback_fb->GetFbId())) {
|
|
ALOGE("DrmAtomicStateManager: Failed to set writeback FB_ID property");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!pipe_->writeback_connector->Get()
|
|
->GetWritebackOutFenceProperty()
|
|
.AtomicSet(*pset, uint64_t(&wb_fence))) {
|
|
ALOGE(
|
|
"DrmAtomicStateManager: Failed to set writeback OUT_FENCE_PTR "
|
|
"property");
|
|
return -EINVAL;
|
|
}
|
|
|
|
// Wait on input fence if provided
|
|
if (args.writeback_release_fence) {
|
|
sync_wait(*args.writeback_release_fence, -1);
|
|
args.writeback_release_fence.reset();
|
|
}
|
|
}
|
|
|
|
int out_fence = -1;
|
|
if (!crtc->GetOutFencePtrProperty().AtomicSet(*pset, uint64_t(&out_fence))) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
bool nonblock = !args.blocking;
|
|
auto *connector = pipe_->connector->Get();
|
|
if (args.active) {
|
|
nonblock = false;
|
|
new_frame_state.crtc_active_state = *args.active;
|
|
if (!crtc->GetActiveProperty().AtomicSet(*pset, *args.active ? 1 : 0) ||
|
|
!connector->GetCrtcIdProperty().AtomicSet(*pset, crtc->GetId())) {
|
|
return -EINVAL;
|
|
}
|
|
if (!*args.active && args.teardown) {
|
|
if (!connector->GetCrtcIdProperty().AtomicSet(*pset, 0) ||
|
|
!crtc->GetModeProperty().AtomicSet(*pset, 0)) {
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
}
|
|
|
|
auto *drm = pipe_->device;
|
|
if (args.display_mode) {
|
|
new_frame_state.mode_blob = args.display_mode.value().CreateModeBlob(*drm);
|
|
|
|
if (!new_frame_state.mode_blob) {
|
|
ALOGE("Failed to create mode_blob");
|
|
return -EINVAL;
|
|
}
|
|
|
|
auto raw_mode = args.display_mode.value().GetRawMode();
|
|
whole_display_rect_.i_rect = {0, 0, raw_mode.hdisplay, raw_mode.vdisplay};
|
|
|
|
if (!crtc->GetModeProperty().AtomicSet(*pset, *new_frame_state.mode_blob)) {
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
if (args.color_matrix && crtc->GetCtmProperty()) {
|
|
auto blob = drm->RegisterUserPropertyBlob(args.color_matrix.get(),
|
|
sizeof(drm_color_ctm));
|
|
new_frame_state.ctm_blob = std::move(blob);
|
|
|
|
if (!new_frame_state.ctm_blob) {
|
|
ALOGE("Failed to create CTM blob");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!crtc->GetCtmProperty().AtomicSet(*pset, *new_frame_state.ctm_blob))
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (args.colorspace && connector->GetColorspaceProperty()) {
|
|
if (!connector->GetColorspaceProperty()
|
|
.AtomicSet(*pset, connector->GetColorspacePropertyValue(
|
|
*args.colorspace)))
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (args.content_type && connector->GetContentTypeProperty()) {
|
|
if (!connector->GetContentTypeProperty().AtomicSet(*pset,
|
|
static_cast<uint64_t>(
|
|
*args.content_type)))
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (args.hdr_metadata && connector->GetHdrOutputMetadataProperty()) {
|
|
auto blob = drm->RegisterUserPropertyBlob(args.hdr_metadata.get(),
|
|
sizeof(hdr_output_metadata));
|
|
new_frame_state.hdr_metadata_blob = std::move(blob);
|
|
if (!new_frame_state.hdr_metadata_blob) {
|
|
ALOGE("Failed to create %s blob",
|
|
connector->GetHdrOutputMetadataProperty().GetName().c_str());
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!connector->GetHdrOutputMetadataProperty()
|
|
.AtomicSet(*pset, *new_frame_state.hdr_metadata_blob))
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (args.min_bpc && connector->GetMinBpcProperty()) {
|
|
int err = 0;
|
|
uint64_t range_min = 0;
|
|
uint64_t range_max = 0;
|
|
std::tie(err, range_min) = connector->GetMinBpcProperty().RangeMin();
|
|
if (err != 0)
|
|
return err;
|
|
std::tie(err, range_max) = connector->GetMinBpcProperty().RangeMax();
|
|
if (err != 0)
|
|
return err;
|
|
|
|
// Adjust requested min bpc to be within the property range
|
|
int32_t min_bpc_val = std::max(args.min_bpc.value(),
|
|
static_cast<int32_t>(range_min));
|
|
min_bpc_val = std::min(min_bpc_val, static_cast<int32_t>(range_max));
|
|
if (!connector->GetMinBpcProperty().AtomicSet(*pset, min_bpc_val))
|
|
return -EINVAL;
|
|
}
|
|
|
|
auto unused_planes = new_frame_state.used_planes;
|
|
|
|
if (args.composition) {
|
|
new_frame_state.used_planes.clear();
|
|
|
|
for (auto &joining : args.composition->plan) {
|
|
DrmPlane *plane = joining.plane->Get();
|
|
LayerData &layer = joining.layer;
|
|
|
|
new_frame_state.used_framebuffers.emplace_back(layer.fb);
|
|
new_frame_state.used_planes.emplace_back(joining.plane);
|
|
|
|
/* Remove from 'unused' list, since plane is re-used */
|
|
auto &v = unused_planes;
|
|
v.erase(std::remove(v.begin(), v.end(), joining.plane), v.end());
|
|
|
|
DrmModeUserPropertyBlobUnique damage_blob;
|
|
if (plane->AtomicSetState(*pset, layer, joining.z_pos, crtc->GetId(),
|
|
whole_display_rect_, damage_blob) != 0) {
|
|
return -EINVAL;
|
|
}
|
|
new_frame_state.damage_blobs.push_back(std::move(damage_blob));
|
|
}
|
|
}
|
|
|
|
if (args.composition) {
|
|
for (auto &plane : unused_planes) {
|
|
if (plane->Get()->AtomicDisablePlane(*pset) != 0) {
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t flags = DRM_MODE_ATOMIC_ALLOW_MODESET;
|
|
const int error_buf_max_size = 64;
|
|
char err_buf[error_buf_max_size];
|
|
|
|
if (args.test_only) {
|
|
auto err = drmModeAtomicCommit(*drm->GetFd(), pset.get(),
|
|
flags | DRM_MODE_ATOMIC_TEST_ONLY, drm);
|
|
|
|
ALOGE_IF(err != 0, "Test-only ret=%d errno=%d strerror=%s\n", err, errno,
|
|
strerror_r(errno, err_buf, error_buf_max_size));
|
|
return err;
|
|
}
|
|
|
|
if (last_present_fence_) {
|
|
// NOLINTNEXTLINE(misc-const-correctness)
|
|
ATRACE_NAME("WaitPriorFramePresented");
|
|
|
|
constexpr int kTimeoutMs = 500;
|
|
const int err = sync_wait(*last_present_fence_, kTimeoutMs);
|
|
if (err != 0) {
|
|
ALOGE("sync_wait(fd=%i) returned: %i (errno: %i)", *last_present_fence_,
|
|
err, errno);
|
|
}
|
|
|
|
CleanupPriorFrameResources();
|
|
}
|
|
|
|
if (nonblock) {
|
|
flags |= DRM_MODE_ATOMIC_NONBLOCK;
|
|
}
|
|
|
|
auto err = drmModeAtomicCommit(*drm->GetFd(), pset.get(), flags, drm);
|
|
if (err != 0) {
|
|
ALOGE("Failed to commit pset ret=%d errno=%d strerror=%s\n", err, errno,
|
|
strerror_r(errno, err_buf, error_buf_max_size));
|
|
return err;
|
|
}
|
|
|
|
args.out_fence = MakeSharedFd(out_fence);
|
|
|
|
// Store the writeback fence if this operation used a writeback connector
|
|
if (pipe_->writeback_connector && args.writeback_fb) {
|
|
args.out_writeback_complete_fence = MakeSharedFd(wb_fence);
|
|
}
|
|
|
|
if (nonblock) {
|
|
{
|
|
const std::lock_guard lock(mutex_);
|
|
last_present_fence_ = args.out_fence;
|
|
staged_frame_state_ = std::move(new_frame_state);
|
|
frames_staged_++;
|
|
}
|
|
cv_.notify_all();
|
|
} else {
|
|
active_frame_state_ = std::move(new_frame_state);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void DrmAtomicStateManager::ThreadFn(
|
|
const std::shared_ptr<DrmAtomicStateManager> &dasm) {
|
|
int tracking_at_the_moment = -1;
|
|
|
|
for (;;) {
|
|
SharedFd present_fence;
|
|
|
|
{
|
|
std::unique_lock lk(mutex_);
|
|
base::ScopedLockAssertion lock_assertion(mutex_);
|
|
cv_.wait(lk);
|
|
|
|
if (exit_thread_ || dasm.use_count() == 1)
|
|
break;
|
|
|
|
// Non-thread safe access to frames_staged_ and last_present_fence_;
|
|
// Main thread writes to these without acquiring mutex_;
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wthread-safety-analysis"
|
|
if (frames_staged_ <= tracking_at_the_moment)
|
|
continue;
|
|
|
|
tracking_at_the_moment = frames_staged_;
|
|
|
|
present_fence = last_present_fence_;
|
|
#pragma clang diagnostic pop
|
|
if (!present_fence)
|
|
continue;
|
|
}
|
|
|
|
{
|
|
// NOLINTNEXTLINE(misc-const-correctness)
|
|
ATRACE_NAME("AsyncWaitForBuffersSwap");
|
|
constexpr int kTimeoutMs = 500;
|
|
auto err = sync_wait(*present_fence, kTimeoutMs);
|
|
if (err != 0) {
|
|
ALOGE("sync_wait(fd=%i) returned: %i (errno: %i)", *present_fence, err,
|
|
errno);
|
|
}
|
|
}
|
|
|
|
{
|
|
const std::lock_guard main_lock(main_mutex_);
|
|
const std::lock_guard lk(mutex_);
|
|
if (exit_thread_)
|
|
break;
|
|
|
|
/* If resources is already cleaned-up by main thread, skip */
|
|
if (tracking_at_the_moment > frames_tracked_)
|
|
CleanupPriorFrameResources();
|
|
}
|
|
}
|
|
|
|
ALOGI("DrmAtomicStateManager thread exit");
|
|
}
|
|
|
|
void DrmAtomicStateManager::CleanupPriorFrameResources() {
|
|
assert(frames_staged_ - frames_tracked_ == 1);
|
|
assert(last_present_fence_);
|
|
|
|
// NOLINTNEXTLINE(misc-const-correctness)
|
|
ATRACE_NAME("CleanupPriorFrameResources");
|
|
frames_tracked_++;
|
|
active_frame_state_ = std::move(staged_frame_state_);
|
|
last_present_fence_ = {};
|
|
}
|
|
|
|
auto DrmAtomicStateManager::ExecuteAtomicCommit(AtomicCommitArgs &args) -> int {
|
|
std::lock_guard lock(main_mutex_);
|
|
|
|
auto err = CommitFrame(args);
|
|
|
|
if (!args.test_only) {
|
|
if (err != 0) {
|
|
ALOGE("Composite failed for pipeline %s",
|
|
pipe_->connector->Get()->GetName().c_str());
|
|
// Disable the hw used by the last active composition. This allows us to
|
|
// signal the release fences from that composition to avoid hanging.
|
|
AtomicCommitArgs cl_args{};
|
|
cl_args.composition = std::make_shared<DrmKmsPlan>();
|
|
if (CommitFrame(cl_args) != 0) {
|
|
ALOGE("Failed to clean-up active composition for pipeline %s",
|
|
pipe_->connector->Get()->GetName().c_str());
|
|
}
|
|
return err;
|
|
}
|
|
}
|
|
|
|
return err;
|
|
} // namespace android
|
|
|
|
auto DrmAtomicStateManager::ActivateDisplayUsingDPMS() -> int {
|
|
return drmModeConnectorSetProperty(*pipe_->device->GetFd(),
|
|
pipe_->connector->Get()->GetId(),
|
|
pipe_->connector->Get()
|
|
->GetDpmsProperty()
|
|
.GetId(),
|
|
DRM_MODE_DPMS_ON);
|
|
}
|
|
|
|
} // namespace android
|