Skip to content

Commit 10c5765

Browse files
committed
Allow decoding MAC addresses in some FRUs
Some motherboards store their mac address in a zlib compressed chunk at a known location in the FRU. Decode that section, and pull the mac address into the appropriate field. This requires some refactoring so that the indexing can now have the indexes passed through the various parse functions. To use this functionality requires the use of libxml and zlib, which are added as new dependencies. Change-Id: Icb5c2e46e2a08ca83b3559892169ee2b3f319b2e Signed-off-by: Ed Tanous <[email protected]>
1 parent 9bde15b commit 10c5765

File tree

10 files changed

+452
-47
lines changed

10 files changed

+452
-47
lines changed

meson.build

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ endif
3838
nlohmann_json_dep = dependency('nlohmann_json', include_type: 'system')
3939
sdbusplus = dependency('sdbusplus')
4040
phosphor_logging_dep = dependency('phosphor-logging')
41+
zlib_dep = dependency('zlib', include_type: 'system')
42+
libxml2_dep = dependency('libxml-2.0', include_type: 'system')
4143

4244
if get_option('gpio-presence') or get_option('tests').allowed()
4345
libgpio_dep = dependency(

src/fru_device/fru_device.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -958,18 +958,22 @@ bool writeFRU(uint8_t bus, uint8_t address, const std::vector<uint8_t>& fru)
958958
return false;
959959
}
960960

961-
std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData{};
962961
std::string errorMessage = "eeprom at " + std::to_string(bus) +
963962
" address " + std::to_string(address);
964963
auto readFunc = [eeprom](off_t offset, size_t length, uint8_t* outbuf) {
965964
return readFromEeprom(eeprom, offset, length, outbuf);
966965
};
967966
FRUReader reader(std::move(readFunc));
968967

969-
if (!findFRUHeader(reader, errorMessage, blockData, offset))
968+
auto sections = findFRUHeader(reader, errorMessage, 0);
969+
if (!sections)
970970
{
971971
offset = 0;
972972
}
973+
else
974+
{
975+
offset = sections->IpmiFruOffset;
976+
}
973977

974978
if (lseek(eeprom, offset, SEEK_SET) < 0)
975979
{

src/fru_device/fru_utils.cpp

Lines changed: 86 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
#include "fru_utils.hpp"
55

6+
#include "gzip_utils.hpp"
7+
68
#include <phosphor-logging/lg2.hpp>
79

810
#include <array>
@@ -717,27 +719,66 @@ bool validateHeader(const std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData)
717719
return true;
718720
}
719721

720-
bool findFRUHeader(FRUReader& reader, const std::string& errorHelp,
721-
std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData,
722-
off_t& baseOffset)
722+
std::string parseMacFromGzipXmlHeader(FRUReader& reader, off_t offset)
723+
{
724+
// gzip starts at offset 512. Read that from the FRU
725+
// in this case, 32k bytes is enough to hold the whole manifest
726+
constexpr size_t totalReadSize = 32UL * 1024UL;
727+
728+
std::vector<uint8_t> headerData(totalReadSize, 0U);
729+
730+
int rc = reader.read(offset, totalReadSize, headerData.data());
731+
if (rc <= 0)
732+
{
733+
return {};
734+
}
735+
736+
std::optional<std::string> xml = gzipInflate(headerData);
737+
if (!xml)
738+
{
739+
return {};
740+
}
741+
std::vector<std::string> node = getNodeFromXml(
742+
*xml, "/GSSKU/BoardInfo/Main/NIC/*[Mode = 'Dedicated']/MacAddr0");
743+
if (node.empty())
744+
{
745+
lg2::debug("No mac address found in gzip xml header");
746+
return {};
747+
}
748+
if (node.size() > 1)
749+
{
750+
lg2::warning("Multiple mac addresses found in gzip xml header");
751+
}
752+
return node[0];
753+
}
754+
755+
std::optional<FruSections> findFRUHeader(
756+
FRUReader& reader, const std::string& errorHelp, off_t startingOffset)
723757
{
724-
if (reader.read(baseOffset, 0x8, blockData.data()) < 0)
758+
std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData = {};
759+
if (reader.read(startingOffset, 0x8, blockData.data()) < 0)
725760
{
726761
lg2::error("failed to read {ERR} base offset {OFFSET}", "ERR",
727-
errorHelp, "OFFSET", baseOffset);
728-
return false;
762+
errorHelp, "OFFSET", startingOffset);
763+
return std::nullopt;
729764
}
730765

731766
// check the header checksum
732767
if (validateHeader(blockData))
733768
{
734-
return true;
769+
FruSections fru = {};
770+
static_assert(fru.ipmiFruBlock.size() == blockData.size(),
771+
"size mismatch in block data");
772+
std::memcpy(fru.ipmiFruBlock.data(), blockData.data(),
773+
I2C_SMBUS_BLOCK_MAX);
774+
fru.IpmiFruOffset = startingOffset;
775+
return fru;
735776
}
736777

