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