blob: 9371db3c24e3155f3d4a529c53f283a771328ee5 [file] [log] [blame]
// Copyright (c) 2014 The Chromium OS 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 "power_manager/powerd/system/display/display_watcher.h"
#include <algorithm>
#include <string>
#include <base/files/file_enumerator.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/string_util.h>
#include "power_manager/powerd/system/udev.h"
namespace power_manager {
namespace system {
namespace {
// Path containing directories describing the state of DRM devices.
const char kSysClassDrmPath[] = "/sys/class/drm";
// Glob-style pattern for device directories within kSysClassDrmPath.
const char kDrmDeviceNamePattern[] = "card*";
// Glob-style pattern for the I2C device name within a DRM device directory.
const char kI2CDeviceNamePattern[] = "i2c-*";
// Directory containing I2C devices.
const char kI2CDevPath[] = "/dev";
// The delay to advertise about change in display configuration after udev
// event.
constexpr base::TimeDelta kDebounceDelay = base::TimeDelta::FromSeconds(1);
// Returns true if the device described by |drm_device_dir| is connected.
bool IsConnectorStatus(const base::FilePath& drm_device_dir,
const std::string& connector_status) {
base::FilePath status_path =
drm_device_dir.Append(DisplayWatcher::kDrmStatusFile);
std::string status;
if (!base::ReadFileToString(status_path, &status))
return false;
// Trim whitespace to deal with trailing newlines.
base::TrimWhitespaceASCII(status, base::TRIM_TRAILING, &status);
return status == connector_status;
}
} // namespace
const char DisplayWatcher::kI2CUdevSubsystem[] = "i2c-dev";
const char DisplayWatcher::kDrmUdevSubsystem[] = "drm";
const char DisplayWatcher::kDrmStatusFile[] = "status";
const char DisplayWatcher::kDrmStatusConnected[] = "connected";
const char DisplayWatcher::kDrmStatusUnknown[] = "unknown";
DisplayWatcher::DisplayWatcher() : udev_(nullptr) {}
DisplayWatcher::~DisplayWatcher() {
if (udev_) {
udev_->RemoveSubsystemObserver(kI2CUdevSubsystem, this);
udev_->RemoveSubsystemObserver(kDrmUdevSubsystem, this);
udev_ = nullptr;
}
}
bool DisplayWatcher::trigger_debounce_timeout_for_testing() {
if (!debounce_timer_.IsRunning())
return false;
debounce_timer_.Stop();
HandleDebounceTimeout();
return true;
}
void DisplayWatcher::Init(UdevInterface* udev) {
udev_ = udev;
udev_->AddSubsystemObserver(kI2CUdevSubsystem, this);
udev_->AddSubsystemObserver(kDrmUdevSubsystem, this);
UpdateDisplays();
}
const std::vector<DisplayInfo>& DisplayWatcher::GetDisplays() const {
return displays_;
}
void DisplayWatcher::AddObserver(DisplayWatcherObserver* observer) {
observers_.AddObserver(observer);
}
void DisplayWatcher::RemoveObserver(DisplayWatcherObserver* observer) {
observers_.RemoveObserver(observer);
}
void DisplayWatcher::OnUdevEvent(const UdevEvent& event) {
VLOG(1) << "Got udev event for " << event.device_info.sysname
<< " on subsystem " << event.device_info.subsystem;
UpdateDisplays();
}
base::FilePath DisplayWatcher::GetI2CDevicePath(const base::FilePath& drm_dir) {
base::FilePath i2c_dev;
i2c_dev = FindI2CDeviceInDir(drm_dir.AppendASCII("ddc/i2c-dev"));
if (!base::PathExists(i2c_dev))
i2c_dev = FindI2CDeviceInDir(drm_dir);
return i2c_dev;
}
base::FilePath DisplayWatcher::FindI2CDeviceInDir(const base::FilePath& dir) {
base::FileEnumerator enumerator(dir,
false, // recursive
base::FileEnumerator::DIRECTORIES,
kI2CDeviceNamePattern);
for (base::FilePath i2c_dir = enumerator.Next(); !i2c_dir.empty();
i2c_dir = enumerator.Next()) {
base::FilePath dev_path = i2c_dev_path_for_testing_.empty()
? base::FilePath(kI2CDevPath)
: i2c_dev_path_for_testing_;
const std::string i2c_name = i2c_dir.BaseName().value();
base::FilePath i2c_dev = dev_path.Append(i2c_name);
if (base::PathExists(i2c_dev))
return i2c_dev;
}
return base::FilePath();
}
void DisplayWatcher::HandleDebounceTimeout() {
for (DisplayWatcherObserver& observer : observers_)
observer.OnDisplaysChanged(displays_);
}
void DisplayWatcher::UpdateDisplays() {
std::vector<DisplayInfo> new_displays;
base::FileEnumerator enumerator(
sysfs_drm_path_for_testing_.empty() ? base::FilePath(kSysClassDrmPath)
: sysfs_drm_path_for_testing_,
false, // recursive
static_cast<base::FileEnumerator::FileType>(
base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES |
base::FileEnumerator::SHOW_SYM_LINKS),
kDrmDeviceNamePattern);
for (base::FilePath device_path = enumerator.Next(); !device_path.empty();
device_path = enumerator.Next()) {
DisplayInfo info;
if (IsConnectorStatus(device_path, kDrmStatusConnected))
info.connector_status = DisplayInfo::ConnectorStatus::CONNECTED;
else if (IsConnectorStatus(device_path, kDrmStatusUnknown))
info.connector_status = DisplayInfo::ConnectorStatus::UNKNOWN;
else
continue;
info.drm_path = device_path;
info.i2c_path = GetI2CDevicePath(device_path);
new_displays.push_back(info);
VLOG(1) << "Found connected display: drm_path=" << info.drm_path.value()
<< ", i2c_path=" << info.i2c_path.value();
}
std::sort(new_displays.begin(), new_displays.end());
if (new_displays == displays_)
return;
displays_.swap(new_displays);
if (!debounce_timer_.IsRunning()) {
// Advertise about display mode change after |kDebounceDelay| delay,
// giving enough time for things to settle.
debounce_timer_.Start(FROM_HERE, kDebounceDelay, this,
&DisplayWatcher::HandleDebounceTimeout);
} else {
// If the debounce timer is already running, avoid advertising about
// display configuration change immediately. Instead reset the timer to
// wait for things to settle.
debounce_timer_.Reset();
}
}
} // namespace system
} // namespace power_manager