blob: 4d8354c76ed324adbe86d74e446ea06143a6c6d4 [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
36bool validateHeader(const std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData)
37{
38 // ipmi spec format version number is currently at 1, verify it
39 if (blockData[0] != fruVersion)
40 {
41 if (DEBUG)
42 {
43 std::cerr << "FRU spec version " << (int)(blockData[0])
44 << " not supported. Supported version is "
45 << (int)(fruVersion) << "\n";
46 }
47 return false;
48 }
49
50 // verify pad is set to 0
51 if (blockData[6] != 0x0)
52 {
53 if (DEBUG)
54 {
55 std::cerr << "PAD value in header is non zero, value is "
56 << (int)(blockData[6]) << "\n";
57 }
58 return false;
59 }
60
61 // verify offsets are 0, or don't point to another offset
62 std::set<uint8_t> foundOffsets;
63 for (int ii = 1; ii < 6; ii++)
64 {
65 if (blockData[ii] == 0)
66 {
67 continue;
68 }
69 auto inserted = foundOffsets.insert(blockData[ii]);
70 if (!inserted.second)
71 {
72 return false;
73 }
74 }
75
76 // validate checksum
77 size_t sum = 0;
78 for (int jj = 0; jj < 7; jj++)
79 {
80 sum += blockData[jj];
81 }
82 sum = (256 - sum) & 0xFF;
83
84 if (sum != blockData[7])
85 {
86 if (DEBUG)
87 {
88 std::cerr << "Checksum " << (int)(blockData[7])
89 << " is invalid. calculated checksum is " << (int)(sum)
90 << "\n";
91 }
92 return false;
93 }
94 return true;
95}
96
97std::vector<uint8_t> readFRUContents(int flag, int file, uint16_t address,
98 ReadBlockFunc readBlock,
99 const std::string& errorHelp)
100{
101 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData;
102
103 if (readBlock(flag, file, address, 0x0, 0x8, blockData.data()) < 0)
104 {
105 std::cerr << "failed to read " << errorHelp << "\n";
106 return {};
107 }
108
109 // check the header checksum
110 if (!validateHeader(blockData))
111 {
112 if (DEBUG)
113 {
114 std::cerr << "Illegal header " << errorHelp << "\n";
115 }
116
117 return {};
118 }
119
120 std::vector<uint8_t> device;
121 device.insert(device.end(), blockData.begin(), blockData.begin() + 8);
122
123 bool hasMultiRecords = false;
124 size_t fruLength = fruBlockSize; // At least FRU header is present
Vijay Khemka7792e392021-01-25 13:03:56 -0800125 unsigned int prevOffset = 0;
Patrick Ventureab296412020-12-30 13:39:37 -0800126 for (fruAreas area = fruAreas::fruAreaInternal;
127 area <= fruAreas::fruAreaMultirecord; ++area)
128 {
129 // Offset value can be 255.
130 unsigned int areaOffset = device[getHeaderAreaFieldOffset(area)];
131 if (areaOffset == 0)
132 {
133 continue;
134 }
135
Vijay Khemka7792e392021-01-25 13:03:56 -0800136 /* Check for offset order, as per Section 17 of FRU specification, FRU
137 * information areas are required to be in order in FRU data layout
138 * which means all offset value should be in increasing order or can be
139 * 0 if that area is not present
140 */
141 if (areaOffset <= prevOffset)
142 {
143 std::cerr << "Fru area offsets are not in required order as per "
144 "Section 17 of Fru specification\n";
145 return {};
146 }
147 prevOffset = areaOffset;
148
Patrick Ventureab296412020-12-30 13:39:37 -0800149 // MultiRecords are different. area is not tracking section, it's
150 // walking the common header.
151 if (area == fruAreas::fruAreaMultirecord)
152 {
153 hasMultiRecords = true;
154 break;
155 }
156
157 areaOffset *= fruBlockSize;
158
159 if (readBlock(flag, file, address, static_cast<uint16_t>(areaOffset),
160 0x2, blockData.data()) < 0)
161 {
162 std::cerr << "failed to read " << errorHelp << "\n";
163 return {};
164 }
165
166 // Ignore data type (blockData is already unsigned).
167 size_t length = blockData[1] * fruBlockSize;
168 areaOffset += length;
169 fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
170 }
171
172 if (hasMultiRecords)
173 {
174 // device[area count] is the index to the last area because the 0th
175 // entry is not an offset in the common header.
176 unsigned int areaOffset =
177 device[getHeaderAreaFieldOffset(fruAreas::fruAreaMultirecord)];
178 areaOffset *= fruBlockSize;
179
180 // the multi-area record header is 5 bytes long.
181 constexpr size_t multiRecordHeaderSize = 5;
182 constexpr uint8_t multiRecordEndOfListMask = 0x80;
183
184 // Sanity hard-limit to 64KB.
185 while (areaOffset < std::numeric_limits<uint16_t>::max())
186 {
187 // In multi-area, the area offset points to the 0th record, each
188 // record has 3 bytes of the header we care about.
189 if (readBlock(flag, file, address,
190 static_cast<uint16_t>(areaOffset), 0x3,
191 blockData.data()) < 0)
192 {
193 std::cerr << "failed to read " << errorHelp << "\n";
194 return {};
195 }
196
197 // Ok, let's check the record length, which is in bytes (unsigned,
198 // up to 255, so blockData should hold uint8_t not char)
199 size_t recordLength = blockData[2];
200 areaOffset += (recordLength + multiRecordHeaderSize);
201 fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
202
203 // If this is the end of the list bail.
204 if ((blockData[1] & multiRecordEndOfListMask))
205 {
206 break;
207 }
208 }
209 }
210
211 // You already copied these first 8 bytes (the ipmi fru header size)
212 fruLength -= std::min(fruBlockSize, fruLength);
213
214 int readOffset = fruBlockSize;
215
216 while (fruLength > 0)
217 {
218 size_t requestLength =
219 std::min(static_cast<size_t>(I2C_SMBUS_BLOCK_MAX), fruLength);
220
221 if (readBlock(flag, file, address, static_cast<uint16_t>(readOffset),
222 static_cast<uint8_t>(requestLength),
223 blockData.data()) < 0)
224 {
225 std::cerr << "failed to read " << errorHelp << "\n";
226 return {};
227 }
228
229 device.insert(device.end(), blockData.begin(),
230 blockData.begin() + requestLength);
231
232 readOffset += requestLength;
233 fruLength -= std::min(requestLength, fruLength);
234 }
235
236 return device;
237}
238
239unsigned int getHeaderAreaFieldOffset(fruAreas area)
240{
241 return static_cast<unsigned int>(area) + 1;
242}