| // 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 |