| #include "tlbmc/hal/fru_scanner_fram.h" |
| |
| #include <cstdint> |
| #include <memory> |
| #include <optional> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/log/log.h" |
| #include "absl/memory/memory.h" |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/time/time.h" |
| #include "io/smbus/smbus.h" |
| #include "time/clock.h" |
| #include "tlbmc/hal/fru_scanner.h" |
| #include "tlbmc/utils/fram_utils.h" |
| |
| namespace milotic_tlbmc { |
| |
| using SmbusLocation = ecclesia::SmbusLocation; |
| using SmbusBus = ecclesia::SmbusBus; |
| using SmbusAddress = ecclesia::SmbusAddress; |
| |
| std::unique_ptr<FruScannerFram> FruScannerFram::Create(const Options& options) { |
| return absl::WrapUnique(new FruScannerFram(options)); |
| } |
| |
| FruScannerFram::~FruScannerFram() = default; |
| |
| absl::StatusOr<std::unique_ptr<I2cFruInfo>> |
| FruScannerFram::GetI2cFruInfoFromBus(int bus, int address, |
| absl::Duration delay_between_reads) const { |
| if (options_.fram_size == 0) { |
| return absl::InvalidArgumentError( |
| absl::StrCat("Invalid FRAM size: ", options_.fram_size)); |
| } |
| |
| LOG(INFO) << "Attempting to read FRAM at bus " << bus << " address " |
| << address << " with size " << options_.fram_size << " bytes."; |
| |
| // Create a new I2cFruInfo object to store the data read from the FRAM. |
| auto i2c_fru_info = std::make_unique<I2cFruInfo>(); |
| i2c_fru_info->bus = bus; |
| i2c_fru_info->address = address; |
| |
| // Create an SmbusLocation object for the FRAM device. |
| std::optional<SmbusLocation> location = SmbusLocation::TryMake(bus, address); |
| if (!location.has_value()) { |
| LOG(ERROR) << "Failed to create SmbusLocation for bus=" << bus |
| << ", address=" << address; |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid bus or address for FRAM: bus=", bus, ", address=", address)); |
| } |
| |
| // Check if the device is present on the I2C bus. |
| absl::Status probe_status = smbus_access_->ProbeDevice(*location); |
| if (!probe_status.ok()) { |
| LOG(ERROR) << "ProbeDevice failed at bus: " << bus |
| << " address: " << address << " with status: " << probe_status; |
| return absl::InternalError( |
| absl::StrCat("No device found at bus: ", bus, " address: ", address, |
| " status: ", probe_status.ToString())); |
| } |
| LOG(INFO) << "ProbeDevice succeeded. Starting read of up to " |
| << options_.fram_size << " bytes."; |
| |
| // Read bytes from the FRAM device. |
| i2c_fru_info->data.reserve(options_.fram_size); |
| |
| // Set the initial offset by reading the first byte using Read8; this is to |
| // set the initial offset for the subsequent ReceiveByte calls. FRAM has |
| // auto-incrementing internal address pointer. |
| uint8_t byte; |
| absl::Status status = smbus_access_->Read8(*location, 0, &byte); |
| if (!status.ok()) { |
| LOG(ERROR) << "Failed to read first byte at offset 0: " << status; |
| return status; // Propagate the error |
| } |
| i2c_fru_info->data.push_back(byte); |
| |
| // Attempt to read subsequent bytes using ReceiveByte |
| for (int offset = 1; offset < options_.fram_size; ++offset) { |
| SleepIfNeeded(delay_between_reads); |
| uint8_t received_byte; |
| status = smbus_access_->ReceiveByte(*location, &received_byte); |
| if (!status.ok()) { |
| LOG(WARNING) << "ReceiveByte at offset " << offset |
| << " failed with status: " << status |
| << ". Skipping this byte and continuing."; |
| continue; |
| } |
| i2c_fru_info->data.push_back(received_byte); |
| } |
| |
| LOG(INFO) << "Finished reading " << i2c_fru_info->data.size() << " bytes."; |
| |
| // Check that what we read is potential Cavium HSM FRAM data. This is to |
| // ensure that this FRAM scanner is only used for Cavium HSM FRAMs. |
| absl::StatusOr<RawFru> fru = CreateRawFruFromFramData(i2c_fru_info); |
| if (!fru.ok()) { |
| LOG(WARNING) << "Failed to detect FRAM data at bus " << bus << " address " |
| << address << ": " << fru.status(); |
| return fru.status(); |
| } |
| |
| // Return the I2cFruInfo object containing the data read from the FRAM. |
| return i2c_fru_info; |
| } |
| |
| absl::StatusOr<std::vector<std::unique_ptr<I2cFruInfo>>> |
| FruScannerFram::ScanAllI2cFrus() const { |
| std::vector<std::unique_ptr<I2cFruInfo>> frus; |
| auto fru_info = GetI2cFruInfoFromBus(options_.bus, options_.address, |
| absl::ZeroDuration()); |
| // Return empty vector considering that scanAllI2cFrus is supposed to scan |
| // for different bus and addresses, and one failure should not block the |
| // other bus and addresses. Additionally, not all platforms have FRAMs, so |
| // this is not an error condition. |
| if (!fru_info.ok()) { |
| LOG(ERROR) << "Failed to read FRAM at bus " << options_.bus << " address " |
| << options_.address << " with status: " << fru_info.status(); |
| return frus; |
| } |
| frus.push_back(std::move(*fru_info)); |
| |
| return frus; |
| } |
| |
| void FruScannerFram::SleepIfNeeded(absl::Duration delay) const { |
| if (delay > absl::ZeroDuration()) { |
| clock_->Sleep(delay); |
| } |
| } |
| |
| } // namespace milotic_tlbmc |