chromium/ui/ozone/platform/drm/gpu/drm_device.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/drm_device.h"

#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <xf86drm.h>
#include <xf86drmMode.h>

#include <memory>
#include <utility>

#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/ranges/algorithm.h"
#include "base/task/current_thread.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/traced_value.h"
#include "third_party/perfetto/include/perfetto/tracing/traced_value.h"
#include "ui/gfx/linux/gbm_device.h"
#include "ui/ozone/platform/drm/gpu/hardware_display_plane.h"
#include "ui/ozone/platform/drm/gpu/hardware_display_plane_manager_atomic.h"
#include "ui/ozone/platform/drm/gpu/hardware_display_plane_manager_legacy.h"

namespace ui {

namespace {

using DrmEventHandler =
    base::RepeatingCallback<void(uint32_t /* frame */,
                                 base::TimeTicks /* timestamp */,
                                 uint64_t /* id */)>;

bool ProcessDrmEvent(int fd, const DrmEventHandler& callback) {
  char buffer[1024];
  int len = read(fd, buffer, sizeof(buffer));
  if (len == 0)
    return false;

  if (len < static_cast<int>(sizeof(drm_event))) {
    PLOG(ERROR) << "Failed to read DRM event";
    return false;
  }

  int idx = 0;
  while (idx < len) {
    DCHECK_LE(static_cast<int>(sizeof(drm_event)), len - idx);
    drm_event event;
    memcpy(&event, &buffer[idx], sizeof(event));
    switch (event.type) {
      case DRM_EVENT_FLIP_COMPLETE: {
        DCHECK_LE(static_cast<int>(sizeof(drm_event_vblank)), len - idx);
        drm_event_vblank vblank;
        memcpy(&vblank, &buffer[idx], sizeof(vblank));
        std::unique_ptr<base::trace_event::TracedValue> drm_data(
            new base::trace_event::TracedValue());
        drm_data->SetInteger("frame_count", 1);
        drm_data->SetInteger("vblank.tv_sec", vblank.tv_sec);
        drm_data->SetInteger("vblank.tv_usec", vblank.tv_usec);
        TRACE_EVENT_INSTANT1("benchmark,drm", "DrmEventFlipComplete",
                             TRACE_EVENT_SCOPE_THREAD, "data",
                             std::move(drm_data));
        // Warning: It is generally unsafe to manufacture TimeTicks values; but
        // here it is required for interfacing with libdrm. Assumption: libdrm
        // is providing the timestamp from the CLOCK_MONOTONIC POSIX clock.
        DCHECK_EQ(base::TimeTicks::GetClock(),
                  base::TimeTicks::Clock::LINUX_CLOCK_MONOTONIC);
        const base::TimeTicks timestamp =
            base::TimeTicks() +
            base::Microseconds(static_cast<int64_t>(vblank.tv_sec) *
                                   base::Time::kMicrosecondsPerSecond +
                               vblank.tv_usec);
        callback.Run(vblank.sequence, timestamp, vblank.user_data);
      } break;
      case DRM_EVENT_VBLANK:
        break;
      default:
        NOTREACHED_IN_MIGRATION();
        break;
    }

    idx += event.length;
  }

  return true;
}

}  // namespace

class DrmDevice::PageFlipManager {
 public:
  PageFlipManager() = default;

  PageFlipManager(const PageFlipManager&) = delete;
  PageFlipManager& operator=(const PageFlipManager&) = delete;

  ~PageFlipManager() = default;

  void OnPageFlip(uint32_t frame, base::TimeTicks timestamp, uint64_t id) {
    auto it = base::ranges::find(callbacks_, id, &PageFlip::id);
    if (it == callbacks_.end()) {
      LOG(WARNING) << "Could not find callback for page flip id=" << id;
      return;
    }

    it->pending_calls--;
    if (it->pending_calls)
      return;

    DrmDevice::PageFlipCallback callback = std::move(it->callback);
    callbacks_.erase(it);
    std::move(callback).Run(frame, timestamp);
  }

  uint64_t GetNextId() { return next_id_++; }

  void RegisterCallback(uint64_t id,
                        uint64_t pending_calls,
                        DrmDevice::PageFlipCallback callback) {
    callbacks_.push_back(
        {id, static_cast<uint32_t>(pending_calls), std::move(callback)});
  }

 private:
  struct PageFlip {
    uint64_t id;
    uint32_t pending_calls;
    DrmDevice::PageFlipCallback callback;
  };

  uint64_t next_id_ = 0;

  std::vector<PageFlip> callbacks_;
};

class DrmDevice::IOWatcher : public base::MessagePumpEpoll::FdWatcher {
 public:
  IOWatcher(int fd, DrmDevice::PageFlipManager* page_flip_manager)
      : page_flip_manager_(page_flip_manager), controller_(FROM_HERE), fd_(fd) {
    Register();
  }

