blob: 5f5cba684d005a2e633221c19ce6edd9ac248b95 [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 <utility>
#include <vector>
#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/strings/string_piece.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<base::StringPiece, base::StringPiece>(
{{"vendor_id", "vendor"}, {"device_id", "device"}});
constexpr auto kPciOptionalFields =
base::MakeFixedFlatMap<base::StringPiece, base::StringPiece>(
{{"revision", "revision"}, {"subsystem", "subsystem_device"}});
constexpr auto kSdioFields =
base::MakeFixedFlatMap<base::StringPiece, base::StringPiece>(
{{"vendor_id", "vendor"}, {"device_id", "device"}});
constexpr auto kUsbFields =
base::MakeFixedFlatMap<base::StringPiece, base::StringPiece>(
{{"vendor_id", "idVendor"}, {"product_id", "idProduct"}});
constexpr auto kUsbOptionalFields =
base::MakeFixedFlatMap<base::StringPiece, base::StringPiece>(
{{"bcd_device", "bcdDevice"}});
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];
}
} // namespace
std::optional<base::Value> GetDeviceBusDataFromSysfsNode(
const base::FilePath& node_path) {
const auto dev_path = node_path.Append("device");
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") {
VLOG(2) << "Path " << node_path
<< " has bus type \"platform\", which usually means it is a device "
"bound with SoC. Ignore it.";
return std::nullopt;
} 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 runtime_probe