blob: 81c7dc218a0d20829648d9fcf81efd4eacd6acde [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 "runtime_probe/utils/bus_utils.h"
#include <optional>
#include <string>
#include <string_view>
#include <base/containers/fixed_flat_map.h>
#include <base/containers/span.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/values.h>
#include "runtime_probe/utils/file_utils.h"
#include "runtime_probe/utils/type_utils.h"
#include "runtime_probe/utils/value_utils.h"
namespace runtime_probe {
namespace {
constexpr auto kPciFields =
base::MakeFixedFlatMap<std::string_view, std::string_view>(
{{"vendor_id", "vendor"}, {"device_id", "device"}, {"class", "class"}});
constexpr auto kPciOptionalFields =
base::MakeFixedFlatMap<std::string_view, std::string_view>(
{{"revision", "revision"}, {"subsystem", "subsystem_device"}});
constexpr auto kSdioFields =
base::MakeFixedFlatMap<std::string_view, std::string_view>(
{{"vendor_id", "vendor"}, {"device_id", "device"}});
constexpr auto kUsbFields =
base::MakeFixedFlatMap<std::string_view, std::string_view>(
{{"vendor_id", "idVendor"}, {"product_id", "idProduct"}});
constexpr auto kUsbOptionalFields =
base::MakeFixedFlatMap<std::string_view, std::string_view>(
{{"bcd_device", "bcdDevice"}, {"removable", "removable"}});
constexpr int PCI_REVISION_ID_OFFSET = 0x08;
// For linux kernels of versions before 4.10-rc1, there is no standalone file
// `revision` describing the revision id of the PCI component. The revision is
// still available at offset 8 of the binary file `config`.
std::optional<uint8_t> GetPciRevisionIdFromConfig(base::FilePath node_path) {
const auto file_path = node_path.Append("config");
if (!base::PathExists(file_path)) {
LOG(ERROR) << file_path.value() << " doesn't exist.";
return std::nullopt;
}
base::File config{file_path, base::File::FLAG_OPEN | base::File::FLAG_READ};
uint8_t revision_array[1];
base::span<uint8_t> revision_span(revision_array);
if (!config.ReadAndCheck(PCI_REVISION_ID_OFFSET, revision_span)) {
LOG(ERROR) << "Cannot read file " << file_path << " at offset "
<< PCI_REVISION_ID_OFFSET;
return std::nullopt;
}
return revision_array[0];
}
std::optional<base::Value> GetDeviceBusDataFromSysfsDeviceNode(
const base::FilePath& node_path, const base::FilePath& dev_path) {
const auto dev_subsystem_path = dev_path.Append("subsystem");
base::FilePath dev_subsystem_link_path;
if (!base::ReadSymbolicLink(dev_subsystem_path, &dev_subsystem_link_path)) {
VLOG(2) << "Cannot get real path of " << dev_subsystem_path;
return std::nullopt;
}
std::string bus_type = dev_subsystem_link_path.BaseName().value();
std::optional<base::Value> res;
if (bus_type == "pci") {
res = MapFilesToDict(dev_path, kPciFields, kPciOptionalFields);
if (res && !res->GetDict().FindString("revision")) {
auto revision_id = GetPciRevisionIdFromConfig(dev_path);
if (revision_id) {
res->GetDict().Set("revision", ByteToHexString(*revision_id));
}
}
} else if (bus_type == "sdio") {
res = MapFilesToDict(dev_path, kSdioFields);
} else if (bus_type == "usb") {
auto field_path = base::MakeAbsoluteFilePath(dev_path.Append(".."));
res = MapFilesToDict(field_path, kUsbFields, kUsbOptionalFields);
} else if (bus_type == "platform") {
// Try to find a parent device as the device bus info. See b/307870105 for
// example.
VLOG(2) << "Found a platform device at " << node_path
<< ". Try to get its parent device.";
const auto perent_dev_path =
base::MakeAbsoluteFilePath(dev_path.Append(".."));
auto parent_dev =
GetDeviceBusDataFromSysfsDeviceNode(node_path, perent_dev_path);
if (!parent_dev) {
VLOG(2) << "Path " << node_path
<< " has bus type \"platform\", which usually means it is a "
"device bound with SoC. Ignore it.";
return std::nullopt;
}
VLOG(2) << "Found a parent device at " << perent_dev_path;
return parent_dev;
} else if (bus_type == "wwan") {
// Some wwan devices in /sys/class/net point to wwan subsystem, not the
// real device. Find the real device by checking the `device` link.
const auto real_dev_path =
base::MakeAbsoluteFilePath(dev_path.Append("device"));
return GetDeviceBusDataFromSysfsDeviceNode(node_path, real_dev_path);
} else {
LOG(ERROR) << "Unknown bus_type " << bus_type;
return std::nullopt;
}
if (!res) {
LOG(ERROR) << "Cannot find " << bus_type << "-specific fields from \""
<< dev_path << "\"";
return std::nullopt;
}
PrependToDVKey(&*res, bus_type + "_");
res->GetDict().Set("bus_type", bus_type);
CHECK(!res->GetDict().FindString("path"));
res->GetDict().Set("path", node_path.value());
return res;
}
} // namespace
bool IsRemovableBusDevice(const base::Value::Dict& result) {
// Currently the "removable" attribute is only available for USB devices. We
// might need to revise the filter once we have removable PCI devices in
// ChromeOS.
const auto* removable = result.FindString("usb_removable");
return (removable != nullptr && *removable != "fixed");
}
std::optional<base::Value> GetDeviceBusDataFromSysfsNode(
const base::FilePath& node_path) {
const auto dev_path = node_path.Append("device");
return GetDeviceBusDataFromSysfsDeviceNode(node_path, dev_path);
}
std::optional<base::Value> GetDeviceBusDataFromSysfsNode(
const base::FilePath& node_path, bool is_fixed) {
auto res = GetDeviceBusDataFromSysfsNode(node_path);
if (is_fixed && res.has_value() && IsRemovableBusDevice(res->GetDict())) {
return std::nullopt;
}
return res;
}
} // namespace runtime_probe