// 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.
#include "ui/ozone/platform/drm/gpu/drm_gpu_display_manager.h"
#include <stddef.h>
#include <cstring>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/containers/contains.h"
#include "base/containers/flat_map.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/ranges/algorithm.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/trace_event/trace_event.h"
#include "ui/display/display_features.h"
#include "ui/display/types/display_configuration_params.h"
#include "ui/display/types/display_mode.h"
#include "ui/display/types/display_snapshot.h"
#include "ui/display/types/gamma_ramp_rgb_entry.h"
#include "ui/ozone/platform/drm/common/drm_util.h"
#include "ui/ozone/platform/drm/common/hardware_display_controller_info.h"
#include "ui/ozone/platform/drm/gpu/drm_device.h"
#include "ui/ozone/platform/drm/gpu/drm_device_manager.h"
#include "ui/ozone/platform/drm/gpu/drm_display.h"
#include "ui/ozone/platform/drm/gpu/drm_gpu_util.h"
#include "ui/ozone/platform/drm/gpu/hardware_display_controller.h"
#include "ui/ozone/platform/drm/gpu/screen_manager.h"
namespace ui {
namespace {
constexpr char kMultipleDisplayIdsCollisionDetected[] =
"Display.MultipleDisplays.GenerateId.CollisionDetection";
// A list of property names that are blocked from issuing a full display
// configuration (modeset) via a udev display CHANGE event.
const char* kBlockedEventsByTriggerProperty[] = {"Content Protection"};
struct DrmDisplayParams {
scoped_refptr<DrmDevice> drm;
std::unique_ptr<HardwareDisplayControllerInfo> display_info;
raw_ptr<display::DisplaySnapshot> snapshot;
};
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class TestOnlyModesetOutcome {
kSuccess = 0,
kFallbackSuccess = 1,
kFailure = 2,
kMaxValue = kFailure,
};
class DisplayComparator {
public:
explicit DisplayComparator(const DrmDisplay* display)
: drm_(display->drm()),
crtc_(display->GetPrimaryCrtcId()),
connector_(display->GetPrimaryConnectorId()) {
const std::optional<TileProperty> tile_property =
display->GetTileProperty();
tile_group_id_ = tile_property.has_value()
? std::optional<int>(tile_property->group_id)
: std::nullopt;
}
DisplayComparator(const scoped_refptr<DrmDevice>& drm,
uint32_t crtc,
uint32_t connector,
const std::optional<TileProperty> tile_property)
: drm_(drm), crtc_(crtc), connector_(connector) {
tile_group_id_ = tile_property.has_value()
? std::optional<int>(tile_property->group_id)
: std::nullopt;
}
bool operator()(const std::unique_ptr<DrmDisplay>& other) const {
const std::optional<TileProperty>& other_tile_property =
other->GetTileProperty();
std::optional<int> other_tile_group_id =
other_tile_property.has_value() ? other_tile_property->group_id
: std::optional<int>(std::nullopt);
return drm_ == other->drm() &&
connector_ == other->GetPrimaryConnectorId() &&
crtc_ == other->GetPrimaryCrtcId() &&
tile_group_id_ == other_tile_group_id;
}
private:
scoped_refptr<DrmDevice> drm_;
uint32_t crtc_;
uint32_t connector_;
std::optional<int> tile_group_id_;
};
bool MatchMode(const display::DisplayMode& display_mode,
const drmModeModeInfo& m) {
return display_mode.size() == ModeSize(m) &&
display_mode.refresh_rate() == ModeRefreshRate(m) &&
display_mode.is_interlaced() == ModeIsInterlaced(m);
}
// Finds all modes from |modes| that match the size and timing specified by
// |request_mode| and returns a vector of pointers thereto.
std::vector<const drmModeModeInfo*> FindMatchingModes(
const display::DisplayMode& request_mode,
const std::vector<drmModeModeInfo>& modes) {
std::vector<const drmModeModeInfo*> matches;
for (const drmModeModeInfo& m : modes) {
if (MatchMode(request_mode, m)) {
matches.push_back(&m);
}
}
return matches;
}
// Finds and returns the drm mode from |display| matching |request_mode| except
// with the closest (or greater) refresh rate, or nullptr if no such mode
// exists. If |is_seamless| is set, only modes which pass seamless verification
// will be considered.
const drmModeModeInfo* FindClosestModeWithGreaterRefreshRate(
const display::DisplayMode& request_mode,
const DrmDisplay& display,
bool is_seamless,
HardwareDisplayController* controller) {
if (!display.IsVrrCapable()) {
return nullptr;
}
if (is_seamless && !controller) {
LOG(ERROR) << "Could not find HardwareDisplayController for display_id: "
<< display.display_id()
<< " required to perform seamless verification.";
return nullptr;
}
const drmModeModeInfo* closest_mode = nullptr;
for (const auto& m : display.modes()) {
if (request_mode.size() != ModeSize(m) ||
request_mode.is_interlaced() != ModeIsInterlaced(m)) {
continue;
}
if (request_mode.refresh_rate() <
ModeVSyncRateMin(m, display.vsync_rate_min_from_edid())) {
continue;
}
if (is_seamless &&
!controller->TestSeamlessMode(display.GetPrimaryCrtcId(), m)) {
continue;
}
if (ModeRefreshRate(m) >= request_mode.refresh_rate() &&
(!closest_mode ||
ModeRefreshRate(m) < ModeRefreshRate(*closest_mode))) {
closest_mode = &m;
}
}
return closest_mode;
}
std::string GetEventPropertyByKey(const std::string& key,
const EventPropertyMap event_props) {
const auto it = event_props.find(key);
if (it == event_props.end())
return std::string();
return std::string(it->second);
}
ControllerConfigParams* FindConfigParamsForConnector(
std::vector<ControllerConfigParams>& config_list,
uint32_t connector_id) {
for (auto& config : config_list) {
if (config.connector == connector_id) {
return &config;
}
}
return nullptr;
}
std::string ConfigRequestToString(
const std::vector<display::DisplayConfigurationParams>& config_requests) {
std::string signature;
for (const auto& config : config_requests) {
if (config.id <= 0) {
LOG(WARNING) << __func__
<< ": potentially invalid display ID: " << config.id;
}
signature += base::NumberToString(config.id) + ":" +
config.origin.ToString() + ":" +
(config.mode ? config.mode->ToString() : "Disabled") + ":" +
(config.enable_vrr ? "vrr" : "no_vrr") + ";";
}
if (signature.empty()) {
LOG(WARNING) << __func__ << ": empty return value with request of size: "
<< config_requests.size();
}
return signature;
}
TestOnlyModesetOutcome GetTestOnlyModesetOutcome(
bool config_success,
bool did_test_modeset_with_fallback) {
if (!config_success) {
return TestOnlyModesetOutcome::kFailure;
}
return did_test_modeset_with_fallback
? TestOnlyModesetOutcome::kFallbackSuccess
: TestOnlyModesetOutcome::kSuccess;
}
std::string NumDisplaysToHistogramString(int num_displays) {
DCHECK(num_displays >= 0)
<< __func__ << ": " << num_displays << " displays detected.";
switch (num_displays) {
case 1:
return "OneDisplay";
case 2:
return "TwoDisplays";
case 3:
return "ThreeDisplays";
default:
return "FourOrMoreDisplays";
}
}
std::string GetNumFallbackHistogramName(int num_displays) {
return base::StrCat({"ConfigureDisplays.Modeset.Test.DynamicCRTCs.",
NumDisplaysToHistogramString(num_displays),
".PermutationsAttempted"});
}
std::string GetTestOnlyModesetOutcomeName(int num_displays) {
return base::StrCat({"ConfigureDisplays.Modeset.Test.",
NumDisplaysToHistogramString(num_displays), ".Outcome"});
}
} // namespace
DrmGpuDisplayManager::DrmGpuDisplayManager(ScreenManager* screen_manager,
DrmDeviceManager* drm_device_manager)
: screen_manager_(screen_manager),
drm_device_manager_(drm_device_manager) {}
DrmGpuDisplayManager::~DrmGpuDisplayManager() = default;
void DrmGpuDisplayManager::SetDisplaysConfiguredCallback(
base::RepeatingClosure callback) {
displays_configured_callback_ = std::move(callback);
}
MovableDisplaySnapshots DrmGpuDisplayManager::GetDisplays() {
successful_test_config_params_.clear();
std::vector<std::unique_ptr<DrmDisplay>> old_displays;
old_displays.swap(displays_);
std::vector<DrmDisplayParams> displays_to_create;
MovableDisplaySnapshots display_snapshots;
const DrmDeviceVector& devices = drm_device_manager_->GetDrmDevices();
size_t device_index = 0;
MapEdidIdToDisplaySnapshot edid_id_collision_map;
bool collision_detected = false;
for (const auto& drm : devices) {
if (device_index >= kMaxDrmCount) {
LOG(WARNING) << "Reached the current limit of " << kMaxDrmCount
<< " connected DRM devices. Ignoring the remaining "
<< devices.size() - kMaxDrmCount << " connected devices.";
break;
}
// Receiving a signal that DRM state was updated. Need to reset the plane
// manager's resource cache since IDs may have changed.
base::flat_set<uint32_t> valid_connector_ids =
drm->plane_manager()->ResetConnectorsCacheAndGetValidIds(
drm->GetResources());
// TODO: b/327011965 - Move assigning CRTCs to connectors from
// RefreshNativeDisplays() to before test modeset.
// Create new DisplaySnapshots and resolve display ID collisions.
auto display_infos = GetDisplayInfosAndUpdateCrtcs(*drm);
// Make sure that the display infos we got have valid connector IDs.
// If not, we need to remove the display info from the list. This removes
// any zombie connectors.
std::erase_if(
display_infos, [&valid_connector_ids](const auto& display_info) {
return !base::Contains(valid_connector_ids,
display_info->connector()->connector_id);
});
// Consolidate all display infos that belong to the same tiled display into
// one.
ConsolidateTiledDisplayInfo(display_infos);
for (auto& display_info : display_infos) {
display_snapshots.emplace_back(CreateDisplaySnapshot(
*drm, display_info.get(), static_cast<uint8_t>(device_index)));
display::DisplaySnapshot* current_display_snapshot =
display_snapshots.back().get();
const auto colliding_display_snapshot_iter = edid_id_collision_map.find(
current_display_snapshot->edid_display_id());
if (colliding_display_snapshot_iter != edid_id_collision_map.end()) {
collision_detected = true;
current_display_snapshot->AddIndexToDisplayId();
display::DisplaySnapshot* colliding_display_snapshot =
colliding_display_snapshot_iter->second;
colliding_display_snapshot->AddIndexToDisplayId();
edid_id_collision_map[colliding_display_snapshot->edid_display_id()] =
colliding_display_snapshot;
}
edid_id_collision_map[current_display_snapshot->edid_display_id()] =
current_display_snapshot;
// Ownership of |display_info| is handed over.
displays_to_create.push_back(
{drm, std::move(display_info), current_display_snapshot});
}
device_index++;
}
// Create a new DrmDisplay with each of the corresponding display info and
// display snapshot. Note: do not use |display_infos| beyond this point,
// since some of the objects' internal references will be surrendered.
for (const DrmDisplayParams& params : displays_to_create) {
// If the DrmDisplay was present previously, copy its origin to the
// corresponding DisplaySnapshot before creating a new DrmDisplay.
auto old_drm_display_it = base::ranges::find_if(
old_displays,
DisplayComparator(params.drm, params.display_info->crtc()->crtc_id,
params.display_info->connector()->connector_id,
params.display_info->tile_property()));
if (old_drm_display_it != old_displays.end()) {
params.snapshot->set_origin(old_drm_display_it->get()->origin());
old_displays.erase(old_drm_display_it);
}
displays_.emplace_back(std::make_unique<DrmDisplay>(
params.drm, params.display_info.get(), *params.snapshot));
}
const bool multiple_connected_displays = display_snapshots.size() > 1;
if (multiple_connected_displays) {
base::UmaHistogramBoolean(kMultipleDisplayIdsCollisionDetected,
collision_detected);
}
NotifyScreenManager(displays_, old_displays);
return display_snapshots;
}
bool DrmGpuDisplayManager::TakeDisplayControl() {
const DrmDeviceVector& devices = drm_device_manager_->GetDrmDevices();
bool status = true;
for (const auto& drm : devices) {
const bool set_master_status = drm->SetMaster();
if (!set_master_status) {
LOG(ERROR) << __func__ << "Drm set master failed for: " // nocheck
<< drm->device_path().value();
}
status &= set_master_status;
}
// Roll-back any successful operation.
if (!status) {
LOG(ERROR) << "Failed to take control of the display";
RelinquishDisplayControl();
}
return status;
}
void DrmGpuDisplayManager::RelinquishDisplayControl() {
const DrmDeviceVector& devices = drm_device_manager_->GetDrmDevices();
for (const auto& drm : devices) {
if (!drm->DropMaster()) {
LOG(ERROR) << __func__ << "Drm drop master failed for: " // nocheck
<< drm->device_path().value();
}
}
}
bool DrmGpuDisplayManager::ShouldDisplayEventTriggerConfiguration(
const EventPropertyMap& event_props) {
DCHECK(!event_props.empty());
const std::string event_seq_num =
GetEventPropertyByKey("SEQNUM", event_props);
std::string log_prefix =
"Display event CHANGE" +
(event_seq_num.empty() ? "" : "(SEQNUM:" + event_seq_num + ") ");
std::string trigger_prop_log;
const std::string event_dev_path =
GetEventPropertyByKey("DEVPATH", event_props);
const DrmDeviceVector& devices = drm_device_manager_->GetDrmDevices();
for (const auto& drm : devices) {
if (drm->device_path().value().find(event_dev_path) == std::string::npos)
continue;
// Get the connector's ID and convert it to an int.
const std::string connector_id_str =
GetEventPropertyByKey("CONNECTOR", event_props);
if (connector_id_str.empty()) {
break;
}
uint32_t connector_id;
{
const bool conversion_success =
base::StringToUint(connector_id_str, &connector_id);
DCHECK(conversion_success);
}
// Get the trigger property's ID and convert to an int.
const std::string trigger_prop_id_str =
GetEventPropertyByKey("PROPERTY", event_props);
if (trigger_prop_id_str.empty())
break;
uint32_t trigger_prop_id;
{
const bool conversion_success =
base::StringToUint(trigger_prop_id_str, &trigger_prop_id);
DCHECK(conversion_success);
}
ScopedDrmObjectPropertyPtr property_values(
drm->GetObjectProperties(connector_id, DRM_MODE_OBJECT_CONNECTOR));
DCHECK(property_values);
// Fetch the name of the property from the device.
ScopedDrmPropertyPtr drm_property(drm->GetProperty(trigger_prop_id));
DCHECK(drm_property);
const std::string enum_value =
GetEnumNameForProperty(*drm_property, *property_values);
DCHECK(!enum_value.empty());
trigger_prop_log =
"[CONNECTOR:" + connector_id_str +
"] trigger property: " + std::string(drm_property->name) + "=" +
enum_value + ", ";
for (const char* blocked_prop : kBlockedEventsByTriggerProperty) {
if (strcmp(drm_property->name, blocked_prop) == 0) {
VLOG(1) << log_prefix << trigger_prop_log
<< "resolution: blocked; display configuration task "
"rejected.";
return false;
}
}
}
VLOG(1) << log_prefix << trigger_prop_log
<< "resolution: allowed; display configuration task triggered.";
return true;
}
bool DrmGpuDisplayManager::ConfigureDisplays(
const std::vector<display::DisplayConfigurationParams>& config_requests,
display::ModesetFlags modeset_flags,
std::vector<display::DisplayConfigurationParams>& out_requests) {
const bool is_commit =
modeset_flags.Has(display::ModesetFlag::kCommitModeset);
const bool is_seamless =
modeset_flags.Has(display::ModesetFlag::kSeamlessModeset);
std::vector<ControllerConfigParams> controllers_to_configure;
if (is_commit) {
controllers_to_configure = GetLatestModesetTestConfig(config_requests);
}
out_requests.clear();
if (controllers_to_configure.empty()) {
for (const auto& request : config_requests) {
int64_t display_id = request.id;
DrmDisplay* display = FindDisplay(display_id);
if (!display) {
LOG(WARNING) << __func__ << ": there is no display with ID "
<< display_id;
out_requests = config_requests;
return false;
}
std::unique_ptr<drmModeModeInfo> found_mode = nullptr;
if (request.mode) {
found_mode = FindModeForDisplay(*request.mode, *display, is_seamless);
if (!found_mode) {
out_requests = config_requests;
return false;
}
// Populate |out_requests| with a new request which holds an updated
// DisplayMode that precisely matches the found drm mode.
const std::unique_ptr<display::DisplayMode> out_mode =
CreateDisplayMode(*found_mode, display->vsync_rate_min_from_edid());
out_requests.emplace_back(request.id, request.origin, out_mode.get(),
request.enable_vrr);
} else {
out_requests.emplace_back(request);
}
scoped_refptr<DrmDevice> drm = display->drm();
ControllerConfigParams params(
display->display_id(), drm, display->GetPrimaryCrtcId(),
display->GetPrimaryConnectorId(), request.origin,
std::move(found_mode), request.enable_vrr,
display->base_connector_id());
controllers_to_configure.push_back(std::move(params));
}
}
bool config_success = screen_manager_->ConfigureDisplayControllers(
controllers_to_configure, modeset_flags);
// Only attempt to fallback on using different CRTC-connector pairings if
// hardware mirroring is disabled as hardware mirroring has multiple
// connectors assigned to one CRTC, and the fallback assumes 1:1 pairing.
const bool should_try_test_fallback =
!is_commit && !config_success &&
!display::features::IsHardwareMirrorModeEnabled();
bool did_test_modeset_with_fallback = false;
if (should_try_test_fallback) {
did_test_modeset_with_fallback = true;
config_success = RetryTestConfigureDisplaysWithAlternateCrtcs(
config_requests, controllers_to_configure);
}
if (displays_configured_callback_)
displays_configured_callback_.Run();
if (is_commit) {
successful_test_config_params_.clear();
if (config_success) {
for (const auto& controller : controllers_to_configure) {
FindDisplay(controller.display_id)->SetOrigin(controller.origin);
}
}
} else {
const std::string test_modest_outcome_histogram =
GetTestOnlyModesetOutcomeName(config_requests.size());
const TestOnlyModesetOutcome test_modeset_outcome =
GetTestOnlyModesetOutcome(config_success,
did_test_modeset_with_fallback);
base::UmaHistogramEnumeration(test_modest_outcome_histogram,
test_modeset_outcome);
}
return config_success;
}
bool DrmGpuDisplayManager::SetHdcpKeyProp(int64_t display_id,
const std::string& key) {
DrmDisplay* display = FindDisplay(display_id);
if (!display) {
LOG(ERROR) << "SetHdcpKeyProp: There is no display with ID " << display_id;
return false;
}
return display->SetHdcpKeyProp(key);
}
bool DrmGpuDisplayManager::GetHDCPState(
int64_t display_id,
display::HDCPState* state,
display::ContentProtectionMethod* protection_method) {
DrmDisplay* display = FindDisplay(display_id);
if (!display) {
LOG(WARNING) << __func__ << ": there is no display with ID " << display_id;
return false;
}
return display->GetHDCPState(state, protection_method);
}
bool DrmGpuDisplayManager::SetHDCPState(
int64_t display_id,
display::HDCPState state,
display::ContentProtectionMethod protection_method) {
DrmDisplay* display = FindDisplay(display_id);
if (!display) {
LOG(WARNING) << __func__ << ": there is no display with ID " << display_id;
return false;
}
return display->SetHDCPState(state, protection_method);
}
void DrmGpuDisplayManager::SetColorTemperatureAdjustment(
int64_t display_id,
const display::ColorTemperatureAdjustment& cta) {
DrmDisplay* display = FindDisplay(display_id);
if (!display) {
LOG(WARNING) << __func__ << ": there is no display with ID " << display_id;
return;
}
display->SetColorTemperatureAdjustment(cta);
}
void DrmGpuDisplayManager::SetColorCalibration(
int64_t display_id,
const display::ColorCalibration& calibration) {
DrmDisplay* display = FindDisplay(display_id);
if (!display) {
LOG(WARNING) << __func__ << ": there is no display with ID " << display_id;
return;
}
display->SetColorCalibration(calibration);
}
void DrmGpuDisplayManager::SetGammaAdjustment(
int64_t display_id,
const display::GammaAdjustment& adjustment) {
DrmDisplay* display = FindDisplay(display_id);
if (!display) {
LOG(WARNING) << __func__ << ": there is no display with ID " << display_id;
return;
}
display->SetGammaAdjustment(adjustment);
}
void DrmGpuDisplayManager::SetBackgroundColor(int64_t display_id,
const uint64_t background_color) {
DrmDisplay* display = FindDisplay(display_id);
if (!display) {
LOG(WARNING) << __func__ << ": there is no display with ID" << display_id;
return;
}
display->SetBackgroundColor(background_color);
}
bool DrmGpuDisplayManager::SetPrivacyScreen(int64_t display_id, bool enabled) {
DrmDisplay* display = FindDisplay(display_id);
if (!display) {
LOG(WARNING) << __func__ << ": there is no display with ID " << display_id;
return false;
}
return display->SetPrivacyScreen(enabled);
}
std::optional<std::vector<float>> DrmGpuDisplayManager::GetSeamlessRefreshRates(
int64_t display_id) const {
TRACE_EVENT1("drm", "DrmGpuDisplayManager::GetSeamlessRefreshRates",
"display_id", display_id);
DrmDisplay* display = FindDisplay(display_id);
if (!display) {
LOG(WARNING) << __func__ << ": there is no display with ID " << display_id;
return std::nullopt;
}
HardwareDisplayController* controller = screen_manager_->GetDisplayController(
display->drm(), display->GetPrimaryCrtcId());
if (!controller) {
LOG(ERROR) << "Could not find HardwareDisplayController for display_id: "
<< display_id;
return std::nullopt;
}
// TODO: b/323362145: Support continuity logic.
const gfx::Size current_mode_size = controller->GetModeSize();
std::vector<float> rates;
for (const drmModeModeInfo& mode : display->modes()) {
if (ui::ModeSize(mode) != current_mode_size) {
continue;
}
// Do a test commit to check if this mode can be configured without
// a modeset.
if (controller->TestSeamlessMode(display->GetPrimaryCrtcId(), mode)) {
rates.push_back(ModeRefreshRate(mode));
}
}
return rates;
}
DrmDisplay* DrmGpuDisplayManager::FindDisplay(int64_t display_id) const {
for (const auto& display : displays_) {
if (display->display_id() == display_id)
return display.get();
}
return nullptr;
}
DrmDisplay* DrmGpuDisplayManager::FindDisplayByConnectorId(
uint32_t connector_id) const {
for (const auto& display : displays_) {
if (display->GetCrtcConnectorPairForConnectorId(connector_id) != nullptr) {
return display.get();
}
}
return nullptr;
}
void DrmGpuDisplayManager::NotifyScreenManager(
const std::vector<std::unique_ptr<DrmDisplay>>& new_displays,
const std::vector<std::unique_ptr<DrmDisplay>>& old_displays) const {
ScreenManager::CrtcsWithDrmList controllers_to_remove;
for (const auto& old_display : old_displays) {
if (base::ranges::none_of(new_displays,
DisplayComparator(old_display.get()))) {
for (const auto& crtc_connector_pair :
old_display->crtc_connector_pairs()) {
controllers_to_remove.emplace_back(crtc_connector_pair.crtc_id,
old_display->drm());
}
}
}
if (!controllers_to_remove.empty())
screen_manager_->RemoveDisplayControllers(controllers_to_remove);
for (const auto& new_display : new_displays) {
if (base::ranges::none_of(old_displays,
DisplayComparator(new_display.get()))) {
screen_manager_->AddDisplayController(
new_display->drm(), new_display->GetPrimaryCrtcId(),
new_display->GetPrimaryConnectorId());
}
}
}
// TODO: b/327015722 - Move test modeset fallback with alternate CRTCs
// from DrmGpuDisplayManager to ScreenManager.
// The kernel can sometimes silently reallocate the resources of one CRTC to
// another, making the other ineffective. One such case is when i915's Bigjoiner
// takes the underlying pipe of a secondary CRTC for high bandwidth displays
// (DP 2.1+). Attempting modeset with a stolen CRTC will result in failure. The
// only way for userspace to overcome a stolen CRTC is to dynamically assign
// other CRTC configurations via test modesets.
bool DrmGpuDisplayManager::RetryTestConfigureDisplaysWithAlternateCrtcs(
const std::vector<display::DisplayConfigurationParams>& config_requests,
const std::vector<ControllerConfigParams>& controllers_to_configure) {
// Separate individual params of |controllers_to_configure| into multiple
// std::vector<ControllerConfigParams> by their DrmDevice.
base::flat_map<scoped_refptr<DrmDevice>, std::vector<ControllerConfigParams>>
drm_device_controllers_to_configure;
for (const auto& config : controllers_to_configure) {
scoped_refptr<DrmDevice> drm = config.drm;
drm_device_controllers_to_configure[drm].emplace_back(config);
}
// For each DrmDevice, try test modeset with all possible CRTC-connector
// combinations. Use the first successful one.
int num_permutations_attempted = 0;
bool fallback_successful_for_all_devices = true;
std::vector<ControllerConfigParams> successful_config_list;
for (auto& [drm, configs_list] : drm_device_controllers_to_configure) {
std::vector<CrtcConnectorPairs> crtc_connector_permutations =
GetAllCrtcConnectorPermutations(*drm, configs_list);
VLOG(1) << "Number of possible fallback CRTC-connector permutations: "
<< crtc_connector_permutations.size();
bool has_successful_permutation = false;
for (const auto& permutation : crtc_connector_permutations) {
// Set up the display abstractions according to the current |permutation|.
for (const auto& crtc_connector_pair : permutation) {
uint32_t crtc_id = crtc_connector_pair.crtc_id;
uint32_t connector_id = crtc_connector_pair.connector_id;
ControllerConfigParams* param =
FindConfigParamsForConnector(configs_list, connector_id);
if (!param) {
LOG(ERROR) << __func__
<< ": Could not find ControllerConfigParams for connector "
"with ID: "
<< connector_id;
continue;
}
param->crtc = crtc_id;
}
if (!UpdateDisplaysWithNewCrtcs(configs_list)) {
continue;
}
++num_permutations_attempted;
if (screen_manager_->ConfigureDisplayControllers(
configs_list, {display::ModesetFlag::kTestModeset})) {
has_successful_permutation = true;
for (auto& config : configs_list) {
successful_config_list.push_back(config);
}
// No need to try other permutations for the device if one is
// successful.
break;
}
}
fallback_successful_for_all_devices &= has_successful_permutation;
if (!fallback_successful_for_all_devices) {
LOG(WARNING) << __func__
<< ": No successful CRTC-connector pairing permutation "
"found or DRM device: "
<< drm->device_path().value();
// TODO: b/329078793 - Stop reverting to the original config once
// pageflips are deferred/skipped during configuration.
// Revert ozone abstractions back to the original CRTC-controller pairings
// before the fallback attempt. The original CRTC-connector pairings are
// usually stable across display changes, and has better chances for a
// successful pageflip if one manages to happen between
// ConfigureDisplays() calls.
if (!UpdateDisplaysWithNewCrtcs(controllers_to_configure)) {
LOG(ERROR)
<< __func__
<< ": Failed to revert to the original CRTC-connector pairings.";
}
const std::string num_fallback_histogram =
GetNumFallbackHistogramName(config_requests.size());
base::UmaHistogramCounts1000(num_fallback_histogram,
num_permutations_attempted);
return false;
}
}
if (fallback_successful_for_all_devices) {
const std::string config_request_string =
ConfigRequestToString(config_requests);
successful_test_config_params_.insert(
{config_request_string, successful_config_list});
}
// TODO: b/329078793 - Stop reverting to the original config once
// pageflips are deferred/skipped during configuration.
// Revert ozone abstractions back to the original CRTC-controller pairings
// before the fallback attempt. The original CRTC-connector pairings are
// usually stable across display changes, and has better chances for a
// successful pageflip if one manages to happen between
// ConfigureDisplays() calls.
if (!UpdateDisplaysWithNewCrtcs(controllers_to_configure)) {
LOG(ERROR) << __func__
<< ": Failed to revert to the original CRTC-connector pairings.";
}
const std::string num_fallback_histogram =
GetNumFallbackHistogramName(config_requests.size());
base::UmaHistogramCounts1000(num_fallback_histogram,
num_permutations_attempted);
return fallback_successful_for_all_devices;
}
bool DrmGpuDisplayManager::UpdateDisplaysWithNewCrtcs(
const std::vector<ControllerConfigParams>& controllers_to_configure) {
base::flat_map<scoped_refptr<DrmDevice>, std::vector<ControllerConfigParams>>
drm_device_to_configs;
for (const auto& config : controllers_to_configure) {
scoped_refptr<DrmDevice> drm = config.drm;
drm_device_to_configs[drm].emplace_back(config);
}
// TODO: b/327015722 - handle ReplaceDisplayControllersCrtcs() inside
// ScreenManager.
base::flat_map<DrmDisplay*, base::flat_map<uint32_t /*current_crtc_id*/,
uint32_t /*new_crtc_id*/>>
display_to_new_crtcs_pairs;
for (const auto& [drm, config_list] : drm_device_to_configs) {
ConnectorCrtcMap current_connector_to_crtc_pairings;
ConnectorCrtcMap new_connector_to_crtc_pairings;
for (const auto& config_param : config_list) {
const uint32_t connector_id = config_param.connector;
DrmDisplay* display = FindDisplayByConnectorId(connector_id);
if (!display) {
LOG(DFATAL) << "DrmDisplay with connector ID " << connector_id
<< " not found.";
return false;
}
const DrmDisplay::CrtcConnectorPair* crtc_connector_pair =
display->GetCrtcConnectorPairForConnectorId(connector_id);
if (!crtc_connector_pair) {
LOG(DFATAL) << "CrtcConnectorPair with connector ID " << connector_id
<< " not found.";
return false;
}
uint32_t current_crtc_id = crtc_connector_pair->crtc_id;
display_to_new_crtcs_pairs[display][current_crtc_id] = config_param.crtc;
current_connector_to_crtc_pairings[connector_id] = current_crtc_id;
new_connector_to_crtc_pairings[connector_id] = config_param.crtc;
}
if (!screen_manager_->ReplaceDisplayControllersCrtcs(
drm, current_connector_to_crtc_pairings,
new_connector_to_crtc_pairings)) {
return false;
}
}
for (auto& [display, new_crtc_pairs] : display_to_new_crtcs_pairs) {
display->ReplaceCrtcs(new_crtc_pairs);
}
return true;
}
std::vector<ControllerConfigParams>
DrmGpuDisplayManager::GetLatestModesetTestConfig(
const std::vector<display::DisplayConfigurationParams>& config_requests) {
const std::string config_request_string =
ConfigRequestToString(config_requests);
const auto& config_param_it =
successful_test_config_params_.find(config_request_string);
if (config_param_it == successful_test_config_params_.end()) {
return {};
}
if (!UpdateDisplaysWithNewCrtcs(config_param_it->second)) {
LOG(ERROR) << __func__ << ": Unable to restore CRTC-connector pairings.";
}
return config_param_it->second;
}
std::unique_ptr<drmModeModeInfo> DrmGpuDisplayManager::FindModeForDisplay(
const display::DisplayMode& request_mode,
const DrmDisplay& display,
bool is_seamless) {
std::vector<const drmModeModeInfo*> matching_modes =
FindMatchingModes(request_mode, display.modes());
// Filter the matched modes by testing for seamless configurability if needed.
HardwareDisplayController* controller = screen_manager_->GetDisplayController(
display.drm(), display.GetPrimaryCrtcId());
if (is_seamless) {
if (!controller) {
LOG(ERROR) << "Could not find HardwareDisplayController for display_id: "
<< display.display_id()
<< ". Continuing without seamless verification.";
} else {
std::erase_if(matching_modes, [&display,
&controller](const drmModeModeInfo* mode) {
return !controller->TestSeamlessMode(display.GetPrimaryCrtcId(), *mode);
});
}
}
// If the display doesn't have the mode natively, then lookup the mode
// from other displays and try using it on the current display (some
// displays support panel fitting and they can use different modes even
// if the mode isn't explicitly declared). Not attempted for seamless
// configuration requests.
if (matching_modes.empty() && !is_seamless) {
for (const auto& other_display : displays_) {
matching_modes = FindMatchingModes(request_mode, other_display->modes());
if (!matching_modes.empty()) {
VLOG(3) << "Found matching mode from another display. Attempting to "
"apply via panel fitting.";
break;
}
}
}
// If a matching mode hasn't been found and the display supports VRR, attempt
// to create a virtual mode with the requested properties.
if (matching_modes.empty() && display.IsVrrCapable()) {
const drmModeModeInfo* closest_mode = FindClosestModeWithGreaterRefreshRate(
request_mode, display, is_seamless, controller);
// Use the closest mode to create and return a virtual mode.
if (closest_mode) {
std::unique_ptr<drmModeModeInfo> out_virtual_mode =
CreateVirtualMode(*closest_mode, request_mode.refresh_rate());
if (out_virtual_mode) {
VLOG(3) << "Using virtual mode to achieve refresh_rate="
<< request_mode.refresh_rate();
return out_virtual_mode;
}
}
}
if (matching_modes.empty()) {
LOG(ERROR) << "Failed to find mode: size=" << request_mode.size().ToString()
<< " is_interlaced=" << request_mode.is_interlaced()
<< " refresh_rate=" << request_mode.refresh_rate();
return nullptr;
}
auto out_mode = std::make_unique<drmModeModeInfo>();
*out_mode = *matching_modes.front();
return out_mode;
}
} // namespace ui