blob: 91f062b4dffec58733656f22dde0322fd5646633 [file]
#include "tlbmc/hal/fru_scanner_i2c.h"
#include <endian.h>
#include <fcntl.h>
#include <linux/i2c.h>
#include <stdbool.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include <array>
#include <cerrno>
#include <charconv>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <filesystem> // NOLINT
#include <iomanip>
#include <ios>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <system_error> // NOLINT
#include <utility>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/log/log.h"
#include "absl/memory/memory.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/strings/substitute.h"
#include "absl/time/time.h"
#include "absl/types/span.h"
#include "boost/filesystem.hpp" // NOLINT
#include "boost/filesystem/operations.hpp" // NOLINT
#include "boost/filesystem/path.hpp" // NOLINT
#include "boost/system/error_code.hpp" // NOLINT
#include "apifs/apifs.h"
#include "io/smbus/smbus.h"
#include "g3/macros.h"
#include "time/clock.h"
#include "hal_common_config.pb.h"
#include "tlbmc/deterministic_bmc/i2c_walker/i2c_walker_interface.h"
#include "tlbmc/hal/fru_scanner.h"
#include "tlbmc/sensors/ixc_hwmon_based_sensor.h"
#include "tlbmc/utils/fru_reader.h"
#include "tlbmc/utils/fru_utils.h"
#include "re2/re2.h"
namespace milotic_tlbmc {
using SmbusLocation = ecclesia::SmbusLocation;
namespace {
namespace fs = std::filesystem;
// Regex to extract the address from i2c file path.
// Example: /sys/bus/i2c/devices/i2c-12/12-0050 -> 0050
constexpr LazyRE2 kAddressRegex = {".+\\d+-([0-9abcdef]+$)"};
// Regex to match numeric I2C bus entries and extract the bus number.
// Example: /dev/i2c-12 -> 12
constexpr LazyRE2 kI2cBusNameRegex = {".*/i2c-(\\d+)$"};
// Size of common header in FRU contents.
constexpr size_t kFruCommonHeaderSize = 8;
// Found I2c bus list under given directory path. /dev is the canonical used
// here.
absl::StatusOr<absl::flat_hash_set<int>> GetI2cBusList(
absl::string_view dir_path) {
absl::flat_hash_set<int> bus_list;
ecclesia::ApifsDirectory apifs((std::string(dir_path)));
ECCLESIA_ASSIGN_OR_RETURN(auto entries, apifs.ListEntryPaths());
for (const auto& entry : entries) {
int bus = 0;
if (!RE2::FullMatch(entry, *kI2cBusNameRegex, &bus)) {
continue;
}
bus_list.insert(bus);
}
if (bus_list.empty()) {
return absl::InternalError("No i2c buses found in directory");
}
return bus_list;
}
template <typename Func>
absl::Status RetryI2cOp(Func&& op, int max_retries, absl::Duration delay,
ecclesia::Clock* clock) {
absl::Status status;
for (int attempt = 1; attempt <= max_retries; ++attempt) {
status = op();
if (status.ok()) {
return absl::OkStatus();
}
if (attempt < max_retries && delay > absl::ZeroDuration()) {
clock->Sleep(delay);
}
}
return status;
}
// Reads from eeprom file at given offset and returns the number of bytes
// read.
absl::StatusOr<int64_t> ReadFromEeprom(int fd, off_t offset, size_t len,
uint8_t* buf) {
auto result = lseek(fd, offset, SEEK_SET);
if (result < 0) {
LOG(ERROR) << "Failed to seek to offset " << offset << " in eeprom file";
return absl::InternalError("Failed to seek to offset");
};
auto bytes_read = read(fd, buf, len);
if (bytes_read < 0) {
LOG(ERROR) << "Failed to read from eeprom file";
return absl::InternalError("Failed to read from eeprom file");
}
return bytes_read;
}
std::string GetEepromPath(size_t bus, size_t address,
absl::string_view root_dir) {
std::stringstream output;
output << root_dir << "/sys/bus/i2c/devices/" << bus << "-" << std::right
<< std::setfill('0') << std::setw(4) << std::hex << address
<< "/eeprom";
return output.str();
}
std::vector<uint8_t> ProcessEeprom(int bus, size_t address,
absl::string_view root_dir,
bool* parsing_error = nullptr) {
auto path = GetEepromPath(bus, address, root_dir);
int file = open(path.c_str(), O_RDONLY);
if (file < 0) {
LOG(ERROR) << "Failed to open eeprom file: " << path << " "
<< strerror(errno);
return {};
}
std::string error_message = "eeprom at " + std::to_string(bus) + " address " +
std::to_string(address);
FRUReader reader(
[file](off_t offset, size_t length, uint8_t* outbuf) -> int64_t {
absl::StatusOr<int64_t> bytes_read =
ReadFromEeprom(file, offset, length, outbuf);
if (!bytes_read.ok()) {
return -1;
}
return bytes_read.value();
});
std::pair<std::vector<uint8_t>, bool> pair =
readFRUContents(reader, error_message);
close(file);
if (pair.first.empty()) {
LOG(ERROR) << "Failed to read or parse eeprom at " << bus << " address "
<< address;
if (parsing_error != nullptr) {
LOG(ERROR) << "ProcessEeprom: Setting parsing error to true for bus "
<< bus << " address " << address;
*parsing_error = true;
}
return {};
}
return pair.first;
}
int GetRootBus(size_t bus, absl::string_view root_dir) {
auto ec = std::error_code();
auto path = std::filesystem::read_symlink(
std::filesystem::path(absl::StrCat(root_dir, "/sys/bus/i2c/devices/i2c-",
bus, "/mux_device")),
ec);
if (ec) {
return -1;
}
std::string filename = path.filename();
auto bus_iter = filename.find('-');
if (bus_iter == std::string::npos) {
return -1;
}
int root_bus = 0;
return absl::SimpleAtoi(filename.substr(0, bus_iter), &root_bus) ? root_bus
: -1;
}
} // namespace
FruScannerI2c::AddressingMode FruScannerI2c::GetDeviceAddressingMode(
const SmbusLocation& location) const {
// Try 8-bit reading first. Read the first kFruCommonHeaderSize bytes
// (enough for common header).
std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> block_data{};
bool read_8bit_ok = false;
absl::Span<unsigned char> block_span(block_data.data(), kFruCommonHeaderSize);
size_t bytes_read = 0;
if (smbus_access_->ReadBlockI2C(location, 0, block_span, &bytes_read).ok() &&
bytes_read == kFruCommonHeaderSize) {
read_8bit_ok = true;
} else {
read_8bit_ok = true;
for (size_t i = 0; i < kFruCommonHeaderSize; ++i) {
if (!smbus_access_->Read8(location, static_cast<int>(i), &block_data[i])
.ok()) {
read_8bit_ok = false;
break;
}
}
}
if (read_8bit_ok && validateHeader(block_data)) {
// Valid header read via 8-bit. It is definitely an 8-bit device.
return AddressingMode::k8Bit;
}
// Try 16-bit reading using our new ReadBlock16BitAddr
bytes_read = 0;
if (smbus_access_->ReadBlock16BitAddr(location, 0, block_span, &bytes_read)
.ok() &&
bytes_read == kFruCommonHeaderSize) {
if (validateHeader(block_data)) {
// Valid header read via 16-bit. It is definitely a 16-bit device!
return AddressingMode::k16Bit;
}
}
// Fallback: if both failed to yield a valid header, default to unknown.
return AddressingMode::kUnknown;
}
std::pair<std::vector<uint8_t>, bool> FruScannerI2c::ReadFruContentsFromI2c(
int i2c_bus, int address, absl::Duration delay_between_reads,
bool* parsing_error) const {
std::optional<SmbusLocation> location =
SmbusLocation::TryMake(i2c_bus, address);
if (!location.has_value()) {
return {{}, false};
}
constexpr int kMaxRetries = 10;
constexpr int kProbeRetries = 3;
uint8_t data;
// Probe to see if this address is valid, with retries.
// Probe failures are expected for empty addresses, so we don't log them.
absl::Status status = RetryI2cOp(
[this, &location, &data]() {
return smbus_access_->ReceiveByte(*location, &data);
},
kProbeRetries, delay_between_reads, clock_);
if (!status.ok()) {
return {{}, false};
}
bool is_16bit = false;
status = RetryI2cOp(
[this, &location, &is_16bit]() -> absl::Status {
AddressingMode mode = GetDeviceAddressingMode(*location);
if (mode == AddressingMode::kUnknown) {
return absl::NotFoundError("Unknown addressing mode");
}
is_16bit = (mode == AddressingMode::k16Bit);
return absl::OkStatus();
},
kMaxRetries, delay_between_reads, clock_);
if (!status.ok()) {
LOG(WARNING) << "Device at bus " << i2c_bus << " address " << address
<< " has unknown addressing mode (invalid FRU header) after "
<< kMaxRetries << " retries.";
if (parsing_error != nullptr) {
LOG(WARNING) << "ReadFruContentsFromI2c: Setting parsing error to true "
"for bus "
<< i2c_bus << " address " << address;
*parsing_error = true;
}
return {{}, false};
}
LOG(INFO) << "Device at bus " << i2c_bus << " address " << address
<< " is 16-bit: " << is_16bit;
std::string error_message =
absl::Substitute("i2c scan at $0 address $1", i2c_bus, address);
FRUReader reader([this, location, is_16bit, delay_between_reads](
off_t offset, size_t length,
uint8_t* outbuf) -> int64_t {
absl::Span<uint8_t> outbuf_span(outbuf, length);
DLOG(INFO) << "Reading from i2c at offset " << offset << " length "
<< length;
if (is_16bit) {
return Read16BitFru(*location, offset, outbuf_span, delay_between_reads);
}
return Read8BitFru(*location, offset, outbuf_span, delay_between_reads);
});
std::pair<std::vector<uint8_t>, bool> pair =
readFRUContents(reader, error_message);
if (!pair.second) {
LOG(WARNING) << "Failed to parse FRU contents for device at bus " << i2c_bus
<< " address " << address;
if (parsing_error != nullptr) {
LOG(WARNING) << "ReadFruContentsFromI2c: Setting parsing error to true "
"for bus "
<< i2c_bus << " address " << address;
*parsing_error = true;
}
}
return pair;
}
int64_t FruScannerI2c::Read16BitFru(const ecclesia::SmbusLocation& location,
off_t offset, absl::Span<uint8_t> outbuf,
absl::Duration delay_between_reads) const {
constexpr int kMaxRetries = 10;
size_t bytes_read = 0;
absl::Status status = RetryI2cOp(
[this, &location, offset, outbuf, &bytes_read]() {
bytes_read = 0;
return smbus_access_->ReadBlock16BitAddr(
location, static_cast<int>(offset), outbuf, &bytes_read);
},
kMaxRetries, delay_between_reads, clock_);
if (!status.ok()) {
LOG(INFO) << "16-bit read at offset " << offset << " failed: " << status;
return -1;
}
if (delay_between_reads > absl::ZeroDuration()) {
clock_->Sleep(delay_between_reads);
}
return static_cast<int64_t>(bytes_read);
}
int64_t FruScannerI2c::Read8BitFru(const ecclesia::SmbusLocation& location,
off_t offset, absl::Span<uint8_t> outbuf,
absl::Duration delay_between_reads) const {
constexpr int kMaxRetries = 10;
size_t bytes_read = 0;
absl::Status status = RetryI2cOp(
[this, &bytes_read, outbuf, &location, offset]() -> absl::Status {
bytes_read = 0;
while (bytes_read < outbuf.size()) {
size_t chunk_len = std::min(outbuf.size() - bytes_read,
static_cast<size_t>(I2C_SMBUS_BLOCK_MAX));
absl::Span<unsigned char> chunk_span(
reinterpret_cast<unsigned char*>(outbuf.data() + bytes_read),
chunk_len);
size_t chunk_bytes_read = 0;
absl::Status chunk_status = smbus_access_->ReadBlockI2C(
location, static_cast<int>(offset + bytes_read), chunk_span,
&chunk_bytes_read);
if (!chunk_status.ok()) {
return chunk_status;
}
bytes_read += chunk_bytes_read;
if (chunk_bytes_read < chunk_len) {
break;
}
}
return absl::OkStatus();
},
kMaxRetries, delay_between_reads, clock_);
if (!status.ok()) {
LOG(INFO) << "8-bit block read failed: " << status;
return -1;
}
if (delay_between_reads > absl::ZeroDuration()) {
clock_->Sleep(delay_between_reads);
}
return static_cast<int64_t>(bytes_read);
}
absl::StatusOr<std::unique_ptr<I2cFruInfo>> FruScannerI2c::GetI2cFruInfoFromBus(
int bus, int address, absl::Duration delay_between_reads) const {
auto fru_info = std::make_unique<I2cFruInfo>();
fru_info->bus = bus;
fru_info->address = address;
std::vector<uint8_t> device =
ProcessEeprom(bus, address, root_dir_, &fru_info->parsing_error);
// If we get a device, then we don't need to manually scan.
if (!device.empty()) {
fru_info->data = std::move(device);
return fru_info;
}
// If the EEPROM file exists but parsing failed, skip the fallback. Since the
// device is managed by the kernel driver, direct I2C access will definitely
// fail with an EBUSY error.
if (fru_info->parsing_error) {
return fru_info;
}
return ScanDirectI2cFru(bus, address, delay_between_reads);
}
absl::StatusOr<std::unique_ptr<I2cFruInfo>> FruScannerI2c::ScanDirectI2cFru(
int bus, int address, absl::Duration delay_between_reads) const {
std::pair<std::vector<uint8_t>, bool> pair =
ReadFruContentsFromI2c(bus, address, delay_between_reads);
if (!pair.second) {
return absl::InternalError(
absl::StrCat("Failed to read eeprom at ", bus, " address ", address));
}
auto fru_info = std::make_unique<I2cFruInfo>();
fru_info->bus = bus;
fru_info->address = address;
fru_info->data = pair.first;
return fru_info;
}
absl::flat_hash_set<size_t> FruScannerI2c::FindI2cEeproms(
int i2cBus, const std::shared_ptr<DeviceMap>& devices,
absl::string_view root_dir,
const absl::flat_hash_set<size_t>& root_blocked_addresses,
absl::flat_hash_set<size_t>& root_found_addresses) const {
absl::flat_hash_set<size_t> found_list;
std::string path =
absl::StrCat(root_dir, "/sys/bus/i2c/devices/i2c-", i2cBus);
// For each file listed under the i2c device
// NOTE: This should be faster than just checking for each possible address
// path.
auto ec = std::error_code();
for (const auto& p : fs::directory_iterator(path, ec)) {
LOG(INFO) << "FindI2cEeproms: discovering p: " << p.path().string();
if (ec) {
LOG(ERROR) << "directory_iterator err " << ec.message();
break;
}
const std::string node = p.path().string();
std::string address_string;
if (!RE2::PartialMatch(node, *kAddressRegex, &address_string)) {
LOG(INFO) << "FindI2cEeproms: node file name not matched regex: " << node;
continue;
}
absl::string_view address_view(address_string);
size_t address = 0;
std::from_chars(address_view.begin(), address_view.end(), address, 16);
// Don't look for eeprom if the address is not in the range or extra
// addresses.
bool in_range = address >= kI2cFruScanStart && address <= kI2cFruScanEnd;
if (!in_range && !extra_fru_scan_addresses_set_.contains(address)) {
LOG(INFO) << "FindI2cEeproms: address 0x" << absl::Hex(address)
<< " is not in the range 0x" << absl::Hex(kI2cFruScanStart)
<< " to 0x" << absl::Hex(kI2cFruScanEnd)
<< " or extra scan addresses";
continue;
}
if (root_blocked_addresses.contains(address) ||
root_found_addresses.contains(address)) {
LOG(INFO) << "FindI2cEeproms: address 0x" << absl::Hex(address)
<< " is blocked or found at root bus";
continue;
}
const std::string eeprom = node + "/eeprom";
if (!fs::exists(eeprom, ec)) {
LOG(INFO) << "FindI2cEeproms: eeprom file does not exist: " << eeprom
<< " for address 0x" << absl::Hex(address);
continue;
}
// There is an eeprom file at this address, it may have invalid
// contents, but we found it.
LOG(INFO) << "FindI2cEeproms: successfully found eeprom file exists: "
<< eeprom << " for address 0x" << absl::Hex(address);
found_list.insert(address);
std::vector<uint8_t> device = ProcessEeprom(i2cBus, address, root_dir);
if (!device.empty()) {
devices->emplace(address, device);
} else {
LOG(INFO) << "FindI2cEeproms: device is empty for i2cBus " << i2cBus
<< " address 0x" << absl::Hex(address)
<< ". It means something is wrong reading the eeprom.";
}
}
// Scan every bus for the standard 0x50-0x57 range and extra addresses only.
// SoT:
// https://source.corp.google.com/h/gbmc/codesearch/+/main:meta-gbmc-staging/recipes-phosphor/configuration/entity-manager/0002-fru_device-limit-the-fru-scan-range-to-0x50-0x57.patch?q=fru%20device%20limit%20the%20fru%20scan%20range
std::vector<uint32_t> addresses_to_scan;
addresses_to_scan.reserve((kI2cFruScanEnd - kI2cFruScanStart + 1) +
extra_fru_scan_addresses_.size());
for (uint32_t address = kI2cFruScanStart; address <= kI2cFruScanEnd;
address++) {
addresses_to_scan.push_back(address);
}
for (uint32_t extra_addr : extra_fru_scan_addresses_) {
if (extra_addr < kI2cFruScanStart || extra_addr > kI2cFruScanEnd) {
addresses_to_scan.push_back(extra_addr);
}
}
for (uint32_t address : addresses_to_scan) {
if (found_list.contains(address) ||
root_blocked_addresses.contains(address) ||
root_found_addresses.contains(address)) {
LOG(INFO) << "FindI2cEeproms: scan 0x" << absl::Hex(address)
<< " is blocked or found at root bus, continue";
continue;
}
std::pair<std::vector<uint8_t>, bool> pair =
ReadFruContentsFromI2c(i2cBus, address, absl::ZeroDuration());
if (!pair.second) {
LOG(INFO)
<< "FindI2cEeproms: Failed to read eeprom from i2c directly at bus "
<< i2cBus << " address 0x" << absl::Hex(address);
continue;
}
LOG(INFO)
<< "FindI2cEeproms: successfully found eeprom reading directly: 0x"
<< absl::Hex(address);
found_list.insert(address);
devices->emplace(address, pair.first);
}
return found_list;
}
void FruScannerI2c::FindI2CDevices(const absl::flat_hash_set<int>& i2c_buses,
BusMap& bus_map) const {
ScanContext scan_context;
DLOG(INFO) << "Scanning i2c buses! ";
for (const auto& bus : i2c_buses) {
// Will need to store all addresses in blocklist for a bus.
// If this is an extended bus, need to skip addresses found at root bus and
// inherit the root bus block list.
// blocked_addresses: addresses that are blocked on this bus or root bus.
// root_found_addresses: addresses that are found at root bus.
absl::flat_hash_set<size_t> blocked_addresses;
absl::flat_hash_set<size_t> root_found_addresses;
if (scan_context.fru_addresses_list.contains(bus)) {
// Bus is a root bus that has already been scanned, continue
continue;
}
auto bus_iter = bus_block_list_.find(bus);
if (bus_iter != bus_block_list_.end()) {
if (bus_iter->second == std::nullopt) {
DLOG(INFO) << "Skipping blocked bus " << bus;
continue; // Skip blocked buses.
}
for (size_t address : *(bus_iter->second)) {
DLOG(INFO) << "Adding blocked address " << address << " on bus " << bus;
blocked_addresses.insert(address);
}
}
int root_bus = GetRootBus(bus, root_dir_);
if (root_bus >= 0) {
auto root_bus_iter = bus_block_list_.find(root_bus);
if (root_bus_iter != bus_block_list_.end()) {
if (root_bus_iter->second != std::nullopt) {
for (auto& root_address : *(root_bus_iter->second)) {
DLOG(INFO) << "Skipping root address " << root_address;
blocked_addresses.insert(root_address);
}
}
}
if (auto find_root_bus = scan_context.fru_addresses_list.find(root_bus);
find_root_bus != scan_context.fru_addresses_list.end()) {
root_found_addresses = find_root_bus->second;
}
}
auto device = std::make_shared<DeviceMap>();
DLOG(INFO) << "Scanning bus " << bus;
if (root_bus >= 0 && scan_context.fru_addresses_list.find(root_bus) ==
scan_context.fru_addresses_list.end()) {
// Root bus has not been scanned, scan it before extended bus.
DLOG(INFO) << "Scanning root bus " << root_bus;
auto root_device = std::make_shared<DeviceMap>();
absl::flat_hash_set<size_t> root_found_list =
FindI2cEeproms(root_bus, root_device, root_dir_, blocked_addresses,
root_found_addresses);
bus_map.emplace(root_bus, std::move(root_device));
scan_context.fru_addresses_list[root_bus].insert(root_found_list.begin(),
root_found_list.end());
root_found_addresses = root_found_list;
}
absl::flat_hash_set<size_t> found_list = FindI2cEeproms(
bus, device, root_dir_, blocked_addresses, root_found_addresses);
bus_map.emplace(bus, std::move(device));
scan_context.fru_addresses_list[bus].insert(found_list.begin(),
found_list.end());
}
}
absl::StatusOr<std::vector<std::unique_ptr<I2cFruInfo>>>
FruScannerI2c::ScanAllI2cFrus() const {
std::vector<std::unique_ptr<I2cFruInfo>> i2c_frus;
if (deterministic_scan_) {
ECCLESIA_ASSIGN_OR_RETURN(
std::vector<deterministic_bmc::I2cWalkerInterface::HardwareInfo>
hardware_infos,
uhmm_i2c_walker_->GetLogicalI2cAddresses());
for (const auto& hardware_info : hardware_infos) {
DLOG(INFO) << "Deterministic BMC: hardware_info: " << hardware_info;
if (hardware_info.sensor_id.has_value()) {
// We don't scan FRU for I2C sensor controller in this function.
continue;
}
auto fru_info = std::make_unique<I2cFruInfo>();
// Populate deterministic FRU scanning fields no matter:
// 1. whether the i2c walk for FRU on virtual bus is successful or not.
// 2. whether the FRU eeprom is processed successfully or not.
fru_info->expected_barepath = hardware_info.barepath;
fru_info->expected_part_number = hardware_info.part_number;
fru_info->expected_serial_number = hardware_info.serial_number;
if (!hardware_info.logical_bus.has_value()) {
// the expected on virtual bus is not found during the i2c walk. It
// means missing FRU eeprom. We populate the expected FRU information
// placeholder so that deterministic scan can report the expected but
// missing FRU.
fru_info->present = false;
i2c_frus.push_back(std::move(fru_info));
continue;
}
fru_info->bus = hardware_info.logical_bus.value();
fru_info->address = hardware_info.address;
// TODO(haoooamazing): Remove the G3_READ_EEPROM_FILE. An issue here is if
// we build gBMCWeb from g3, we will have to remove this flag.
// Before that we need to figure out a way to simulate the i2c operations
// This should be dealt when we switch to gbmc HAL lib for eeprom reading.
// If the switching doesn't happen before we build gBMCWeb from g3, we
// might need to consider alternative solutions.
#ifdef G3_READ_EEPROM_FILE
bool parsing_error = false;
std::vector<uint8_t> device = ProcessEeprom(
static_cast<int>(
hardware_info.logical_bus
.value()), // safe to case because logical bus will not
// exceed INT_MAX in reality. Technically we
// should check the type of the bus to be
// uint32_t.
hardware_info.address, root_dir_, &parsing_error);
if (!device.empty()) {
fru_info->present = true;
fru_info->data = std::move(device);
} else {
// Count the unsuccessful FRU eeprom processing as not present.
fru_info->present = false;
fru_info->data = {};
fru_info->parsing_error = parsing_error;
LOG(ERROR) << "Set present to false because failed to read eeprom at "
<< hardware_info.logical_bus.value() << " address "
<< hardware_info.address;
}
#else
// We are not able to read i2c dev directly for all devices since some of
// them are already managed by kernel where the direct read will fail.
// Need to remove them from device tree of something equivalent to make
// all direct read will succeed.
bool parsing_error = false;
std::vector<uint8_t> device_eeprom = ProcessEeprom(
static_cast<int>(
hardware_info.logical_bus
.value()), // safe to case because logical bus will not
// exceed INT_MAX in reality. Technically we
// should check the type of the bus to be
// uint32_t.
hardware_info.address, root_dir_, &parsing_error);
if (!device_eeprom.empty()) {
fru_info->present = true;
fru_info->data = std::move(device_eeprom);
} else if (parsing_error) {
// If the EEPROM file exists but parsing failed, skip the direct I2C
// fallback. Since the device is managed by the kernel driver, direct
// I2C access will definitely fail with an EBUSY error.
fru_info->present = false;
fru_info->parsing_error = true;
fru_info->data = {};
LOG(WARNING) << "Skipping direct I2C fallback for eeprom at "
<< hardware_info.logical_bus.value() << " address "
<< hardware_info.address << " due to parsing error.";
} else {
std::pair<std::vector<uint8_t>, bool> pair = ReadFruContentsFromI2c(
static_cast<int>(hardware_info.logical_bus.value()),
static_cast<int>(hardware_info.address), absl::ZeroDuration(),
&parsing_error);
if (pair.second) {
fru_info->present = true;
fru_info->data = std::move(pair.first);
} else {
// Count the unsuccessful FRU eeprom reading as not present.
fru_info->present = false;
fru_info->data = {};
fru_info->parsing_error = parsing_error;
LOG(ERROR) << "Set present to false because failed to read eeprom at "
<< hardware_info.logical_bus.value() << " address "
<< hardware_info.address;
}
}
#endif
i2c_frus.push_back(std::move(fru_info));
}
} else {
ECCLESIA_ASSIGN_OR_RETURN(auto bus_list, GetI2cBusList(root_dir_ + "/dev"));
BusMap bus_map;
FindI2CDevices(bus_list, bus_map);
for (const auto& [bus, device_map] : bus_map) {
for (const auto& [address, data] : *device_map) {
auto fru_info = std::make_unique<I2cFruInfo>();
fru_info->bus = bus;
fru_info->address = address;
fru_info->data = data;
i2c_frus.push_back(std::move(fru_info));
}
}
}
return i2c_frus;
}
absl::StatusOr<std::vector<std::unique_ptr<I2cSensorAssemblyFruInfo>>>
FruScannerI2c::ScanAllI2cSensorAssemblyFru() const {
if (!deterministic_scan_) {
return absl::UnimplementedError(
"ScanAllI2cSensorAssemblyFru is only supported for deterministic "
"scan.");
}
std::vector<std::unique_ptr<I2cSensorAssemblyFruInfo>>
i2c_sensor_assembly_frus;
ECCLESIA_ASSIGN_OR_RETURN(
std::vector<deterministic_bmc::I2cWalkerInterface::HardwareInfo>
hardware_infos,
uhmm_i2c_walker_->GetLogicalI2cAddresses());
for (const auto& hardware_info : hardware_infos) {
if (!hardware_info.sensor_id.has_value()) {
// only scan sensor assembly FRU for I2C sensor controller.
continue;
}
if (!hardware_info.sensor_controller_model.has_value()) {
// this should not happen. as I2cWalker should return error if the sensor
// controller model is not available.
return absl::InternalError(absl::StrCat(
"Sensor controller model is not available for sensor FRU: ",
hardware_info.barepath));
}
auto fru_info = std::make_unique<I2cSensorAssemblyFruInfo>();
fru_info->expected_barepath = hardware_info.barepath;
if (!hardware_info.logical_bus.has_value()) {
// this means the virtual bus of the sensor controller is not found. So
// the sensor assembly FRU is not found.
fru_info->present = false;
i2c_sensor_assembly_frus.push_back(std::move(fru_info));
continue;
}
fru_info->bus = hardware_info.logical_bus.value();
fru_info->address = hardware_info.address;
HalCommonConfig hal_common_config;
hal_common_config.set_bus(hardware_info.logical_bus.value());
hal_common_config.set_address(hardware_info.address);
// E.g., /sys/bus/i2c/devices/i2c-19/19-002c/hwmon/hwmon9/
absl::StatusOr<boost::filesystem::path> hwmon_path =
IXcHwmonBasedSensor::CreateIXcDeviceAndReturnsHwmonPath(
hal_common_config, hardware_info.sensor_controller_model.value(),
i2c_sysfs_);
if (!hwmon_path.ok()) {
// Any failure in hwmon path creation is considered as the sensor assembly
// FRU is not present.
fru_info->present = false;
i2c_sensor_assembly_frus.push_back(std::move(fru_info));
continue;
}
fru_info->present = false;
boost::system::error_code ec;
boost::filesystem::directory_iterator end_itr;
boost::filesystem::directory_iterator itr(hwmon_path.value(), ec);
while (!ec && itr != end_itr) {
// Look for the sensor id file in the hwmon directory.
if (itr->path().filename().string() == hardware_info.sensor_id.value()) {
fru_info->present = true;
break;
}
itr.increment(ec);
}
if (ec) {
LOG(ERROR) << "Failed to iterate directory during sensor FRU scan. "
<< hwmon_path.value().string() << ": " << ec.message();
fru_info->present = false;
}
i2c_sensor_assembly_frus.push_back(std::move(fru_info));
}
return i2c_sensor_assembly_frus;
}
std::unique_ptr<FruScannerI2c> FruScannerI2c::Create(const Options& options) {
return absl::WrapUnique(new FruScannerI2c(
options.root_dir, options.i2c_dev_dir, options.deterministic_scan,
options.i2c_root, options.i2c_devices_sysfs_dir, options.bus_block_list,
options.extra_fru_scan_addresses));
}
} // namespace milotic_tlbmc