737778
// only continue the search if we just looked at 0x0.
738-
if (baseOffset != 0)
779+
if (startingOffset != 0)
739780
{
740-
return false;
781+
return std::nullopt;
741782
}
742783

743784
// now check for special cases where the IPMI data is at an offset
@@ -748,8 +789,8 @@ bool findFRUHeader(FRUReader& reader, const std::string& errorHelp,
748789
std::equal(tyanHeader.begin(), tyanHeader.end(), blockData.begin()))
749790
{
750791
// look for the FRU header at offset 0x6000
751-
baseOffset = 0x6000;
752-
return findFRUHeader(reader, errorHelp, blockData, baseOffset);
792+
off_t tyanOffset = 0x6000;
793+
return findFRUHeader(reader, errorHelp, tyanOffset);
753794
}
754795

755796
// check if blockData starts with gigabyteHeader
@@ -760,27 +801,39 @@ bool findFRUHeader(FRUReader& reader, const std::string& errorHelp,
760801
blockData.begin()))
761802
{
762803
// look for the FRU header at offset 0x4000
763-
baseOffset = 0x4000;
764-
return findFRUHeader(reader, errorHelp, blockData, baseOffset);
804+
off_t gbOffset = 0x4000;
805+
auto sections = findFRUHeader(reader, errorHelp, gbOffset);
806+
if (sections)
807+
{
808+
lg2::debug("succeeded on GB parse");
809+
// GB xml header is at 512 bytes
810+
sections->GigabyteXmlOffset = 512;
811+
}
812+
else
813+
{
814+
lg2::error("Failed on GB parse");
815+
}
816+
return sections;
765817
}
766818

767819
lg2::debug("Illegal header {HEADER} base offset {OFFSET}", "HEADER",
768-
errorHelp, "OFFSET", baseOffset);
820+
errorHelp, "OFFSET", startingOffset);
769821

770-
return false;
822+
return std::nullopt;
771823
}
772824

773825
std::pair<std::vector<uint8_t>, bool> readFRUContents(
774826
FRUReader& reader, const std::string& errorHelp)
775827
{
776828
std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData{};
777-
off_t baseOffset = 0x0;
778-
779-
if (!findFRUHeader(reader, errorHelp, blockData, baseOffset))
829+
std::optional<FruSections> sections = findFRUHeader(reader, errorHelp, 0);
830+
if (!sections)
780831
{
781832
return {{}, false};
782833
}
783-
834+
const off_t baseOffset = sections->IpmiFruOffset;
835+
std::memcpy(blockData.data(), sections->ipmiFruBlock.data(),
836+
blockData.size());
784837
std::vector<uint8_t> device;
785838
device.insert(device.end(), blockData.begin(),
786839
std::next(blockData.begin(), 8));
@@ -897,6 +950,20 @@ std::pair<std::vector<uint8_t>, bool> readFRUContents(
897950
fruLength -= std::min(requestLength, fruLength);
898951
}
899952

953+
if (sections->GigabyteXmlOffset != 0)
954+
{
955+
std::string macAddress =
956+
parseMacFromGzipXmlHeader(reader, sections->GigabyteXmlOffset);
957+
if (!macAddress.empty())
958+
{
959+
// launder the mac address as we expect into
960+
// BOARD_INFO_AM2 to allow the rest of the
961+
// system to use it
962+
std::string mac = std::format("MAC: {}", macAddress);
963+
updateAddProperty(mac, "BOARD_INFO_AM2", device);
964+
}
965+
}
966+
900967
return {device, true};
901968
}
902969

