Coverage Report

Created: 2026-08-09 07:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/phosphor-host-ipmid/ipmi_fru_info_area.cpp
Line
Count
Source
1
#include "ipmi_fru_info_area.hpp"
2
3
#include <ipmid/utils.hpp>
4
#include <phosphor-logging/elog.hpp>
5
#include <phosphor-logging/lg2.hpp>
6
7
#include <algorithm>
8
#include <ctime>
9
#include <iomanip>
10
#include <map>
11
#include <numeric>
12
#include <sstream>
13
14
namespace ipmi
15
{
16
namespace fru
17
{
18
using namespace phosphor::logging;
19
20
// Property variables
21
static constexpr auto partNumber = "Part Number";
22
static constexpr auto serialNumber = "Serial Number";
23
static constexpr auto manufacturer = "Manufacturer";
24
static constexpr auto buildDate = "Mfg Date";
25
static constexpr auto modelNumber = "Model Number";
26
static constexpr auto prettyName = "Name";
27
static constexpr auto version = "Version";
28
static constexpr auto type = "Type";
29
30
// Board info areas
31
static constexpr auto board = "Board";
32
static constexpr auto chassis = "Chassis";
33
static constexpr auto product = "Product";
34
35
static constexpr auto specVersion = 0x1;
36
static constexpr auto recordUnitOfMeasurement = 0x8; // size in bytes
37
static constexpr auto checksumSize = 0x1;            // size in bytes
38
static constexpr auto recordNotPresent = 0x0;
39
static constexpr auto englishLanguageCode = 0x0;
40
static constexpr auto typeLengthByteNull = 0x0;
41
static constexpr auto endOfCustomFields = 0xC1;
42
static constexpr auto commonHeaderFormatSize = 0x8; // size in bytes
43
static constexpr auto areaSizeOffset = 0x1;
44
static constexpr uint8_t typeASCII = 0xC0;
45
static constexpr auto maxRecordAttributeValue = 0x3F;
46
47
static constexpr auto secs_from_1970_1996 = 820454400;
48
static constexpr auto maxMfgDateValue = 0xFFFFFF; // 3 Byte length
49
static constexpr auto secs_per_min = 60;
50
static constexpr auto secsToMaxMfgdate =
51
    secs_from_1970_1996 + secs_per_min * maxMfgDateValue;
52
53
// Minimum size of resulting FRU blob.
54
// This is also the theoretical maximum size according to the spec:
55
// 8 bytes header + 5 areas at 0xff*8 bytes max each
56
// 8 + 5*0xff*8 = 0x27e0
57
static constexpr auto fruMinSize = 0x27E0;
58
59
// Value to use for padding.
60
// Using 0xff to match the default (blank) value in a physical EEPROM.
61
static constexpr auto fruPadValue = 0xff;
62
63
/**
64
 * @brief Format Beginning of Individual IPMI FRU Data Section
65
 *
66
 * @param[in] langCode Language code
67
 * @param[in/out] data FRU area data
68
 */
69
void preFormatProcessing(bool langCode, FruAreaData& data)
70
1.13k
{
71
    // Add id for version of FRU Info Storage Spec used
72
1.13k
    data.emplace_back(specVersion);
73
74
    // Add Data Size - 0 as a placeholder, can edit after the data is finalized
75
1.13k
    data.emplace_back(typeLengthByteNull);
76
77
1.13k
    if (langCode)
78
758
    {
79
758
        data.emplace_back(englishLanguageCode);
80
758
    }
81
1.13k
}
82
83
/**
84
 * @brief Append checksum of the FRU area data
85
 *
86
 * @param[in/out] data FRU area data
87
 */
88
void appendDataChecksum(FruAreaData& data)
89
1.97k
{
90
1.97k
    uint8_t checksumVal = std::accumulate(data.begin(), data.end(), 0);
91
    // Push the Zero checksum as the last byte of this data
92
    // This appears to be a simple summation of all the bytes
93
1.97k
    data.emplace_back(-checksumVal);
94
1.97k
}
95
96
/**
97
 * @brief Append padding bytes for the FRU area data
98
 *
99
 * @param[in/out] data FRU area data
100
 */
101
void padData(FruAreaData& data)
102
1.13k
{
103
1.13k
    uint8_t pad = (data.size() + checksumSize) % recordUnitOfMeasurement;
104
1.13k
    if (pad)
105
1.00k
    {
106
1.00k
        data.resize((data.size() + recordUnitOfMeasurement - pad));
107
1.00k
    }
108
1.13k
}
109
110
/**
111
 * @brief Format End of Individual IPMI FRU Data Section
112
 *
113
 * @param[in/out] fruAreaData FRU area info data
114
 */
115
void postFormatProcessing(FruAreaData& data)
116
1.13k
{
117
    // This area needs to be padded to a multiple of 8 bytes (after checksum)
118
1.13k
    padData(data);
119
120
    // Set size of data info area
121
1.13k
    data.at(areaSizeOffset) =
122
1.13k
        (data.size() + checksumSize) / (recordUnitOfMeasurement);
123
124
    // Finally add area checksum
125
1.13k
    appendDataChecksum(data);
126
1.13k
}
127
128
/**
129
 * @brief Read chassis type property value from inventory and append to the FRU
130
 * area data.
131
 *
132
 * @param[in] propMap map of property values
133
 * @param[in,out] data FRU area data to be appended
134
 */
135
void appendChassisType(const PropertyMap& propMap, FruAreaData& data)
136
375
{
137
375
    uint8_t chassisType = 0; // Not specified
138
375
    auto iter = propMap.find(type);
139
375
    if (iter != propMap.end())
140
205
    {
141
205
        auto value = iter->second;
142
205
        if (!ipmi::tryParse(value, chassisType))
143
183
        {
144
183
            lg2::error("Could not parse chassis type, value: {VALUE}", "VALUE",
145
183
                       value);
146
183
            chassisType = 0;
147
183
        }
148
205
    }
149
375
    data.emplace_back(chassisType);
150
375
}
151
152
/**
153
 * @brief Read property value from inventory and append to the FRU area data
154
 *
155
 * @param[in] key key to search for in the property inventory data
156
 * @param[in] propMap map of property values
157
 * @param[in,out] data FRU area data to be appended
158
 */
159
void appendData(const Property& key, const PropertyMap& propMap,
160
                FruAreaData& data)
161
4.11k
{
162
4.11k
    auto iter = propMap.find(key);
163
4.11k
    if (iter != propMap.end())
164
1.78k
    {
165
1.78k
        auto value = iter->second;
166
        // If starts with 0x or 0X remove them
167
        // ex: 0x123a just take 123a
168
1.78k
        if ((value.compare(0, 2, "0x")) == 0 ||
169
1.74k
            (value.compare(0, 2, "0X") == 0))
170
100
        {
171
100
            value.erase(0, 2);
172
100
        }
173
174
        // 6 bits for length as per FRU spec v1.0
175
        // if length is greater then 63(2^6) bytes then trim the data to 63
176
        // bytes.
177
1.78k
        auto valueLength = (value.length() > maxRecordAttributeValue)
178
1.78k
                               ? maxRecordAttributeValue
179
1.78k
                               : value.length();
180
        // 2 bits for type
181
        // Set the type to ascii
182
1.78k
        uint8_t typeLength = valueLength | ipmi::fru::typeASCII;
183
184
1.78k
        data.emplace_back(typeLength);
185
1.78k
        std::copy(value.begin(), value.begin() + valueLength,
186
1.78k
                  std::back_inserter(data));
187
1.78k
    }
188
2.33k
    else
189
2.33k
    {
190
        // set 0 size
191
2.33k
        data.emplace_back(typeLengthByteNull);
192
2.33k
    }
193
4.11k
}
194
195
std::time_t timeStringToRaw(const std::string& input)
196
286
{
197
    // TODO: For non-US region timestamps, pass in region information for the
198
    // FRU to avoid the month/day swap.
199
    // 2017-02-24 - 13:59:00, Tue Nov 20 23:08:00 2018, 20251224T064200Z
200
286
    static const std::vector<std::string> patterns = {
201
286
        "%Y-%m-%d - %H:%M:%S", "%a %b %d %H:%M:%S %Y", "%Y%m%dT%H%M%SZ"};
202
203
286
    std::tm time = {};
204
205
286
    for (const auto& pattern : patterns)
206
719
    {
207
719
        std::istringstream timeStream(input);
208
719
        timeStream >> std::get_time(&time, pattern.c_str());
209
719
        if (!timeStream.fail())
210
154
        {
211
154
            break;
212
154
        }
213
719
    }
214
215
286
    return timegm(&time);
216
286
}
217
218
/**
219
 * @brief Appends Build Date
220
 *
221
 * @param[in] propMap map of property values
222
 * @param[in/out] data FRU area to add the manfufacture date
223
 */
224
void appendMfgDate(const PropertyMap& propMap, FruAreaData& data)
225
425
{
226
    // MFG Date/Time
227
425
    auto iter = propMap.find(buildDate);
228
425
    if ((iter != propMap.end()) && (iter->second.size() > 0))
229
286
    {
230
286
        std::time_t raw = timeStringToRaw(iter->second);
231
232
        // From FRU Spec:
233
        // "Mfg. Date / Time
234
        // Number of minutes from 0:00 hrs 1/1/96.
235
        // LSbyte first (little endian)
236
        // 00_00_00h = unspecified."
237
286
        if ((raw >= secs_from_1970_1996) && (raw <= secsToMaxMfgdate))
238
46
        {
239
46
            raw -= secs_from_1970_1996;
240
46
            raw /= secs_per_min;
241
46
            uint8_t fru_raw[3];
242
46
            fru_raw[0] = raw & 0xFF;
243
46
            fru_raw[1] = (raw >> 8) & 0xFF;
244
46
            fru_raw[2] = (raw >> 16) & 0xFF;
245
46
            std::copy(fru_raw, fru_raw + 3, std::back_inserter(data));
246
46
            return;
247
46
        }
248
240
        std::fprintf(stderr, "MgfDate invalid date: %u secs since UNIX epoch\n",
249
240
                     static_cast<unsigned int>(raw));
250
240
    }
251
    // Blank date
252
379
    data.emplace_back(0);
253
379
    data.emplace_back(0);
254
379
    data.emplace_back(0);
255
379
}
256
257
/**
258
 * @brief Builds a section of the common header
259
 *
260
 * @param[in] infoAreaSize size of the FRU area to write
261
 * @param[in] offset Current offset for data in overall record
262
 * @param[in/out] data Common Header section data container
263
 */
264
void buildCommonHeaderSection(const uint32_t& infoAreaSize, uint16_t& offset,
265
                              FruAreaData& data)
266
2.53k
{
267
    // Check if data for internal use section populated
268
2.53k
    if (infoAreaSize == 0)
269
1.40k
    {
270
        // Indicate record not present
271
1.40k
        data.emplace_back(recordNotPresent);
272
1.40k
    }
273
1.13k
    else
274
1.13k
    {
275
        // offset should be multiple of 8.
276
1.13k
        auto remainder = offset % recordUnitOfMeasurement;
277
        // add the padding bytes in the offset so that offset
278
        // will be multiple of 8 byte.
279
1.13k
        offset += (remainder > 0) ? recordUnitOfMeasurement - remainder : 0;
280
        // Place data to define offset to area data section
281
1.13k
        data.emplace_back(offset / recordUnitOfMeasurement);
282
283
1.13k
        offset += infoAreaSize;
284
1.13k
    }
285
2.53k
}
286
287
/**
288
 * @brief Builds the Chassis info area data section
289
 *
290
 * @param[in] propMap map of properties for chassis info area
291
 * @return FruAreaData container with chassis info area
292
 */
293
FruAreaData buildChassisInfoArea(const PropertyMap& propMap)
294
375
{
295
375
    FruAreaData fruAreaData;
296
375
    if (!propMap.empty())
297
375
    {
298
        // Set formatting data that goes at the beginning of the record
299
375
        preFormatProcessing(false, fruAreaData);
300
301
        // chassis type
302
375
        appendChassisType(propMap, fruAreaData);
303
304
        // Chasiss part number, in config.yaml it is configured as model
305
375
        appendData(modelNumber, propMap, fruAreaData);
306
307
        // Board serial number
308
375
        appendData(serialNumber, propMap, fruAreaData);
309
310
        // Indicate End of Custom Fields
311
375
        fruAreaData.emplace_back(endOfCustomFields);
312
313
        // Complete record data formatting
314
375
        postFormatProcessing(fruAreaData);
315
375
    }
316
375
    return fruAreaData;
317
375
}
318
319
/**
320
 * @brief Builds the Board info area data section
321
 *
322
 * @param[in] propMap map of properties for board info area
323
 * @return FruAreaData container with board info area
324
 */
325
FruAreaData buildBoardInfoArea(const PropertyMap& propMap)
326
425
{
327
425
    FruAreaData fruAreaData;
328
425
    if (!propMap.empty())
329
425
    {
330
425
        preFormatProcessing(true, fruAreaData);
331
332
        // Manufacturing date
333
425
        appendMfgDate(propMap, fruAreaData);
334
335
        // manufacturer
336
425
        appendData(manufacturer, propMap, fruAreaData);
337
338
        // Product name/Pretty name
339
425
        appendData(prettyName, propMap, fruAreaData);
340
341
        // Board serial number
342
425
        appendData(serialNumber, propMap, fruAreaData);
343
344
        // Board part number
345
425
        appendData(partNumber, propMap, fruAreaData);
346
347
        // FRU File ID - Empty
348
425
        fruAreaData.emplace_back(typeLengthByteNull);
349
350
        // Empty FRU File ID bytes
351
425
        fruAreaData.emplace_back(recordNotPresent);
352
353
        // End of custom fields
354
425
        fruAreaData.emplace_back(endOfCustomFields);
355
356
425
        postFormatProcessing(fruAreaData);
357
425
    }
358
425
    return fruAreaData;
359
425
}
360
361
/**
362
 * @brief Builds the Product info area data section
363
 *
364
 * @param[in] propMap map of FRU properties for Board info area
365
 * @return FruAreaData container with product info area data
366
 */
367
FruAreaData buildProductInfoArea(const PropertyMap& propMap)
368
333
{
369
333
    FruAreaData fruAreaData;
370
333
    if (!propMap.empty())
371
333
    {
372
        // Set formatting data that goes at the beginning of the record
373
333
        preFormatProcessing(true, fruAreaData);
374
375
        // manufacturer
376
333
        appendData(manufacturer, propMap, fruAreaData);
377
378
        // Product name/Pretty name
379
333
        appendData(prettyName, propMap, fruAreaData);
380
381
        // Product part/model number
382
333
        appendData(modelNumber, propMap, fruAreaData);
383
384
        // Product version
385
333
        appendData(version, propMap, fruAreaData);
386
387
        // Serial Number
388
333
        appendData(serialNumber, propMap, fruAreaData);
389
390
        // Add Asset Tag
391
333
        fruAreaData.emplace_back(recordNotPresent);
392
393
        // FRU File ID - Empty
394
333
        fruAreaData.emplace_back(typeLengthByteNull);
395
396
        // Empty FRU File ID bytes
397
333
        fruAreaData.emplace_back(recordNotPresent);
398
399
        // End of custom fields
400
333
        fruAreaData.emplace_back(endOfCustomFields);
401
402
333
        postFormatProcessing(fruAreaData);
403
333
    }
404
333
    return fruAreaData;
405
333
}
406
407
FruAreaData buildFruAreaData(const FruInventoryData& inventory)
408
845
{
409
845
    FruAreaData combFruArea{};
410
    // Now build common header with data for this FRU Inv Record
411
    // Use this variable to increment size of header as we go along to determine
412
    // offset for the subsequent area offsets
413
845
    uint16_t curDataOffset = commonHeaderFormatSize;
414
    // First byte is id for version of FRU Info Storage Spec used
415
845
    combFruArea.emplace_back(specVersion);
416
417
    // 2nd byte is offset to internal use data
418
845
    combFruArea.emplace_back(recordNotPresent);
419
420
    // 3rd byte is offset to chassis data
421
845
    FruAreaData chassisArea;
422
845
    auto chassisIt = inventory.find(chassis);
423
845
    if (chassisIt != inventory.end())
424
375
    {
425
375
        chassisArea = buildChassisInfoArea(chassisIt->second);
426
375
    }
427
    // update the offset to chassis data.
428
845
    buildCommonHeaderSection(chassisArea.size(), curDataOffset, combFruArea);
429
430
    // 4th byte is offset to board data
431
845
    FruAreaData boardArea;
432
845
    auto boardIt = inventory.find(board);
433
845
    if (boardIt != inventory.end())
434
425
    {
435
425
        boardArea = buildBoardInfoArea(boardIt->second);
436
425
    }
437
    // update the offset to the board data.
438
845
    buildCommonHeaderSection(boardArea.size(), curDataOffset, combFruArea);
439
440
    // 5th byte is offset to product data
441
845
    FruAreaData prodArea;
442
845
    auto prodIt = inventory.find(product);
443
845
    if (prodIt != inventory.end())
444
333
    {
445
333
        prodArea = buildProductInfoArea(prodIt->second);
446
333
    }
447
    // update the offset to the product data.
448
845
    buildCommonHeaderSection(prodArea.size(), curDataOffset, combFruArea);
449
450
    // 6th byte is offset to multirecord data
451
845
    combFruArea.emplace_back(recordNotPresent);
452
453
    // 7th byte is PAD
454
845
    combFruArea.emplace_back(recordNotPresent);
455
456
    // 8th (Final byte of Header Format) is the checksum
457
845
    appendDataChecksum(combFruArea);
458
459
    // Combine everything into one full IPMI FRU specification Record
460
    // add chassis use area data
461
845
    combFruArea.insert(combFruArea.end(), chassisArea.begin(),
462
845
                       chassisArea.end());
463
464
    // add board area data
465
845
    combFruArea.insert(combFruArea.end(), boardArea.begin(), boardArea.end());
466
467
    // add product use area data
468
845
    combFruArea.insert(combFruArea.end(), prodArea.begin(), prodArea.end());
469
470
    // If area is smaller than the minimum size, pad it. This enables ipmitool
471
    // to update the FRU blob with values longer than the original payload.
472
845
    if (combFruArea.size() < fruMinSize)
473
845
    {
474
845
        combFruArea.resize(fruMinSize, fruPadValue);
475
845
    }
476
477
845
    return combFruArea;
478
845
}
479
480
} // namespace fru
481
} // namespace ipmi