blob: c234a0d146598e62f4f7950f03a14ec9c2081337 [file]
// Copyright 2026 The Fuchsia 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 "device_resolver.h"
#include <fidl/fuchsia.hardware.adc/cpp/wire.h>
#include <fidl/fuchsia.hardware.temperature/cpp/wire.h>
#include <fidl/fuchsia.hardware.trippoint/cpp/wire.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <stdio.h>
#include <algorithm>
#include <array>
#include <cctype>
#include <charconv>
#include <filesystem>
#include <optional>
#include <string>
#include <vector>
namespace FidlTemperature = fuchsia_hardware_temperature;
namespace {
// Device Paths
constexpr char kTemperatureServiceDir[] = "/svc/fuchsia.hardware.temperature.Service";
constexpr char kTemperatureDevfsDir[] = "/dev/class/temperature";
constexpr char kAdcServiceDir[] = "/svc/fuchsia.hardware.adc.Service";
constexpr char kAdcDevfsDir[] = "/dev/class/adc";
constexpr char kTrippointServiceDir[] = "/svc/fuchsia.hardware.trippoint.Service";
constexpr char kTrippointDebugServiceDir[] = "/svc/fuchsia.hardware.trippoint.DebugService";
// Service Members
constexpr char kServiceMemberDevice[] = "device";
constexpr char kServiceMemberTripPoint[] = "trippoint";
constexpr char kServiceMemberDebug[] = "debug";
// Represents a raw device candidate found during filesystem scanning (discovery phase).
// This is transient internal helper state and is mapped to the public ResolvedDevice
// once a command and resolution path are selected.
struct DiscoveredDevice {
std::string name;
std::string path;
bool is_service = false;
};
bool IsTrippointCommand(std::string_view arg) {
return arg == kCmdTripPoint || arg == kCmdWait || arg == kCmdTrigger;
}
std::string_view GetMemberForService(std::string_view service_dir, std::string_view command) {
if (service_dir == kTemperatureServiceDir) {
return IsTrippointCommand(command) ? kServiceMemberTripPoint : kServiceMemberDevice;
}
if (service_dir == kAdcServiceDir) {
return kServiceMemberDevice;
}
if (service_dir == kTrippointServiceDir) {
return kServiceMemberTripPoint;
}
if (service_dir == kTrippointDebugServiceDir) {
return kServiceMemberDebug;
}
return "";
}
bool HasServiceMember(std::string_view path) {
auto check = [](std::string_view path, std::string_view member) {
return path.size() >= member.size() + 1 && path[path.size() - member.size() - 1] == '/' &&
path.ends_with(member);
};
return check(path, kServiceMemberDevice) || check(path, kServiceMemberTripPoint) ||
check(path, kServiceMemberDebug);
}
std::string GetPathForDevice(const DiscoveredDevice& dev, std::string_view command) {
if (dev.is_service) {
for (auto service_dir : {
kTemperatureServiceDir,
kAdcServiceDir,
kTrippointServiceDir,
kTrippointDebugServiceDir,
}) {
if (dev.path.starts_with(service_dir)) {
std::string_view member = GetMemberForService(service_dir, command);
if (!member.empty()) {
return (std::filesystem::path(dev.path) / member).string();
}
}
}
}
return dev.path;
}
std::optional<std::string> TryGetSensorName(const std::string& device_member_path) {
auto client_res = ConnectToDevice<FidlTemperature::Device>(device_member_path);
if (client_res.is_error()) {
return std::nullopt;
}
auto client = std::move(client_res.value());
auto response = client->GetSensorName();
if (response.ok()) {
return std::string(response->name.data(), response->name.size());
}
return std::nullopt;
}
zx::result<size_t> PromptUserSelection(const std::vector<std::string>& display_items,
std::string_view device_type_name) {
printf("Multiple %.*s devices found:\n", static_cast<int>(device_type_name.size()),
device_type_name.data());
for (size_t i = 0; i < display_items.size(); ++i) {
printf(" %zu. %s\n", i + 1, display_items[i].c_str());
}
printf("Select a device (1-%zu): ", display_items.size());
char input_buf[128];
if (fgets(input_buf, sizeof(input_buf), stdin) == nullptr) {
printf("Error reading input.\n");
return zx::error(ZX_ERR_INVALID_ARGS);
}
std::string_view input_sv(input_buf);
auto first = input_sv.find_first_not_of(" \t\r\n");
if (first == std::string_view::npos) {
printf("Invalid selection.\n");
return zx::error(ZX_ERR_INVALID_ARGS);
}
auto last = input_sv.find_last_not_of(" \t\r\n");
input_sv = input_sv.substr(first, last - first + 1);
uint64_t selection;
auto [p, ec] = std::from_chars(input_sv.data(), input_sv.data() + input_sv.size(), selection);
if (ec != std::errc() || p != input_sv.data() + input_sv.size() || selection < 1 ||
selection > display_items.size()) {
printf("Invalid selection.\n");
return zx::error(ZX_ERR_INVALID_ARGS);
}
return zx::ok(selection - 1);
}
std::vector<std::filesystem::directory_entry> GetEntriesInDirectory(std::string_view dir_path) {
std::vector<std::filesystem::directory_entry> entries;
std::error_code ec;
std::filesystem::path path(dir_path);
if (std::filesystem::exists(path, ec) && !ec) {
for (auto it = std::filesystem::directory_iterator(path, ec);
it != std::filesystem::directory_iterator() && !ec; it.increment(ec)) {
entries.push_back(*it);
}
if (ec) {
printf("Warning: directory iteration failed for '%.*s': %s\n",
static_cast<int>(dir_path.size()), dir_path.data(), ec.message().c_str());
}
}
return entries;
}
std::vector<DiscoveredDevice> ScanServiceDir(std::string_view service_dir,
std::string_view member_to_check,
std::string_view name_prefix = "",
std::string_view name_member_to_connect = "") {
std::vector<DiscoveredDevice> devices;
for (const auto& entry : GetEntriesInDirectory(service_dir)) {
std::filesystem::path path = entry.path();
std::filesystem::path member_path = path / member_to_check;
std::error_code ec;
if (std::filesystem::exists(member_path, ec) && !ec) {
std::string name;
if (name_prefix.empty()) {
name = path.filename().string();
} else {
name = std::string(name_prefix) + path.filename().string();
}
if (!name_member_to_connect.empty()) {
std::filesystem::path name_member_path = path / name_member_to_connect;
auto name_opt = TryGetSensorName(name_member_path.string());
if (name_opt) {
if (name_prefix.empty()) {
name = *name_opt;
} else {
name = std::string(name_prefix) + *name_opt;
}
} else if (member_to_check == kServiceMemberDevice) {
// Skip if we can't connect to temperature device member (matches original behavior)
continue;
}
}
devices.push_back({name, path.string(), /*is_service=*/true});
}
}
return devices;
}
std::vector<DiscoveredDevice> ScanDevfsDir(std::string_view devfs_dir) {
std::vector<DiscoveredDevice> devices;
for (const auto& entry : GetEntriesInDirectory(devfs_dir)) {
devices.push_back(
{entry.path().filename().string(), entry.path().string(), /*is_service=*/false});
}
return devices;
}
std::vector<DiscoveredDevice> GetTemperatureDevices() {
auto devices =
ScanServiceDir(kTemperatureServiceDir, kServiceMemberDevice, "", kServiceMemberDevice);
if (devices.empty()) {
return ScanDevfsDir(kTemperatureDevfsDir);
}
return devices;
}
std::vector<DiscoveredDevice> GetTrippointDevices() {
auto devices =
ScanServiceDir(kTemperatureServiceDir, kServiceMemberTripPoint, "", kServiceMemberDevice);
auto standalone = ScanServiceDir(kTrippointServiceDir, kServiceMemberTripPoint, "trippoint-");
devices.insert(devices.end(), standalone.begin(), standalone.end());
return devices;
}
std::vector<DiscoveredDevice> GetTrippointDebugDevices() {
return ScanServiceDir(kTrippointDebugServiceDir, kServiceMemberDebug, "debug-");
}
std::vector<DiscoveredDevice> GetAdcDevices() {
auto devices = ScanServiceDir(kAdcServiceDir, kServiceMemberDevice);
if (devices.empty()) {
return ScanDevfsDir(kAdcDevfsDir);
}
return devices;
}
zx::result<ResolvedDevice> ResolveDeviceFromList(const std::vector<DiscoveredDevice>& devices,
std::string_view provided_path_or_name,
std::string_view command, DeviceType device_type,
bool suppress_errors = false) {
std::string_view device_type_name = GetDeviceTypeName(device_type, command);
if (!provided_path_or_name.empty()) {
for (const auto& dev : devices) {
std::string instance_name = std::filesystem::path(dev.path).filename().string();
if (dev.name == provided_path_or_name || instance_name == provided_path_or_name) {
return zx::ok(ResolvedDevice{
.path = GetPathForDevice(dev, command),
.type = device_type,
.friendly_name = dev.name,
.base_path = dev.path,
});
}
}
if (!suppress_errors) {
printf("Failed to resolve %.*s device name '%.*s'\n",
static_cast<int>(device_type_name.size()), device_type_name.data(),
static_cast<int>(provided_path_or_name.size()), provided_path_or_name.data());
}
return zx::error(ZX_ERR_NOT_FOUND);
}
if (devices.empty()) {
if (!suppress_errors) {
printf("No %.*s devices found.\n", static_cast<int>(device_type_name.size()),
device_type_name.data());
}
return zx::error(ZX_ERR_NOT_FOUND);
}
if (devices.size() == 1) {
std::string path = GetPathForDevice(devices[0], command);
printf("Using %.*s device: %s (%s)\n", static_cast<int>(device_type_name.size()),
device_type_name.data(), devices[0].name.c_str(), path.c_str());
return zx::ok(ResolvedDevice{
.path = path,
.type = device_type,
.friendly_name = devices[0].name,
.base_path = devices[0].path,
});
}
std::vector<std::string> display_items;
display_items.reserve(devices.size());
for (const auto& dev : devices) {
display_items.push_back(dev.name + " (" + dev.path + ")");
}
auto selection_res = PromptUserSelection(display_items, device_type_name);
if (selection_res.is_error()) {
return selection_res.take_error();
}
size_t selection = selection_res.value();
std::string path = GetPathForDevice(devices[selection], command);
printf("Using %.*s device: %s (%s)\n", static_cast<int>(device_type_name.size()),
device_type_name.data(), devices[selection].name.c_str(), path.c_str());
return zx::ok(ResolvedDevice{
.path = path,
.type = device_type,
.friendly_name = devices[selection].name,
.base_path = devices[selection].path,
});
}
zx::result<ResolvedDevice> ResolveTemperatureDevice(std::string_view provided_path_or_name,
std::string_view command,
bool suppress_errors = false) {
return ResolveDeviceFromList(GetTemperatureDevices(), provided_path_or_name, command,
DeviceType::kTemperature, suppress_errors);
}
zx::result<ResolvedDevice> ResolveTrippointDevice(std::string_view provided_path_or_name,
std::string_view command) {
// The 'trigger' command simulates a trippoint event for testing, which requires the
// trippoint debug service. Other commands (e.g., 'trippoint', 'wait') interact with the
// production trippoint service.
bool is_debug = command == kCmdTrigger;
auto devices = is_debug ? GetTrippointDebugDevices() : GetTrippointDevices();
return ResolveDeviceFromList(devices, provided_path_or_name, command, DeviceType::kTrippoint);
}
DeviceType DeduceDeviceTypeFromPathAndCommand(std::string_view path, std::string_view command) {
if (path.starts_with(kAdcServiceDir) || path.starts_with(kAdcDevfsDir)) {
return DeviceType::kAdc;
}
if (path.starts_with(kTrippointServiceDir) || path.starts_with(kTrippointDebugServiceDir)) {
return DeviceType::kTrippoint;
}
if (path.starts_with(kTemperatureServiceDir)) {
if (IsTrippointCommand(command)) {
return DeviceType::kTrippoint;
}
return DeviceType::kTemperature;
}
if (path.starts_with(kTemperatureDevfsDir)) {
return DeviceType::kTemperature;
}
// Fallback for arbitrary/unknown paths
if (IsTrippointCommand(command)) {
return DeviceType::kTrippoint;
}
if (command == kCmdResolution || command == kCmdReadNorm) {
return DeviceType::kAdc;
}
if (command == kCmdRead) {
if (path.find("adc") != std::string::npos) {
return DeviceType::kAdc;
}
return DeviceType::kTemperature;
}
return DeviceType::kTemperature;
}
zx::result<ResolvedDevice> ResolveAdcDevice(std::string_view provided_path_or_name,
std::string_view command,
bool suppress_errors = false) {
return ResolveDeviceFromList(GetAdcDevices(), provided_path_or_name, command, DeviceType::kAdc,
suppress_errors);
}
} // namespace
std::string_view ToString(DeviceType type) {
switch (type) {
case DeviceType::kTemperature:
return "temperature";
case DeviceType::kAdc:
return "ADC";
case DeviceType::kTrippoint:
return "trippoint";
}
}
std::string_view GetDeviceTypeName(DeviceType type, std::string_view command) {
if (type == DeviceType::kTrippoint && command == kCmdTrigger) {
return "trippoint debug";
}
return ToString(type);
}
bool IsInteger(std::string_view s) {
return !s.empty() &&
std::all_of(s.begin(), s.end(), [](unsigned char c) { return std::isdigit(c); });
}
bool IsKnownCommand(std::string_view arg) {
static constexpr std::array kKnownCommands = {
kCmdList, kCmdResolution, kCmdRead, kCmdReadNorm, kCmdTripPoint,
kCmdWait, kCmdName, kCmdTrigger, kCmdReadAll, kCmdHelp,
};
return std::find(kKnownCommands.begin(), kKnownCommands.end(), arg) != kKnownCommands.end();
}
void do_list() {
auto devices = GetTemperatureDevices();
if (devices.empty()) {
printf("No temperature devices found.\n");
return;
}
printf("Found %zu temperature devices:\n", devices.size());
for (const auto& dev : devices) {
printf(" %-20s (%s)\n", dev.name.c_str(), dev.path.c_str());
}
}
zx::result<ResolvedDevice> ResolveDevice(std::string_view provided_path_or_name,
std::string_view command) {
std::string path(provided_path_or_name);
if (!path.empty() && path.starts_with('/')) {
DeviceType dev_type = DeduceDeviceTypeFromPathAndCommand(path, command);
// It's a path. Check if it needs a service member.
for (auto service_dir : {
kTemperatureServiceDir,
kAdcServiceDir,
kTrippointServiceDir,
kTrippointDebugServiceDir,
}) {
if (path.starts_with(service_dir)) {
if (!HasServiceMember(path)) {
std::string_view member = GetMemberForService(service_dir, command);
if (!member.empty()) {
if (!path.ends_with('/')) {
path += '/';
}
path += member;
}
}
break;
}
}
return zx::ok(ResolvedDevice{
.path = path,
.type = dev_type,
.friendly_name = "",
.base_path = "",
});
}
if (IsTrippointCommand(command)) {
return ResolveTrippointDevice(path, command);
}
if (command == kCmdResolution || command == kCmdReadNorm) {
return ResolveAdcDevice(path, command);
}
if (command == kCmdRead) {
// Try Temperature first (silently to avoid noisy fallback prints)
auto temp_res = ResolveTemperatureDevice(path, command, /*suppress_errors=*/true);
if (temp_res.is_ok()) {
return temp_res;
}
// Fallback to ADC
auto adc_res = ResolveAdcDevice(path, command, /*suppress_errors=*/true);
if (adc_res.is_ok()) {
return adc_res;
}
if (!path.empty()) {
printf("Failed to resolve device name '%.*s' as a temperature or ADC device\n",
static_cast<int>(path.size()), path.data());
} else {
printf("No temperature or ADC devices found.\n");
}
return temp_res.take_error();
}
// Fallback for any other command (e.g., name or unknown commands)
return ResolveTemperatureDevice(path, command);
}
std::vector<DeviceInfo> GetTemperatureDevicesForReading() {
auto devices = GetTemperatureDevices();
std::vector<DeviceInfo> result;
result.reserve(devices.size());
for (const auto& dev : devices) {
result.push_back({
.name = dev.name,
.path = GetPathForDevice(dev, kCmdRead),
});
}
return result;
}