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

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

#include "ui/ozone/platform/drm/gpu/page_flip_watchdog.h"
#include <cstdint>

#include "base/containers/ring_buffer.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/syslog_logging.h"

namespace ui {

PageFlipWatchdog::PageFlipWatchdog() = default;

PageFlipWatchdog::~PageFlipWatchdog() = default;

void PageFlipWatchdog::OnSuccessfulPageFlip() {
  page_flip_status_tracker_.SaveToBuffer(true);
}

void PageFlipWatchdog::CrashOnFailedPlaneAssignment() {
  page_flip_status_tracker_.SaveToBuffer(false);
  failed_page_flip_counter_++;

  // Wait until the log of recent page flips is full to avoid crashing
  // too early.
  if (page_flip_status_tracker_.CurrentIndex() <
      page_flip_status_tracker_.BufferSize())
    return;

  bool last_page_flip_status = true;
  uint32_t flakes = 0;
  uint32_t failures = 0;
  for (auto iter = page_flip_status_tracker_.Begin(); iter; ++iter) {
    bool page_flip_status = **iter;
    if (page_flip_status && !last_page_flip_status)
      flakes += 1;
    if (!page_flip_status)
      failures += 1;
    last_page_flip_status = page_flip_status;
  }

  if (flakes >= kPlaneAssignmentFlakeThreshold) {
    LOG(FATAL) << "Plane assignment has flaked " << flakes
               << " times, but the threshold is "
               << kPlaneAssignmentFlakeThreshold
               << ". Crashing the GPU process.";
  }

  if (failures >= kPlaneAssignmentMaximumFailures) {
    LOG(FATAL) << "Plane assignment has failed " << failures << "/"
               << page_flip_status_tracker_.BufferSize()
               << " times, but the threshold is "
               << kPlaneAssignmentMaximumFailures
               << ". Crashing the GPU process.";
  }
}

void PageFlipWatchdog::ArmForFailedCommit() {
  page_flip_status_tracker_.SaveToBuffer(false);
  failed_page_flip_counter_++;
  StartCrashGpuTimer();
}

void PageFlipWatchdog::Disarm() {
  failed_page_flip_counter_ = 0;
  page_flip_status_tracker_.Clear();

  if (crash_gpu_timer_.IsRunning()) {
    crash_gpu_timer_.AbandonAndStop();
    SYSLOG(INFO)
        << "Detected a modeset attempt after " << failed_page_flip_counter_
        << " failed page flips. Aborting GPU process self-destruct with "
        << crash_gpu_timer_.desired_run_time() - base::TimeTicks::Now()
        << " to spare.";
  }
}

void PageFlipWatchdog::StartCrashGpuTimer() {
  if (!crash_gpu_timer_.IsRunning()) {
    DCHECK_GE(failed_page_flip_counter_, 1);
    LOG(WARNING) << "Initiating GPU process self-destruct in "
                 << kWaitForModesetTimeout
                 << " unless a modeset attempt is detected.";

    crash_gpu_timer_.Start(
        FROM_HERE, kWaitForModesetTimeout, base::BindOnce([] {
          LOG(FATAL) << "Failed to modeset within " << kWaitForModesetTimeout
                     << " of the first page flip failure. Crashing GPU "
                        "process.";
        }));
  }
}

}  // namespace ui