| // 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 <cstdint> |
| #include <map> |
| #include <memory> |
| #include <optional> |
| #include <sstream> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/files/scoped_file.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/values.h> |
| #include <libec/get_version_command.h> |
| #include <libec/i2c_passthru_command.h> |
| |
| #include "runtime_probe/system/context.h" |
| #include "runtime_probe/utils/ec_component_manifest.h" |
| #include "runtime_probe/utils/ish_component_manifest.h" |
| |
| namespace runtime_probe { |
| |
| namespace { |
| constexpr int kEcCmdNumAttempts = 10; |
| constexpr char kCrosEcPath[] = "dev/cros_ec"; |
| constexpr char kCrosIshPath[] = "dev/cros_ish"; |
| constexpr int kPauseMicrosecsBetweenI2cCommands = 20 * 1000; |
| constexpr int kPauseMicrosecsBetweenComponents = 600 * 1000; |
| |
| bool IsMatchExpect(EcComponentManifest::Component::I2c::Expect expect, |
| base::span<const uint8_t> resp_data) { |
| if (expect.value->size() != resp_data.size()) { |
| LOG(WARNING) << "The response data length is different from the expect " |
| "value length."; |
| return false; |
| } |
| if (!expect.mask.has_value()) { |
| return expect.value == resp_data; |
| } |
| |
| for (int i = 0; i < resp_data.size(); i++) { |
| if ((resp_data[i] & (*expect.mask)[i]) != (*expect.value)[i]) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool RunI2cCommandAndCheckSuccess(const base::ScopedFD& ec_dev_fd, |
| ec::I2cPassthruCommand* cmd) { |
| bool result = |
| cmd->RunWithMultipleAttempts(ec_dev_fd.get(), kEcCmdNumAttempts) && |
| !cmd->I2cStatus(); |
| Context::Get()->syscaller()->Usleep(kPauseMicrosecsBetweenI2cCommands); |
| return result; |
| } |
| |
| std::string GenerateComponentLogLabel( |
| const EcComponentManifest::Component& comp) { |
| std::stringstream string_builder; |
| string_builder << "EC component " << comp.component_type << ":" |
| << comp.component_name << " on i2c port " |
| << static_cast<int>(comp.i2c.port) << " addr 0x" |
| << base::HexEncode({comp.i2c.addr}); |
| return string_builder.str(); |
| } |
| |
| std::string GenerateExpectI2cCommandLogLabel( |
| const EcComponentManifest::Component::I2c::Expect& expect) { |
| std::stringstream string_builder; |
| string_builder << "i2cxfer command reg=0x" << base::HexEncode({expect.reg}) |
| << " write_data=0x" << base::HexEncode(expect.write_data); |
| if (expect.override_addr.has_value()) { |
| string_builder << " override_addr=0x" |
| << base::HexEncode({*expect.override_addr}); |
| } |
| return string_builder.str(); |
| } |
| |
| uint8_t GetI2cAddr(const EcComponentManifest::Component& comp, |
| const EcComponentManifest::Component::I2c::Expect& expect) { |
| if (expect.override_addr.has_value()) { |
| return *expect.override_addr; |
| } |
| return comp.i2c.addr; |
| } |
| |
| } // namespace |
| |
| class EcComponentFunction::CommandSequenceHistoryTracker { |
| public: |
| class I2cCommandRunRecord; |
| |
| I2cCommandRunRecord* LookupRunRecord( |
| const EcComponentManifest::Component& comp, |
| const EcComponentManifest::Component::I2c::Expect& expect); |
| |
| I2cCommandRunRecord* RegisterRunRecord( |
| const EcComponentManifest::Component& comp, |
| const EcComponentManifest::Component::I2c::Expect& expect, |
| std::unique_ptr<ec::I2cPassthruCommand> cmd, |
| bool is_cmd_success); |
| |
| private: |
| struct RecordKey { |
| static RecordKey Create( |
| const EcComponentManifest::Component& comp, |
| const EcComponentManifest::Component::I2c::Expect& expect) { |
| const auto addr = GetI2cAddr(comp, expect); |
| return {.port = comp.i2c.port, |
| .addr7 = addr, |
| .offset = expect.reg, |
| .read_len = static_cast<uint8_t>(expect.bytes), |
| .write_data = expect.write_data}; |
| } |
| uint8_t port; |
| uint8_t addr7; |
| uint8_t offset; |
| uint8_t read_len; |
| std::vector<uint8_t> write_data; |
| |
| bool operator<(const RecordKey& rhs) const { |
| if (port != rhs.port) { |
| return port < rhs.port; |
| } |
| if (addr7 != rhs.addr7) { |
| return addr7 < rhs.addr7; |
| } |
| if (offset != rhs.offset) { |
| return offset < rhs.offset; |
| } |
| if (read_len != rhs.read_len) { |
| return read_len < rhs.read_len; |
| } |
| return write_data < rhs.write_data; |
| } |
| }; |
| std::map<RecordKey, std::unique_ptr<I2cCommandRunRecord>> run_records_; |
| }; |
| |
| class EcComponentFunction::CommandSequenceHistoryTracker::I2cCommandRunRecord { |
| public: |
| explicit I2cCommandRunRecord(std::unique_ptr<ec::I2cPassthruCommand> cmd, |
| bool is_cmd_success) |
| : cmd_(std::move(cmd)), is_cmd_success_(is_cmd_success), next_() {} |
| |
| ec::I2cPassthruCommand* cmd() { return cmd_.get(); } |
| bool is_cmd_success() { return is_cmd_success_; } |
| CommandSequenceHistoryTracker* next() { return &next_; } |
| |
| private: |
| std::unique_ptr<ec::I2cPassthruCommand> cmd_; |
| bool is_cmd_success_; |
| EcComponentFunction::CommandSequenceHistoryTracker next_; |
| }; |
| |
| EcComponentFunction::CommandSequenceHistoryTracker::I2cCommandRunRecord* |
| EcComponentFunction::CommandSequenceHistoryTracker::LookupRunRecord( |
| const EcComponentManifest::Component& comp, |
| const EcComponentManifest::Component::I2c::Expect& expect) { |
| const auto key = RecordKey::Create(comp, expect); |
| auto it = run_records_.find(key); |
| if (it == run_records_.end()) { |
| return nullptr; |
| } |
| return it->second.get(); |
| } |
| |
| EcComponentFunction::CommandSequenceHistoryTracker::I2cCommandRunRecord* |
| EcComponentFunction::CommandSequenceHistoryTracker::RegisterRunRecord( |
| const EcComponentManifest::Component& comp, |
| const EcComponentManifest::Component::I2c::Expect& expect, |
| std::unique_ptr<ec::I2cPassthruCommand> cmd, |
| bool is_cmd_success) { |
| const auto key = RecordKey::Create(comp, expect); |
| auto emplace_result = run_records_.insert_or_assign( |
| key, |
| std::make_unique<I2cCommandRunRecord>(std::move(cmd), is_cmd_success)); |
| return emplace_result.first->second.get(); |
| } |
| |
| std::unique_ptr<ec::I2cPassthruCommand> EcComponentFunction::GetI2cReadCommand( |
| uint8_t port, |
| uint8_t addr7, |
| uint8_t offset, |
| const std::vector<uint8_t>& write_data, |
| uint8_t read_len) const { |
| std::vector<uint8_t> passthru_data(1, offset); |
| passthru_data.insert(passthru_data.end(), write_data.begin(), |
| write_data.end()); |
| return ec::I2cPassthruCommand::Create(port, addr7, passthru_data, read_len); |
| } |
| |
| std::unique_ptr<ec::I2cPassthruCommand> EcComponentFunction::GetI2cReadCommand( |
| const EcComponentManifest::Component& comp) const { |
| // No expect value. Just verify the accessibility of the component. |
| return GetI2cReadCommand(comp.i2c.port, comp.i2c.addr, 0u, {}, 1u); |
| } |
| |
| std::unique_ptr<ec::I2cPassthruCommand> EcComponentFunction::GetI2cReadCommand( |
| const EcComponentManifest::Component& comp, |
| const EcComponentManifest::Component::I2c::Expect& expect) const { |
| return GetI2cReadCommand(comp.i2c.port, GetI2cAddr(comp, expect), expect.reg, |
| expect.write_data, expect.bytes); |
| } |
| |
| 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::WakeUpComponent( |
| const EcComponentManifest::Component& comp, |
| const base::ScopedFD& ec_dev_fd) const { |
| auto comp_label = GenerateComponentLogLabel(comp); |
| std::unique_ptr<ec::I2cPassthruCommand> wakeup_cmd; |
| if (comp.i2c.expect.size() == 0) { |
| wakeup_cmd = GetI2cReadCommand(comp); |
| if (!wakeup_cmd) { |
| LOG(ERROR) << "Failed to construct the EC i2cxfer command for " |
| << "accessibility check for " << comp_label; |
| return false; |
| } |
| } else { |
| const auto& expect = comp.i2c.expect[0]; |
| wakeup_cmd = GetI2cReadCommand(comp, expect); |
| auto i2c_cmd_label = GenerateExpectI2cCommandLogLabel(expect); |
| if (!wakeup_cmd) { |
| LOG(ERROR) << "Failed to construct " << i2c_cmd_label << " for " |
| << comp_label; |
| return false; |
| } |
| } |
| RunI2cCommandAndCheckSuccess(ec_dev_fd, wakeup_cmd.get()); |
| return true; |
| } |
| |
| bool EcComponentFunction::IsValidComponent( |
| const EcComponentManifest::Component& comp, |
| const base::ScopedFD& ec_dev_fd, |
| CommandSequenceHistoryTracker* tracker, |
| bool use_cached_invocations) const { |
| auto comp_label = GenerateComponentLogLabel(comp); |
| VLOG(1) << "Probing " << comp_label; |
| |
| if (comp.probe_strategy == EcComponentManifest::ProbeStrategy::WAKE_UP && |
| (!use_cached_invocations || comp.i2c.expect.size() == 0)) { |
| if (!WakeUpComponent(comp, ec_dev_fd)) { |
| return false; |
| } |
| } |
| |
| if (comp.i2c.expect.size() == 0) { |
| auto cmd = GetI2cReadCommand(comp); |
| if (!cmd) { |
| LOG(ERROR) << "Failed to construct the EC i2cxfer command for " |
| << "accessibility check for " << comp_label; |
| return false; |
| } |
| bool success = RunI2cCommandAndCheckSuccess(ec_dev_fd, cmd.get()); |
| VLOG(1) << comp_label << (success ? " probed" : " not probed") |
| << " per the accessibility of that address"; |
| return success; |
| } |
| |
| auto curr_tracker = tracker; |
| for (const auto& expect : comp.i2c.expect) { |
| auto i2c_cmd_label = GenerateExpectI2cCommandLogLabel(expect); |
| |
| auto run_record = curr_tracker->LookupRunRecord(comp, expect); |
| if (use_cached_invocations) { |
| if (run_record == nullptr) { |
| // The command hasn't been run, we should run through the whole command |
| // sequence. |
| bool result = IsValidComponent(comp, ec_dev_fd, tracker, false); |
| Context::Get()->syscaller()->Usleep(kPauseMicrosecsBetweenComponents); |
| return result; |
| } |
| i2c_cmd_label = "(cached) " + i2c_cmd_label; |
| } else { |
| auto cmd = GetI2cReadCommand(comp, expect); |
| if (!cmd) { |
| LOG(ERROR) << "Failed to construct " << i2c_cmd_label << " for " |
| << comp_label; |
| return false; |
| } |
| bool success = RunI2cCommandAndCheckSuccess(ec_dev_fd, cmd.get()); |
| if (run_record == nullptr || run_record->is_cmd_success() != success) { |
| run_record = curr_tracker->RegisterRunRecord(comp, expect, |
| std::move(cmd), success); |
| } |
| } |
| curr_tracker = run_record->next(); |
| |
| if (!run_record->is_cmd_success()) { |
| VLOG(1) << comp_label << " not probed because " << i2c_cmd_label |
| << " failed"; |
| return false; |
| } |
| if (!expect.value.has_value()) { |
| VLOG(1) << comp_label << " passed the expect rule: " << i2c_cmd_label |
| << " succeeded"; |
| continue; |
| } |
| if (!IsMatchExpect(expect, run_record->cmd()->RespData())) { |
| VLOG(1) << comp_label << " not probed because " << i2c_cmd_label |
| << " responded unmatched data 0x" |
| << base::HexEncode(run_record->cmd()->RespData()); |
| return false; |
| } |
| VLOG(1) << comp_label << " passed the expect rule: " << i2c_cmd_label |
| << " responded matched data 0x" |
| << base::HexEncode(run_record->cmd()->RespData()); |
| } |
| VLOG(1) << comp_label << " probed because it passed all expect rules"; |
| return true; |
| } |
| |
| template <typename ManifestReader> |
| EcComponentFunction::DataType EcComponentFunction::ProbeWithManifest( |
| const std::optional<std::string>& manifest_path, |
| const std::string_view dev_path) const { |
| const auto path = Context::Get()->root_dir().Append(dev_path); |
| if (!base::PathExists(path)) { |
| VLOG(1) << path << " doesn't exist."; |
| return {}; |
| } |
| auto dev_fd = base::ScopedFD(open(path.value().c_str(), O_RDWR)); |
| |
| auto ec_version = GetCurrentECVersion(dev_fd); |
| if (!ec_version.has_value()) { |
| LOG(ERROR) << "Failed to get EC version for device \"" << path << "\"."; |
| return {}; |
| } |
| |
| ManifestReader manifest_reader(ec_version.value()); |
| std::optional<EcComponentManifest> manifest; |
| if (manifest_path) { |
| manifest = manifest_reader.ReadFromFilePath(base::FilePath(*manifest_path)); |
| } else { |
| manifest = manifest_reader.Read(); |
| } |
| |
| if (!manifest) { |
| LOG(ERROR) << "Get component manifest failed."; |
| return {}; |
| } |
| |
| CommandSequenceHistoryTracker history_tracker; |
| 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, dev_fd, &history_tracker, true)) { |
| result.Append(base::DictValue() |
| .Set("component_type", comp.component_type) |
| .Set("component_name", comp.component_name)); |
| } |
| } |
| return result; |
| } |
| |
| bool EcComponentFunction::PostParseArguments() { |
| if ((manifest_path_ || ish_manifest_path_) && |
| !Context::Get()->factory_mode()) { |
| LOG(ERROR) << "manifest_path and ish_manifest_path can only be set in " |
| "factory_runtime_probe."; |
| return false; |
| } |
| return true; |
| } |
| |
| EcComponentFunction::DataType EcComponentFunction::EvalImpl() const { |
| DataType results{}; |
| |
| auto ec_result = |
| ProbeWithManifest<EcComponentManifestReader>(manifest_path_, kCrosEcPath); |
| for (auto& res : ec_result) { |
| results.Append(std::move(res)); |
| } |
| |
| auto ish_result = ProbeWithManifest<IshComponentManifestReader>( |
| ish_manifest_path_, kCrosIshPath); |
| for (auto& res : ish_result) { |
| results.Append(std::move(res)); |
| } |
| |
| return results; |
| } |
| |
| } // namespace runtime_probe |