blob: 6570b356ac46b3772acd0503aec044d51be4761b [file] [log] [blame]
#include "tlbmc/utils/fram_utils.h"
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/ascii.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "tlbmc/hal/fru_scanner.h"
#include "fru.pb.h"
namespace milotic_tlbmc {
namespace {
// Struct to hold the data extracted from the FRAM.
struct FramData {
std::string board_info;
std::string board_product_name;
std::string board_serial_number;
};
constexpr absl::string_view kBoardSerialNumberKey = "Board Serial Number:";
constexpr absl::string_view kBoardInfoKey = "Board Information:";
// Parses the raw byte data read from the FRAM.
// The FRAM contains text logs, not a standard IPMI FRU format.
// This function filters out non-printable characters and then parses
// lines starting with "INFO:" to extract key information.
//
// Example FRAM data:
// INFO:1003913708:Linux:Board Serial Number: 6.0G2337-VPM018009
// INFO:2143122215:Bootloader:Board Information: EBB6100 board rev 1
// INFO:1003913708:Linux:Firmware Version: CNN35XX-1.0.0
//
// Args:
// data: The raw byte data read from the FRAM.
//
// Returns:
// A FramData struct containing the extracted key-value pairs.
FramData ParseFramData(const std::vector<uint8_t>& data) {
FramData parsed_data = {};
if (data.empty()) {
return parsed_data; // Return empty struct if no data.
}
std::string content;
content.reserve(data.size());
for (uint8_t byte : data) {
if (absl::ascii_isprint(static_cast<char>(byte)) || byte == '\n') {
content += static_cast<char>(byte);
}
}
// Split the content into lines.
std::vector<absl::string_view> lines = absl::StrSplit(content, '\n');
// Iterate through each line to find relevant information.
for (const auto& line : lines) {
absl::string_view trimmed_line = absl::StripAsciiWhitespace(line);
// We only care about lines that start with "INFO:".
if (!absl::StartsWith(trimmed_line, "INFO:")) {
continue;
}
// Split the line by colons. Expected format: INFO:<ts>:<Module>:<Message>
std::vector<absl::string_view> parts =
absl::StrSplit(trimmed_line, absl::MaxSplits(':', 3));
if (parts.size() < 4) {
continue; // Skip lines not matching the format.
}
// Extract Module and Message parts.
absl::string_view module = absl::StripAsciiWhitespace(parts[2]);
absl::string_view message = absl::StripAsciiWhitespace(parts[3]);
if (module == "Linux") {
// Handle Linux:Board Serial Number: <value>
if (absl::StartsWith(message, kBoardSerialNumberKey)) {
parsed_data.board_serial_number =
std::string(absl::StripAsciiWhitespace(
message.substr(kBoardSerialNumberKey.length())));
}
continue;
}
if (module == "Bootloader") {
// Handle Bootloader:Board Information: <value>
if (absl::StartsWith(message, kBoardInfoKey)) {
parsed_data.board_info = std::string(
absl::StripAsciiWhitespace(message.substr(kBoardInfoKey.length())));
// Attempt to extract the product name from the Board Information.
// This assumes the product name is the first word in the Board
// Information string. This is based on the observed format:
// "<ProductName> board revision major:X, minor:Y ..."
// Therefore, splitting by space and taking the 0th index should give
// the product name.
// Example: "EBB6100 board revision major:1, minor:0, serial #: unknown"
// Here, "EBB6100" is the product name.
std::vector<std::string> board_parts =
absl::StrSplit(parsed_data.board_info, ' ');
if (!board_parts.empty()) {
parsed_data.board_product_name = board_parts[0];
}
}
continue;
}
}
return parsed_data; // Return the struct of extracted key-value pairs.
}
} // namespace
// Creates a RawFru proto from the data read from the FRAM.
// It uses ParseFramData to get the key-value pairs and then populates
// the RawFru proto fields.
absl::StatusOr<RawFru> CreateRawFruFromFramData(
const std::unique_ptr<I2cFruInfo>& i2c_fru) {
// Parse the raw FRAM data to extract information.
FramData parsed_data = ParseFramData(i2c_fru->data);
if (parsed_data.board_serial_number.empty() ||
parsed_data.board_info.empty() ||
parsed_data.board_product_name.empty()) {
return absl::InvalidArgumentError("Incomplete data parsed from FRAM");
}
// Initialize the RawFru proto.
RawFru fru;
fru.set_key(absl::StrCat(i2c_fru->bus, ":", i2c_fru->address));
fru.mutable_i2c_info()->set_bus(i2c_fru->bus);
fru.mutable_i2c_info()->set_address(i2c_fru->address);
FruData* fru_data = fru.mutable_data();
// Populate FRU fields from the parsed data.
if (!parsed_data.board_serial_number.empty()) {
fru_data->mutable_fru_info()->set_board_serial_number(
parsed_data.board_serial_number);
(*fru_data->mutable_fields())["BOARD_SERIAL_NUMBER"] =
parsed_data.board_serial_number;
}
if (!parsed_data.board_product_name.empty()) {
fru_data->mutable_fru_info()->set_board_product_name(
parsed_data.board_product_name);
(*fru_data->mutable_fields())["BOARD_PRODUCT_NAME"] =
parsed_data.board_product_name;
}
if (!parsed_data.board_info.empty()) {
(*fru_data->mutable_fields())["BOARD_INFO"] = parsed_data.board_info;
}
return fru;
}
} // namespace milotic_tlbmc