  IOWatcher(const IOWatcher&) = delete;
  IOWatcher& operator=(const IOWatcher&) = delete;

  ~IOWatcher() override { Unregister(); }

 private:
  void Register() {
    DCHECK(base::CurrentIOThread::IsSet());
    base::CurrentIOThread::Get()->WatchFileDescriptor(
        fd_, true, base::MessagePumpForIO::WATCH_READ, &controller_, this);
  }

  void Unregister() {
    DCHECK(base::CurrentIOThread::IsSet());
    controller_.StopWatchingFileDescriptor();
  }

  // base::MessagePumpEpoll::FdWatcher overrides:
  void OnFileCanReadWithoutBlocking(int fd) override {
    DCHECK(base::CurrentIOThread::IsSet());
    TRACE_EVENT1("drm", "OnDrmEvent", "socket", fd);

    if (!ProcessDrmEvent(
            fd, base::BindRepeating(&DrmDevice::PageFlipManager::OnPageFlip,
                                    base::Unretained(page_flip_manager_))))
      Unregister();
  }

  void OnFileCanWriteWithoutBlocking(int fd) override {
    NOTREACHED_IN_MIGRATION();
  }

  raw_ptr<DrmDevice::PageFlipManager> page_flip_manager_;

  base::MessagePumpEpoll::FdWatchController controller_;

  int fd_;
};

DrmDevice::DrmDevice(const base::FilePath& device_path,
                     base::ScopedFD fd,
                     bool is_primary_device,
                     std::unique_ptr<GbmDevice> gbm)
    : DrmWrapper(device_path, std::move(fd), is_primary_device),
      page_flip_manager_(new PageFlipManager()),
      gbm_(std::move(gbm)) {}

DrmDevice::~DrmDevice() = default;

bool DrmDevice::Initialize() {
  if (!DrmWrapper::Initialize()) {
    return false;
  }

  // Use atomic only if kernel allows it.
  if (is_atomic()) {
    plane_manager_ = std::make_unique<HardwareDisplayPlaneManagerAtomic>(this);
  } else {
    plane_manager_ = std::make_unique<HardwareDisplayPlaneManagerLegacy>(this);
  }

  if (!plane_manager_->Initialize()) {
    LOG(ERROR) << "Failed to initialize the plane manager for "
               << device_path().value();
    plane_manager_.reset();
    return false;
  }

  watcher_ = std::make_unique<IOWatcher>(GetFd(), page_flip_manager_.get());

  return true;
}

bool DrmDevice::SetCrtc(uint32_t crtc_id,
                        uint32_t framebuffer,
                        std::vector<uint32_t> connectors,
                        const drmModeModeInfo& mode) {
  if (!DrmWrapper::SetCrtc(crtc_id, framebuffer, connectors, mode))
    return false;

  ++modeset_sequence_id_;
  return true;
}

bool DrmDevice::PageFlip(uint32_t crtc_id,
                         uint32_t framebuffer,
                         scoped_refptr<PageFlipRequest> page_flip_request) {
  const uint64_t id = page_flip_manager_->GetNextId();
  if (!DrmWrapper::PageFlip(crtc_id, framebuffer, id))
    return false;

  // If successful the payload will be removed by a PageFlip event.
  page_flip_manager_->RegisterCallback(id, 1, page_flip_request->AddPageFlip());
  return true;
}

bool DrmDevice::CommitProperties(
    drmModeAtomicReq* properties,
    uint32_t flags,
    uint32_t crtc_count,
    scoped_refptr<PageFlipRequest> page_flip_request) {
  uint64_t id = 0;
  if (page_flip_request) {
    flags |= DRM_MODE_PAGE_FLIP_EVENT;
    id = page_flip_manager_->GetNextId();
  }

  if (!DrmWrapper::CommitProperties(properties, flags, id))
    return false;

  if (page_flip_request) {
    page_flip_manager_->RegisterCallback(id, crtc_count,
                                         page_flip_request->AddPageFlip());
  }

  if (flags == DRM_MODE_ATOMIC_ALLOW_MODESET)
    ++modeset_sequence_id_;

  return true;
}

void DrmDevice::WriteIntoTrace(perfetto::TracedDictionary dict) const {
  dict.Add("planes", plane_manager_->planes());
  DrmWrapper::WriteIntoTrace(std::move(dict));
}

display::DrmFormatsAndModifiers DrmDevice::GetFormatsAndModifiersForCrtc(
    uint32_t crtc_id) const {
  display::DrmFormatsAndModifiers drm_formats_and_modifiers;
  for (uint32_t format : plane_manager_->GetSupportedFormats()) {
    std::vector<uint64_t> modifiers =
        plane_manager_->GetFormatModifiers(crtc_id, format);
    drm_formats_and_modifiers.emplace(format, modifiers);
  }
  return drm_formats_and_modifiers;
}

int DrmDevice::modeset_sequence_id() const { return modeset_sequence_id_; }

}  // namespace ui