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