chromium/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_legacy.cc

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "ui/ozone/platform/drm/gpu/hardware_display_plane_manager_legacy.h"

#include <errno.h>
#include <sync/sync.h>

#include <memory>
#include <utility>

#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
#include "base/task/thread_pool.h"
#include "ui/gfx/gpu_fence.h"
#include "ui/gfx/presentation_feedback.h"
#include "ui/ozone/platform/drm/gpu/crtc_controller.h"
#include "ui/ozone/platform/drm/gpu/drm_device.h"
#include "ui/ozone/platform/drm/gpu/drm_framebuffer.h"
#include "ui/ozone/platform/drm/gpu/hardware_display_plane.h"
#include "ui/ozone/platform/drm/gpu/page_flip_request.h"

namespace ui {

namespace {

// We currently wait for the fences serially, but it's possible
// that merging the fences and waiting on the merged fence fd
// is more efficient. We should revisit once we have more info.
DrmOverlayPlaneList WaitForPlaneFences(DrmOverlayPlaneList planes) {
  for (const auto& plane : planes) {
    if (plane.gpu_fence)
      plane.gpu_fence->Wait();
  }
  return planes;
}

bool CommitPendingCrtcProperty(
    DrmDevice* device,
    uint32_t crtc_id,
    DrmWrapper::Property& prop,
    std::optional<ScopedDrmPropertyBlob>& pending_blob) {
  if (!pending_blob.has_value()) {
    return true;
  }
  ScopedDrmPropertyBlob blob = std::move(pending_blob.value());
  pending_blob = std::nullopt;
  if (!prop.id) {
    return true;
  }

  prop.value = blob ? blob->id() : 0;
  int ret = device->SetObjectProperty(crtc_id, DRM_MODE_OBJECT_CRTC, prop.id,
                                      prop.value);
  if (ret < 0) {
    return false;
  }
  return true;
}

}  // namespace

HardwareDisplayPlaneManagerLegacy::HardwareDisplayPlaneManagerLegacy(
    DrmDevice* drm)
    : HardwareDisplayPlaneManager(drm) {}

HardwareDisplayPlaneManagerLegacy::~HardwareDisplayPlaneManagerLegacy() =
    default;

bool HardwareDisplayPlaneManagerLegacy::Commit(CommitRequest commit_request,
                                               uint32_t flags) {
  if (flags & DRM_MODE_ATOMIC_TEST_ONLY)
    // Legacy DRM does not support testing.
    return true;

  bool status = true;
  for (const auto& crtc_request : commit_request) {
    if (crtc_request.should_enable_crtc()) {
      // Overlays are not supported in legacy hence why we're only looking at
      // the primary plane.
      uint32_t fb_id = DrmOverlayPlane::GetPrimaryPlane(crtc_request.overlays())
                           ->buffer->opaque_framebuffer_id();
      status &=
          drm_->SetCrtc(crtc_request.crtc_id(), fb_id,
                        std::vector<uint32_t>(1, crtc_request.connector_id()),
                        crtc_request.mode());
    } else {
      drm_->DisableCrtc(crtc_request.crtc_id());
    }
  }

  if (status)
    UpdateCrtcAndPlaneStatesAfterModeset(commit_request);

  return status;
}

bool HardwareDisplayPlaneManagerLegacy::Commit(
    HardwareDisplayPlaneList* plane_list,
    scoped_refptr<PageFlipRequest> page_flip_request,
    gfx::GpuFenceHandle* release_fence) {
  bool test_only = !page_flip_request;
  if (test_only) {
    for (HardwareDisplayPlane* plane : plane_list->plane_list) {
      plane->set_in_use(false);
    }
    plane_list->plane_list.clear();
    plane_list->legacy_page_flips.clear();
    return true;
  }
  if (plane_list->plane_list.empty())  // No assigned planes, nothing to do.
    return true;

  bool ret = true;
  for (const auto& flip : plane_list->legacy_page_flips) {
    if (!drm_->PageFlip(flip.crtc_id, flip.framebuffer, page_flip_request)) {
      // 1) Permission Denied is a legitimate error.
      // 2) EBUSY or ENODEV are possible if we're page flipping a disconnected
      // CRTC. Pretend we're fine since a hotplug event is supposed to be on
      // its way.
      // NOTE: We could be getting EBUSY if we're trying to page flip a CRTC
      // that has a pending page flip, however the contract is that the caller
      // will never attempt this (since the caller should be waiting for the
      // page flip completion message).
      if (errno != EACCES && errno != EBUSY && errno != ENODEV) {
        PLOG(ERROR) << "Cannot page flip: crtc=" << flip.crtc_id
                    << " framebuffer=" << flip.framebuffer;
        ret = false;
      }
    }
  }

  if (ret) {
    plane_list->plane_list.swap(plane_list->old_plane_list);
    plane_list->plane_list.clear();
    plane_list->legacy_page_flips.clear();
  } else {
    ResetCurrentPlaneList(plane_list);
  }

  return ret;
}

bool HardwareDisplayPlaneManagerLegacy::TestSeamlessMode(
    int32_t crtc_id,
    const drmModeModeInfo& mode) {
  return false;
}

bool HardwareDisplayPlaneManagerLegacy::DisableOverlayPlanes(
    HardwareDisplayPlaneList* plane_list) {
  // We're never going to ship legacy pageflip with overlays enabled.
  DCHECK(!base::Contains(plane_list->old_plane_list,
                         static_cast<uint32_t>(DRM_PLANE_TYPE_OVERLAY),
                         &HardwareDisplayPlane::type));
  return true;
}

bool HardwareDisplayPlaneManagerLegacy::ValidatePrimarySize(
    const DrmOverlayPlane& primary,
    const drmModeModeInfo& mode) {
  DCHECK(primary.buffer.get());

  return primary.buffer->size() == gfx::Size(mode.hdisplay, mode.vdisplay);
}

void HardwareDisplayPlaneManagerLegacy::RequestPlanesReadyCallback(
    DrmOverlayPlaneList planes,
    base::OnceCallback<void(DrmOverlayPlaneList planes)> callback) {
  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE,
      {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
      base::BindOnce(&WaitForPlaneFences, std::move(planes)),
      std::move(callback));
}

bool HardwareDisplayPlaneManagerLegacy::InitializePlanes() {
  ScopedDrmPlaneResPtr plane_resources = drm_->GetPlaneResources();
  if (!plane_resources) {
    PLOG(ERROR) << "Failed to get plane resources.";
    return false;
  }

  for (uint32_t i = 0; i < plane_resources->count_planes; ++i) {
    std::unique_ptr<HardwareDisplayPlane> plane(
        CreatePlane(plane_resources->planes[i]));

    if (!plane->Initialize(drm_))
      continue;

    // Overlays are not supported on the legacy path, so ignore all overlay
    // planes.
    if (plane->type() == DRM_PLANE_TYPE_OVERLAY)
      continue;

    planes_.push_back(std::move(plane));
  }

  return true;
}

bool HardwareDisplayPlaneManagerLegacy::SetPlaneData(
    HardwareDisplayPlaneList* plane_list,
    HardwareDisplayPlane* hw_plane,
    const DrmOverlayPlane& overlay,
    uint32_t crtc_id,
    const gfx::Rect& src_rect) {
  // Legacy modesetting rejects transforms.
  if (overlay.plane_transform != gfx::OVERLAY_TRANSFORM_NONE)
    return false;

  if (plane_list->legacy_page_flips.empty() ||
      plane_list->legacy_page_flips.back().crtc_id != crtc_id) {
    plane_list->legacy_page_flips.emplace_back(
        crtc_id, overlay.buffer->opaque_framebuffer_id());
  } else {
    return false;
  }

  return true;
}

bool HardwareDisplayPlaneManagerLegacy::IsCompatible(
    HardwareDisplayPlane* plane,
    const DrmOverlayPlane& overlay,
    uint32_t crtc_id) const {
  if (plane->in_use() || plane->type() == DRM_PLANE_TYPE_CURSOR ||
      !plane->CanUseForCrtcId(crtc_id))
    return false;

  // When using legacy kms we always scanout only one plane (the primary),
  // and we always use the opaque fb. Refer to SetPlaneData above.
  const uint32_t format = overlay.buffer->opaque_framebuffer_pixel_format();
  return plane->IsSupportedFormat(format);
}

bool HardwareDisplayPlaneManagerLegacy::CommitPendingCrtcState(
    CrtcState& crtc_state) {
  CrtcProperties& crtc_props = crtc_state.properties;
  bool result = true;

  if (!CommitPendingCrtcProperty(drm_, crtc_props.id, crtc_props.ctm,
                                 crtc_state.pending_ctm_blob)) {
    LOG(ERROR) << "Failed to set CTM property for crtc=" << crtc_props.id;
    result = false;
  }
  if (!CommitPendingCrtcProperty(drm_, crtc_props.id, crtc_props.gamma_lut,
                                 crtc_state.pending_gamma_lut_blob)) {
    LOG(ERROR) << "Failed to set GAMMA_LUT property for crtc=" << crtc_props.id;
    result = false;
  }
  if (!CommitPendingCrtcProperty(drm_, crtc_props.id, crtc_props.degamma_lut,
                                 crtc_state.pending_degamma_lut_blob)) {
    LOG(ERROR) << "Failed to set DEGAMMA_LUT property for crtc="
               << crtc_props.id;
    result = false;
  }
  return result;
}

}  // namespace ui