blob: 1e0ce1de3ff7584a300cb4145041bdfc14ff94f3 [file] [log] [blame]
#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