blob: 09ff66c1d8db288e8e6585bf5d22de2a888008af [file] [log] [blame] [edit]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <base/files/file_util.h>
#include <base/logging.h>
#include <brillo/udev/udev_enumerate.h>
#include <re2/re2.h>
#include "arc/adbd/udev_monitor.h"
namespace adbd {
namespace {
constexpr char kUdev[] = "udev";
constexpr char kTypeCSubsystem[] = "typec";
// Regex to detect typec port partner events.
constexpr char kPartnerRegex[] = R"(port(\d+)-partner)";
// User space control to modify the USB Type-C role.
// Refer Documentation/ABI/testing/sysfs-class-usb_role.
constexpr char kUsbRoleSysPath[] =
"/sys/class/usb_role/CON{PORT}-role-switch/role";
// Regex to replace PORT in USB role setting.
constexpr char kUsbRolePortRegex[] = R"(\{PORT\})";
constexpr char kUsbRole[] = "host";
} // namespace
UdevMonitor::UdevMonitor() : udev_thread_("udev_monitor") {}
bool UdevMonitor::Init() {
udev_ = brillo::Udev::Create();
if (!udev_) {
PLOG(ERROR) << "Failed to initialize udev object.";
return false;
}
// Enumerate existing devices and update usb role.
if (!UdevMonitor::Enumerate()) {
PLOG(ERROR) << "Failed to enumerate existing devices.";
return false;
}
// Set up udev monitor for typec usb events.
udev_monitor_ = udev_->CreateMonitorFromNetlink(kUdev);
if (!udev_monitor_) {
PLOG(ERROR) << "Failed to create udev monitor.";
return false;
}
if (!udev_monitor_->FilterAddMatchSubsystemDeviceType(kTypeCSubsystem,
nullptr)) {
PLOG(ERROR) << "Failed to add typec subsystem to udev monitor.";
return false;
}
if (!udev_monitor_->FilterAddMatchSubsystemDeviceType("tty", nullptr)) {
PLOG(ERROR) << "Failed to add typec subsystem to udev monitor.";
return false;
}
if (!udev_monitor_->EnableReceiving()) {
PLOG(ERROR) << "Failed to enable receiving for udev monitor.";
return false;
}
int fd = udev_monitor_->GetFileDescriptor();
if (fd == brillo::UdevMonitor::kInvalidFileDescriptor) {
PLOG(ERROR) << "Failed to get udev monitor fd.";
return false;
}
if (!udev_thread_.StartWithOptions(
base::Thread::Options(base::MessagePumpType::IO, 0))) {
LOG(ERROR) << "Failed to start udev thread.";
return -1;
}
udev_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&UdevMonitor::StartWatching, base::Unretained(this), fd));
VLOG(1) << "Udev monitor started";
return true;
}
bool UdevMonitor::Enumerate() {
DCHECK(udev_);
auto enumerate = udev_->CreateEnumerate();
if (!enumerate->AddMatchSubsystem(kTypeCSubsystem)) {
PLOG(ERROR) << "Failed to add typec subsystem to udev enumerate.";
return false;
}
enumerate->ScanDevices();
auto entry = enumerate->GetListEntry();
if (!entry) {
LOG(WARNING) << "No existing typec devices.\n";
return true;
}
while (entry != nullptr) {
UdevMonitor::OnDeviceAdd(base::FilePath(std::string(entry->GetName())));
entry = entry->GetNext();
}
return true;
}
void UdevMonitor::StartWatching(int fd) {
udev_monitor_watcher_ = base::FileDescriptorWatcher::WatchReadable(
fd,
base::BindRepeating(&UdevMonitor::OnUdevEvent, base::Unretained(this)));
if (!udev_monitor_watcher_) {
PLOG(ERROR) << "Failed to start watcher for udev monitor fd.";
}
}
// Update usb role for each newly added typec port
void UdevMonitor::OnDeviceAdd(const base::FilePath& path) {
int port_num;
if (RE2::FullMatch(path.BaseName().value(), kPartnerRegex, &port_num)) {
auto path = std::string(kUsbRoleSysPath);
RE2::Replace(&path, kUsbRolePortRegex, std::to_string(port_num));
base::FilePath usb_role_path = base::FilePath(path);
if (!base::PathExists(usb_role_path)) {
PLOG(ERROR) << "Usb role switch control file does not exist";
return;
}
// update USB role to "host"
if (!base::WriteFile(usb_role_path, kUsbRole)) {
PLOG(ERROR) << "Failed to write usb role";
return;
}
VLOG(1) << "Successfully updated usb_role to " << kUsbRole << " for "
<< usb_role_path;
}
}
// Callback for subscribed udev events.
void UdevMonitor::OnUdevEvent() {
auto device = udev_monitor_->ReceiveDevice();
if (!device) {
LOG(ERROR) << "Udev receive device failed.";
return;
}
auto path = base::FilePath(device->GetSysPath());
if (path.empty()) {
LOG(ERROR) << "Failed to get device syspath.";
return;
}
auto action = std::string(device->GetAction());
if (action.empty()) {
LOG(ERROR) << "Failed to get device action.";
return;
}
auto subsystem = std::string(device->GetSubsystem());
if (subsystem.empty()) {
LOG(ERROR) << "Failed to get device subsystem";
return;
}
if (action == "add") {
UdevMonitor::OnDeviceAdd(path);
}
}
} // namespace adbd