blob: 9bfcf30d447f099ab5df001d0c8ae69c7729df20 [file] [log] [blame]
Patrick Ventureab296412020-12-30 13:39:37 -08001/*
2// Copyright (c) 2018 Intel Corporation
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15*/
16/// \file FruUtils.cpp
17
18#include "FruUtils.hpp"
19
20#include <array>
21#include <cstdint>
22#include <iostream>
23#include <set>
24#include <string>
25#include <vector>
26
27extern "C"
28{
29// Include for I2C_SMBUS_BLOCK_MAX
30#include <linux/i2c.h>
31}
32
33static constexpr bool DEBUG = false;
34constexpr size_t fruVersion = 1; // Current FRU spec version number is 1
35
Vijay Khemka06d1b4a2021-02-09 18:39:11 +000036static constexpr std::array<const char*, 5> FRU_AREAS_NAMES = {
37 "INTERNAL", "CHASSIS", "BOARD", "PRODUCT", "MULTIRECORD"};
38
39std::string getFruAreaName(fruAreas area)
40{
41 // Check range of passed area value
42 if (static_cast<unsigned int>(area) >= FRU_AREAS_NAMES.size())
43 {
44 std::cerr << "Error: Fru area is out of range\n";
45 return "";
46 }
47
48 return std::string(FRU_AREAS_NAMES[static_cast<unsigned int>(area)]);
49}
50
Patrick Ventureab296412020-12-30 13:39:37 -080051bool validateHeader(const std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData)
52{
53 // ipmi spec format version number is currently at 1, verify it
54 if (blockData[0] != fruVersion)
55 {
56 if (DEBUG)
57 {
58 std::cerr << "FRU spec version " << (int)(blockData[0])
59 << " not supported. Supported version is "
60 << (int)(fruVersion) << "\n";
61 }
62 return false;
63 }
64
65 // verify pad is set to 0
66 if (blockData[6] != 0x0)
67 {
68 if (DEBUG)
69 {
70 std::cerr << "PAD value in header is non zero, value is "
71 << (int)(blockData[6]) << "\n";
72 }
73 return false;
74 }
75
76 // verify offsets are 0, or don't point to another offset
77 std::set<uint8_t> foundOffsets;
78 for (int ii = 1; ii < 6; ii++)
79 {
80 if (blockData[ii] == 0)
81 {
82 continue;
83 }
84 auto inserted = foundOffsets.insert(blockData[ii]);
85 if (!inserted.second)
86 {
87 return false;
88 }
89 }
90
91 // validate checksum
92 size_t sum = 0;
93 for (int jj = 0; jj < 7; jj++)
94 {
95 sum += blockData[jj];
96 }
97 sum = (256 - sum) & 0xFF;
98
99 if (sum != blockData[7])
100 {
101 if (DEBUG)
102 {
103 std::cerr << "Checksum " << (int)(blockData[7])
104 << " is invalid. calculated checksum is " << (int)(sum)
105 << "\n";
106 }
107 return false;
108 }
109 return true;
110}
111
Vijay Khemka06d1b4a2021-02-09 18:39:11 +0000112/* This function verifies for other offsets to check if they are not
113 * falling under other field area
114 *
115 * fruBytes: Start of Fru data
116 * currentArea: Index of current area offset to be compared against all area
117 * offset and it is a multiple of 8 bytes as per specification
118 * len: Length of current area space and it is a multiple of 8 bytes
119 * as per specification
120 */
121bool verifyOffset(const std::vector<uint8_t>& fruBytes, fruAreas currentArea,
122 uint8_t len)
123{
124
125 unsigned int fruBytesSize = fruBytes.size();
126
127 // check if Fru data has at least 8 byte header
128 if (fruBytesSize <= fruBlockSize)
129 {
130 std::cerr << "Error: trying to parse empty FRU\n";
131 return false;
132 }
133
134 // Check range of passed currentArea value
135 if (currentArea > fruAreas::fruAreaMultirecord)
136 {
137 std::cerr << "Error: Fru area is out of range\n";
138 return false;
139 }
140
141 unsigned int currentAreaIndex = getHeaderAreaFieldOffset(currentArea);
142 if (currentAreaIndex > fruBytesSize)
143 {
144 std::cerr << "Error: Fru area index is out of range\n";
145 return false;
146 }
147
148 unsigned int start = fruBytes[currentAreaIndex];
149 unsigned int end = start + len;
150
151 /* Verify each offset within the range of start and end */
152 for (fruAreas area = fruAreas::fruAreaInternal;
153 area <= fruAreas::fruAreaMultirecord; ++area)
154 {
155 // skip the current offset
156 if (area == currentArea)
157 {
158 continue;
159 }
160
161 unsigned int areaIndex = getHeaderAreaFieldOffset(area);
162 if (areaIndex > fruBytesSize)
163 {
164 std::cerr << "Error: Fru area index is out of range\n";
165 return false;
166 }
167
168 unsigned int areaOffset = fruBytes[areaIndex];
169 // if areaOffset is 0 means this area is not available so skip
170 if (areaOffset == 0)
171 {
172 continue;
173 }
174
175 // check for overlapping of current offset with given areaoffset
176 if (areaOffset == start || (areaOffset > start && areaOffset < end))
177 {
178 std::cerr << getFruAreaName(currentArea)
179 << " offset is overlapping with " << getFruAreaName(area)
180 << " offset\n";
181 return false;
182 }
183 }
184 return true;
185}
186
Patrick Ventureab296412020-12-30 13:39:37 -0800187std::vector<uint8_t> readFRUContents(int flag, int file, uint16_t address,
188 ReadBlockFunc readBlock,
189 const std::string& errorHelp)
190{
191 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData;
192
193 if (readBlock(flag, file, address, 0x0, 0x8, blockData.data()) < 0)
194 {
195 std::cerr << "failed to read " << errorHelp << "\n";
196 return {};
197 }
198
199 // check the header checksum
200 if (!validateHeader(blockData))
201 {
202 if (DEBUG)
203 {
204 std::cerr << "Illegal header " << errorHelp << "\n";
205 }
206
207 return {};
208 }
209
210 std::vector<uint8_t> device;
211 device.insert(device.end(), blockData.begin(), blockData.begin() + 8);
212
213 bool hasMultiRecords = false;
214 size_t fruLength = fruBlockSize; // At least FRU header is present
Vijay Khemka7792e392021-01-25 13:03:56 -0800215 unsigned int prevOffset = 0;
Patrick Ventureab296412020-12-30 13:39:37 -0800216 for (fruAreas area = fruAreas::fruAreaInternal;
217 area <= fruAreas::fruAreaMultirecord; ++area)
218 {
219 // Offset value can be 255.
220 unsigned int areaOffset = device[getHeaderAreaFieldOffset(area)];
221 if (areaOffset == 0)
222 {
223 continue;
224 }
225
Vijay Khemka7792e392021-01-25 13:03:56 -0800226 /* Check for offset order, as per Section 17 of FRU specification, FRU
227 * information areas are required to be in order in FRU data layout
228 * which means all offset value should be in increasing order or can be
229 * 0 if that area is not present
230 */
231 if (areaOffset <= prevOffset)
232 {
233 std::cerr << "Fru area offsets are not in required order as per "
234 "Section 17 of Fru specification\n";
235 return {};
236 }
237 prevOffset = areaOffset;
238
Patrick Ventureab296412020-12-30 13:39:37 -0800239 // MultiRecords are different. area is not tracking section, it's
240 // walking the common header.
241 if (area == fruAreas::fruAreaMultirecord)
242 {
243 hasMultiRecords = true;
244 break;
245 }
246
247 areaOffset *= fruBlockSize;
248
249 if (readBlock(flag, file, address, static_cast<uint16_t>(areaOffset),
250 0x2, blockData.data()) < 0)
251 {
252 std::cerr << "failed to read " << errorHelp << "\n";
253 return {};
254 }
255
256 // Ignore data type (blockData is already unsigned).
257 size_t length = blockData[1] * fruBlockSize;
258 areaOffset += length;
259 fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
260 }
261
262 if (hasMultiRecords)
263 {
264 // device[area count] is the index to the last area because the 0th
265 // entry is not an offset in the common header.
266 unsigned int areaOffset =
267 device[getHeaderAreaFieldOffset(fruAreas::fruAreaMultirecord)];
268 areaOffset *= fruBlockSize;
269
270 // the multi-area record header is 5 bytes long.
271 constexpr size_t multiRecordHeaderSize = 5;
272 constexpr uint8_t multiRecordEndOfListMask = 0x80;
273
274 // Sanity hard-limit to 64KB.
275 while (areaOffset < std::numeric_limits<uint16_t>::max())
276 {
277 // In multi-area, the area offset points to the 0th record, each
278 // record has 3 bytes of the header we care about.
279 if (readBlock(flag, file, address,
280 static_cast<uint16_t>(areaOffset), 0x3,
281 blockData.data()) < 0)
282 {
283 std::cerr << "failed to read " << errorHelp << "\n";
284 return {};
285 }
286
287 // Ok, let's check the record length, which is in bytes (unsigned,
288 // up to 255, so blockData should hold uint8_t not char)
289 size_t recordLength = blockData[2];
290 areaOffset += (recordLength + multiRecordHeaderSize);
291 fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
292
293 // If this is the end of the list bail.
294 if ((blockData[1] & multiRecordEndOfListMask))
295 {
296 break;
297 }
298 }
299 }
300
301 // You already copied these first 8 bytes (the ipmi fru header size)
302 fruLength -= std::min(fruBlockSize, fruLength);
303
304 int readOffset = fruBlockSize;
305
306 while (fruLength > 0)
307 {
308 size_t requestLength =
309 std::min(static_cast<size_t>(I2C_SMBUS_BLOCK_MAX), fruLength);
310
311 if (readBlock(flag, file, address, static_cast<uint16_t>(readOffset),
312 static_cast<uint8_t>(requestLength),
313 blockData.data()) < 0)
314 {
315 std::cerr << "failed to read " << errorHelp << "\n";
316 return {};
317 }
318
319 device.insert(device.end(), blockData.begin(),
320 blockData.begin() + requestLength);
321
322 readOffset += requestLength;
323 fruLength -= std::min(requestLength, fruLength);
324 }
325
326 return device;
327}
328
329unsigned int getHeaderAreaFieldOffset(fruAreas area)
330{
331 return static_cast<unsigned int>(area) + 1;
332}