blob: fa48339e400bb554bf0063efd0b94d0790b6c5e9 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// 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/host/drm_display_host_manager.h"
#include <fcntl.h>
#include <stddef.h>
#include <xf86drm.h>
#include <utility>
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/memory/ptr_util.h"
#include "base/strings/stringprintf.h"
#include "base/task_scheduler/post_task.h"
#include "base/threading/thread_restrictions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "ui/display/types/display_snapshot.h"
#include "ui/events/ozone/device/device_event.h"
#include "ui/events/ozone/device/device_manager.h"
#include "ui/ozone/platform/drm/common/drm_util.h"
#include "ui/ozone/platform/drm/host/drm_device_handle.h"
#include "ui/ozone/platform/drm/host/drm_display_host.h"
#include "ui/ozone/platform/drm/host/drm_native_display_delegate.h"
#include "ui/ozone/platform/drm/host/drm_overlay_manager.h"
#include "ui/ozone/platform/drm/host/gpu_thread_adapter.h"
namespace ui {
namespace {
typedef base::Callback<void(const base::FilePath&,
const base::FilePath&,
std::unique_ptr<DrmDeviceHandle>)>
OnOpenDeviceReplyCallback;
const char kDefaultGraphicsCardPattern[] = "/dev/dri/card%d";
const char* kDisplayActionString[] = {
"ADD", "REMOVE", "CHANGE",
};
// Find sysfs device path for the given device path.
base::FilePath MapDevPathToSysPath(const base::FilePath& device_path) {
// |device_path| looks something like /dev/dri/card0. We take the basename of
// that (card0) and append it to /sys/class/drm. /sys/class/drm/card0 is a
// symlink that points to something like
// /sys/devices/pci0000:00/0000:00:02.0/0000:05:00.0/drm/card0, which exposes
// some metadata about the attached device.
return base::MakeAbsoluteFilePath(
base::FilePath("/sys/class/drm").Append(device_path.BaseName()));
}
void OpenDeviceAsync(const base::FilePath& device_path,
const scoped_refptr<base::TaskRunner>& reply_runner,
const OnOpenDeviceReplyCallback& callback) {
base::FilePath sys_path = MapDevPathToSysPath(device_path);
std::unique_ptr<DrmDeviceHandle> handle(new DrmDeviceHandle());
handle->Initialize(device_path, sys_path);
reply_runner->PostTask(
FROM_HERE,
base::BindOnce(callback, device_path, sys_path, std::move(handle)));
}
base::FilePath GetPrimaryDisplayCardPath() {
struct drm_mode_card_res res;
for (int i = 0; /* end on first card# that does not exist */; i++) {
std::string card_path = base::StringPrintf(kDefaultGraphicsCardPattern, i);
if (access(card_path.c_str(), F_OK) != 0)
break;
int fd = open(card_path.c_str(), O_RDWR | O_CLOEXEC);
if (fd < 0) {
VPLOG(1) << "Failed to open '" << card_path << "'";
continue;
}
memset(&res, 0, sizeof(struct drm_mode_card_res));
int ret = drmIoctl(fd, DRM_IOCTL_MODE_GETRESOURCES, &res);
close(fd);
if (ret == 0 && res.count_crtcs > 0) {
return base::FilePath(card_path);
}
VPLOG_IF(1, ret) << "Failed to get DRM resources for '" << card_path << "'";
}
LOG(FATAL) << "Failed to open primary graphics device.";
return base::FilePath(); // Not reached.
}
class FindDrmDisplayHostById {
public:
explicit FindDrmDisplayHostById(int64_t display_id)
: display_id_(display_id) {}
bool operator()(const std::unique_ptr<DrmDisplayHost>& display) const {
return display->snapshot()->display_id() == display_id_;
}
private:
int64_t display_id_;
};
} // namespace
DrmDisplayHostManager::DrmDisplayHostManager(
GpuThreadAdapter* proxy,
DeviceManager* device_manager,
DrmOverlayManager* overlay_manager,
InputControllerEvdev* input_controller)
: proxy_(proxy),
device_manager_(device_manager),
overlay_manager_(overlay_manager),
input_controller_(input_controller),
primary_graphics_card_path_(GetPrimaryDisplayCardPath()),
weak_ptr_factory_(this) {
{
// First device needs to be treated specially. We need to open this
// synchronously since the GPU process will need it to initialize the
// graphics state.
base::ThreadRestrictions::ScopedAllowIO allow_io;
base::FilePath primary_graphics_card_path_sysfs =
MapDevPathToSysPath(primary_graphics_card_path_);
primary_drm_device_handle_.reset(new DrmDeviceHandle());
if (!primary_drm_device_handle_->Initialize(
primary_graphics_card_path_, primary_graphics_card_path_sysfs)) {
LOG(FATAL) << "Failed to open primary graphics card";
return;
}
drm_devices_[primary_graphics_card_path_] =
primary_graphics_card_path_sysfs;
}
device_manager_->AddObserver(this);
proxy_->RegisterHandlerForDrmDisplayHostManager(this);
proxy_->AddGpuThreadObserver(this);
auto display_infos =
GetAvailableDisplayControllerInfos(primary_drm_device_handle_->fd());
has_dummy_display_ = !display_infos.empty();
for (const auto& display_info : display_infos) {
displays_.push_back(std::make_unique<DrmDisplayHost>(
proxy_,
CreateDisplaySnapshot(
display_info.get(), primary_drm_device_handle_->fd(),
primary_drm_device_handle_->sys_path(), 0, gfx::Point()),
true /* is_dummy */));
}
}
DrmDisplayHostManager::~DrmDisplayHostManager() {
device_manager_->RemoveObserver(this);
proxy_->UnRegisterHandlerForDrmDisplayHostManager();
proxy_->RemoveGpuThreadObserver(this);
}
DrmDisplayHost* DrmDisplayHostManager::GetDisplay(int64_t display_id) {
auto it = std::find_if(displays_.begin(), displays_.end(),
FindDrmDisplayHostById(display_id));
if (it == displays_.end())
return nullptr;
return it->get();
}
void DrmDisplayHostManager::AddDelegate(DrmNativeDisplayDelegate* delegate) {
DCHECK(!delegate_);
delegate_ = delegate;
}
void DrmDisplayHostManager::RemoveDelegate(DrmNativeDisplayDelegate* delegate) {
DCHECK_EQ(delegate_, delegate);
delegate_ = nullptr;
}
void DrmDisplayHostManager::TakeDisplayControl(
const display::DisplayControlCallback& callback) {
if (display_control_change_pending_) {
LOG(ERROR) << "TakeDisplayControl called while change already pending";
callback.Run(false);
return;
}
if (!display_externally_controlled_) {
LOG(ERROR) << "TakeDisplayControl called while display already owned";
callback.Run(true);
return;
}
take_display_control_callback_ = callback;
display_control_change_pending_ = true;
if (!proxy_->GpuTakeDisplayControl())
GpuTookDisplayControl(false);
}
void DrmDisplayHostManager::RelinquishDisplayControl(
const display::DisplayControlCallback& callback) {
if (display_control_change_pending_) {
LOG(ERROR)
<< "RelinquishDisplayControl called while change already pending";
callback.Run(false);
return;
}
if (display_externally_controlled_) {
LOG(ERROR) << "RelinquishDisplayControl called while display not owned";
callback.Run(true);
return;
}
relinquish_display_control_callback_ = callback;
display_control_change_pending_ = true;
if (!proxy_->GpuRelinquishDisplayControl())
GpuRelinquishedDisplayControl(false);
}
void DrmDisplayHostManager::UpdateDisplays(
const display::GetDisplaysCallback& callback) {
get_displays_callback_ = callback;
if (!proxy_->GpuRefreshNativeDisplays()) {
get_displays_callback_.Reset();
RunUpdateDisplaysCallback(callback);
}
}
void DrmDisplayHostManager::OnDeviceEvent(const DeviceEvent& event) {
if (event.device_type() != DeviceEvent::DISPLAY)
return;
event_queue_.push(DisplayEvent(event.action_type(), event.path()));
ProcessEvent();
}
void DrmDisplayHostManager::ProcessEvent() {
while (!event_queue_.empty() && !task_pending_) {
DisplayEvent event = event_queue_.front();
event_queue_.pop();
VLOG(1) << "Got display event " << kDisplayActionString[event.action_type]
<< " for " << event.path.value();
switch (event.action_type) {
case DeviceEvent::ADD:
if (drm_devices_.find(event.path) == drm_devices_.end()) {
base::PostTaskWithTraits(
FROM_HERE,
{base::MayBlock(),
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(
&OpenDeviceAsync, event.path,
base::ThreadTaskRunnerHandle::Get(),
base::Bind(&DrmDisplayHostManager::OnAddGraphicsDevice,
weak_ptr_factory_.GetWeakPtr())));
task_pending_ = true;
}
break;
case DeviceEvent::CHANGE:
task_pending_ = base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&DrmDisplayHostManager::OnUpdateGraphicsDevice,
weak_ptr_factory_.GetWeakPtr()));
break;
case DeviceEvent::REMOVE:
DCHECK(event.path != primary_graphics_card_path_)
<< "Removing primary graphics card";
auto it = drm_devices_.find(event.path);
if (it != drm_devices_.end()) {
task_pending_ = base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&DrmDisplayHostManager::OnRemoveGraphicsDevice,
weak_ptr_factory_.GetWeakPtr(), it->second));
drm_devices_.erase(it);
}
break;
}
}
}
void DrmDisplayHostManager::OnAddGraphicsDevice(
const base::FilePath& dev_path,
const base::FilePath& sys_path,
std::unique_ptr<DrmDeviceHandle> handle) {
if (handle->IsValid()) {
drm_devices_[dev_path] = sys_path;
proxy_->GpuAddGraphicsDevice(sys_path, handle->PassFD());
NotifyDisplayDelegate();
}
task_pending_ = false;
ProcessEvent();
}
void DrmDisplayHostManager::OnUpdateGraphicsDevice() {
NotifyDisplayDelegate();
task_pending_ = false;
ProcessEvent();
}
void DrmDisplayHostManager::OnRemoveGraphicsDevice(
const base::FilePath& sys_path) {
proxy_->GpuRemoveGraphicsDevice(sys_path);
NotifyDisplayDelegate();
task_pending_ = false;
ProcessEvent();
}
void DrmDisplayHostManager::OnGpuProcessLaunched() {
std::unique_ptr<DrmDeviceHandle> handle =
std::move(primary_drm_device_handle_);
{
base::ThreadRestrictions::ScopedAllowIO allow_io;
drm_devices_.clear();
drm_devices_[primary_graphics_card_path_] =
MapDevPathToSysPath(primary_graphics_card_path_);
if (!handle) {
handle.reset(new DrmDeviceHandle());
if (!handle->Initialize(primary_graphics_card_path_,
drm_devices_[primary_graphics_card_path_]))
LOG(FATAL) << "Failed to open primary graphics card";
}
}
// Send the primary device first since this is used to initialize graphics
// state.
proxy_->GpuAddGraphicsDevice(drm_devices_[primary_graphics_card_path_],
handle->PassFD());
}
void DrmDisplayHostManager::OnGpuThreadReady() {
// If in the middle of a configuration, just respond with the old list of
// displays. This is fine, since after the DRM resources are initialized and
// IPC-ed to the GPU NotifyDisplayDelegate() is called to let the display
// delegate know that the display configuration changed and it needs to
// update it again.
if (!get_displays_callback_.is_null()) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&DrmDisplayHostManager::RunUpdateDisplaysCallback,
weak_ptr_factory_.GetWeakPtr(), get_displays_callback_));
get_displays_callback_.Reset();
}
// Signal that we're taking DRM master since we're going through the
// initialization process again and we'll take all the available resources.
if (!take_display_control_callback_.is_null())
GpuTookDisplayControl(true);
if (!relinquish_display_control_callback_.is_null())
GpuRelinquishedDisplayControl(false);
device_manager_->ScanDevices(this);
NotifyDisplayDelegate();
}
void DrmDisplayHostManager::OnGpuThreadRetired() {}
void DrmDisplayHostManager::GpuHasUpdatedNativeDisplays(
const std::vector<DisplaySnapshot_Params>& params_vector) {
if (delegate_)
delegate_->OnDisplaySnapshotsInvalidated();
std::vector<std::unique_ptr<DrmDisplayHost>> old_displays;
displays_.swap(old_displays);
for (const auto& params : params_vector) {
auto it = std::find_if(old_displays.begin(), old_displays.end(),
FindDrmDisplayHostById(params.display_id));
if (it == old_displays.end()) {
displays_.push_back(std::make_unique<DrmDisplayHost>(
proxy_, CreateDisplaySnapshot(params), false /* is_dummy */));
} else {
(*it)->UpdateDisplaySnapshot(CreateDisplaySnapshot(params));
displays_.push_back(std::move(*it));
old_displays.erase(it);
}
}
if (!get_displays_callback_.is_null()) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&DrmDisplayHostManager::RunUpdateDisplaysCallback,
weak_ptr_factory_.GetWeakPtr(), get_displays_callback_));
get_displays_callback_.Reset();
}
}
void DrmDisplayHostManager::GpuConfiguredDisplay(int64_t display_id,
bool status) {
DrmDisplayHost* display = GetDisplay(display_id);
if (display) {
display->OnDisplayConfigured(status);
overlay_manager_->ResetCache();
} else {
LOG(ERROR) << "Couldn't find display with id=" << display_id;
}
}
void DrmDisplayHostManager::GpuReceivedHDCPState(int64_t display_id,
bool status,
display::HDCPState state) {
DrmDisplayHost* display = GetDisplay(display_id);
if (display)
display->OnHDCPStateReceived(status, state);
else
LOG(ERROR) << "Couldn't find display with id=" << display_id;
}
void DrmDisplayHostManager::GpuUpdatedHDCPState(int64_t display_id,
bool status) {
DrmDisplayHost* display = GetDisplay(display_id);
if (display)
display->OnHDCPStateUpdated(status);
else
LOG(ERROR) << "Couldn't find display with id=" << display_id;
}
void DrmDisplayHostManager::GpuTookDisplayControl(bool status) {
if (take_display_control_callback_.is_null()) {
LOG(ERROR) << "No callback for take display control";
return;
}
DCHECK(display_externally_controlled_);
DCHECK(display_control_change_pending_);
if (status) {
input_controller_->SetInputDevicesEnabled(true);
display_externally_controlled_ = false;
}
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(take_display_control_callback_, status));
take_display_control_callback_.Reset();
display_control_change_pending_ = false;
}
void DrmDisplayHostManager::GpuRelinquishedDisplayControl(bool status) {
if (relinquish_display_control_callback_.is_null()) {
LOG(ERROR) << "No callback for relinquish display control";
return;
}
DCHECK(!display_externally_controlled_);
DCHECK(display_control_change_pending_);
if (status) {
input_controller_->SetInputDevicesEnabled(false);
display_externally_controlled_ = true;
}
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(relinquish_display_control_callback_, status));
relinquish_display_control_callback_.Reset();
display_control_change_pending_ = false;
}
void DrmDisplayHostManager::RunUpdateDisplaysCallback(
const display::GetDisplaysCallback& callback) const {
std::vector<display::DisplaySnapshot*> snapshots;
for (const auto& display : displays_)
snapshots.push_back(display->snapshot());
callback.Run(snapshots);
}
void DrmDisplayHostManager::NotifyDisplayDelegate() const {
if (delegate_)
delegate_->OnConfigurationChanged();
}
} // namespace ui