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