blob: 10cf17ef754c0667ad76f58933df0261e5e0b40e [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/functions/ec_component.h"
#include <fcntl.h>
#include <memory>
#include <string>
#include <vector>
#include <base/files/file_path.h>
#include <base/files/scoped_file.h>
#include <base/values.h>
#include <libec/get_version_command.h>
#include <libec/i2c_read_command.h>
#include "runtime_probe/system/context.h"
#include "runtime_probe/utils/ec_component_manifest.h"
namespace runtime_probe {
namespace {
constexpr int kEcCmdNumAttempts = 10;
} // namespace
base::ScopedFD EcComponentFunction::GetEcDevice() const {
return base::ScopedFD(open(ec::kCrosEcPath, O_RDWR));
}
std::unique_ptr<ec::I2cReadCommand> EcComponentFunction::GetI2cReadCommand(
uint8_t port, uint8_t addr8, uint8_t offset, uint8_t read_len) const {
return ec::I2cReadCommand::Create(port, addr8, offset, read_len);
}
std::unique_ptr<ec::GetVersionCommand>
EcComponentFunction::GetGetVersionCommand() const {
return std::make_unique<ec::GetVersionCommand>();
}
std::optional<std::string> EcComponentFunction::GetCurrentECVersion(
const base::ScopedFD& ec_dev_fd) const {
auto cmd = GetGetVersionCommand();
if (!cmd->RunWithMultipleAttempts(ec_dev_fd.get(), kEcCmdNumAttempts)) {
LOG(ERROR) << "Failed to get EC version.";
return std::nullopt;
}
switch (cmd->Image()) {
case EC_IMAGE_UNKNOWN:
LOG(ERROR) << "Got unknown EC image.";
return std::nullopt;
case EC_IMAGE_RO:
case EC_IMAGE_RO_B:
LOG(WARNING) << "EC is currently running RO image.";
return cmd->ROVersion();
case EC_IMAGE_RW:
case EC_IMAGE_RW_B:
return cmd->RWVersion();
}
}
bool EcComponentFunction::IsValidComponent(
const EcComponentManifest::Component& comp,
const base::ScopedFD& ec_dev_fd) const {
// |addr| in component manifest is a 7-bit address, where
// ec::I2cReadCommand::Create() takes 8-bit address, so we convert addresses
// accordingly.
const int addr8 = comp.i2c.addr << 1;
if (comp.i2c.expect.size() == 0) {
// No expect value. Just verify the accessibility of the component.
auto cmd = GetI2cReadCommand(comp.i2c.port, addr8, 0u, 1u);
if (cmd &&
cmd->RunWithMultipleAttempts(ec_dev_fd.get(), kEcCmdNumAttempts) &&
!cmd->I2cStatus()) {
return true;
}
}
for (const auto& expect : comp.i2c.expect) {
auto cmd = GetI2cReadCommand(comp.i2c.port, addr8, expect.reg, 1u);
if (cmd &&
cmd->RunWithMultipleAttempts(ec_dev_fd.get(), kEcCmdNumAttempts) &&
!cmd->I2cStatus()) {
if (expect.value == cmd->Data()) {
return true;
}
}
}
return false;
}
bool EcComponentFunction::PostParseArguments() {
if (manifest_path_ && !Context::Get()->factory_mode()) {
LOG(ERROR) << "manifest_path can only be set in factory_runtime_probe.";
return false;
}
return true;
}
EcComponentFunction::DataType EcComponentFunction::EvalImpl() const {
base::ScopedFD ec_dev = GetEcDevice();
std::optional<EcComponentManifest> manifest;
if (manifest_path_) {
manifest = EcComponentManifestReader::ReadFromFilePath(
base::FilePath(manifest_path_.value()));
} else {
manifest = EcComponentManifestReader::Read();
}
if (!manifest) {
LOG(ERROR) << "Get component manifest failed.";
return {};
}
auto ec_version = GetCurrentECVersion(ec_dev);
if (ec_version != manifest->ec_version) {
LOG(ERROR) << "Current EC version \"" << ec_version.value_or("std::nullopt")
<< "\" doesn't match manifest version \"" << manifest->ec_version
<< "\".";
return {};
}
DataType result{};
for (const auto& comp : manifest->component_list) {
// If type_ or name_ is set, skip those component which doesn't match the
// specified type / name.
if ((type_ && comp.component_type != type_) ||
(name_ && comp.component_name != name_)) {
continue;
}
if (IsValidComponent(comp, ec_dev)) {
result.Append(base::Value::Dict()
.Set("component_type", comp.component_type)
.Set("component_name", comp.component_name));
}
}
return result;
}
} // namespace runtime_probe