src/fru_device/fru_utils.hpp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,14 @@ unsigned int updateFRUAreaLenAndChecksum(
126126

127127
ssize_t getFieldLength(uint8_t fruFieldTypeLenValue);
128128

129+
struct FruSections
130+
{
131+
off_t IpmiFruOffset = 0;
132+
std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> ipmiFruBlock;
133+
134+
off_t GigabyteXmlOffset = 0;
135+
};
136+
129137
/// \brief Find a FRU header.
130138
/// \param reader the FRUReader to read via
131139
/// \param errorHelp and a helper string for failures
@@ -134,9 +142,8 @@ ssize_t getFieldLength(uint8_t fruFieldTypeLenValue);
134142
/// set to 0 to perform search;
135143
/// returns the offset at which a header was found
136144
/// \return whether a header was found
137-
bool findFRUHeader(FRUReader& reader, const std::string& errorHelp,
138-
std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData,
139-
off_t& baseOffset);
145+
std::optional<FruSections> findFRUHeader(
146+
FRUReader& reader, const std::string& errorHelp, off_t offset);
140147

141148
/// \brief Read and validate FRU contents.
142149
/// \param reader the FRUReader to read via
@@ -229,4 +236,6 @@ bool setField(const fruAreas& fruAreaToUpdate, std::vector<uint8_t>& areaData,
229236

230237
bool updateAddProperty(const std::string& propertyValue,
231238
const std::string& propertyName,
232-
std::vector<uint8_t>& data);
239+
std::vector<uint8_t>& fruData);
240+
241+
std::string parseMacFromGzipXmlHeader(FRUReader& reader, off_t offset);

src/fru_device/gzip_utils.cpp

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#include "gzip_utils.hpp"
2+
3+
#include <libxml/parser.h>
4+
#include <libxml/xpath.h>
5+
#include <unistd.h>
6+
#include <zlib.h>
7+
8+
#include <optional>
9+
#include <span>
10+
#include <string>
11+
#include <vector>
12+
13+
std::optional<std::string> gzipInflate(std::span<uint8_t> compressedBytes)
14+
{
15+
std::string uncompressedBytes;
16+
if (compressedBytes.empty())
17+
{
18+
return std::nullopt;
19+
}
20+
21+
z_stream strm{
22+
23+
};
24+
strm.next_in = (Bytef*)compressedBytes.data();
25+
strm.avail_in = static_cast<uInt>(compressedBytes.size());
26+
strm.total_out = 0;
27+
28+
if (inflateInit2(&strm, (16 + MAX_WBITS)) != Z_OK)
29+
{
30+
return std::nullopt;
31+
}
32+
33+
while (strm.avail_in > 0)
34+
{
35+
constexpr size_t chunkSize = 1024;
36+
uncompressedBytes.resize(uncompressedBytes.size() + chunkSize);
37+
strm.next_out =
38+
std::bit_cast<Bytef*>(uncompressedBytes.end() - chunkSize);
39+
strm.avail_out = chunkSize;
40+
41+
// Inflate another chunk.
42+
int err = inflate(&strm, Z_SYNC_FLUSH);
43+
if (err == Z_STREAM_END)
44+
{
45+
break;
46+
}
47+
if (err != Z_OK)
48+
{
49+
return std::nullopt;
50+
}
51+
}
52+
53+
if (inflateEnd(&strm) != Z_OK)
54+
{
55+
return std::nullopt;
56+
}
57+
uncompressedBytes.resize(strm.total_out);
58+
59+
return {uncompressedBytes};
60+
}
61+
62+
static std::vector<std::string> xpathText(xmlDocPtr doc, const char* xp)
63+
{
64+
std::vector<std::string> val;
65+
xmlXPathContextPtr ctx = xmlXPathNewContext(doc);
66+
if (ctx == nullptr)
67+
{
68+
return val;
69+
}
70+
const unsigned char* xpptr = std::bit_cast<const unsigned char*>(xp);
71+
xmlXPathObjectPtr obj = xmlXPathEvalExpression(xpptr, ctx);
72+
if (obj != nullptr)
73+
{
74+
if (obj->type == XPATH_NODESET && obj->nodesetval != nullptr)
75+
{
76+
xmlNodeSetPtr nodeTab = obj->nodesetval;
77+
size_t nodeNr = static_cast<size_t>(nodeTab->nodeNr);
78+
std::span<xmlNodePtr> nodes{nodeTab->nodeTab, nodeNr};
79+
for (xmlNodePtr node : nodes)
80+
{
81+
unsigned char* keyword = xmlNodeGetContent(node);
82+
val.emplace_back(std::bit_cast<const char*>(keyword));
83+
xmlFree(keyword);
84+
}
85+
}
86+
}
87+
88+
xmlXPathFreeObject(obj);
89+
xmlXPathFreeContext(ctx);
90+
return val;
91+
}
92+
93+
std::vector<std::string> getNodeFromXml(std::string_view xml,
94+
const char* nodeName)
95+
{
96+
std::vector<std::string> node;
97+
if (xml.empty())
98+
{
99+
return node;
100+
}
101+
xmlDocPtr doc = xmlReadMemory(
102+
xml.data(), xml.size(), nullptr, nullptr,
103+
XML_PARSE_RECOVER | XML_PARSE_NONET | XML_PARSE_NOWARNING);
104+
if (doc == nullptr)
105+
{
106+
return {};
107+
}
108+
node = xpathText(doc, nodeName);
109+
xmlFreeDoc(doc);
110+
return node;
111+
}

src/fru_device/gzip_utils.hpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright 2018 Intel Corporation
3+
4+
#pragma once
5+
6+
#include <array>
7+
#include <cstdint>
8+
#include <optional>
9+
#include <span>
10+
#include <string>
11+
#include <vector>
12+
13+
std::optional<std::string> gzipInflate(std::span<uint8_t> compressedBytes);
14+
15+
std::vector<std::string> getNodeFromXml(std::string_view xml,
16+
const char* nodeName);

src/fru_device/meson.build

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,20 @@ executable(
1313
'fru-device',
1414
'fru_device.cpp',
1515
'../utils.cpp',
16-
'fru_utils.cpp',
1716
'fru_reader.cpp',
17+
'fru_utils.cpp',
18+
'gzip_utils.cpp',
1819
cpp_args: cpp_args_fd,
1920
dependencies: [
2021
boost,
2122
i2c,
23+
libxml2_dep,
2224
nlohmann_json_dep,
2325
phosphor_logging_dep,
2426
sdbusplus,
2527
threads,
2628
valijson,
29+
zlib_dep,
2730
],
2831
install: true,
2932
install_dir: installdir,

0 commit comments

Comments
 (0)