blob: ed8ab5af2b7f06d65ede0c627bd2ee9c2bda3a4d [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
125 for (fruAreas area = fruAreas::fruAreaInternal;
126 area <= fruAreas::fruAreaMultirecord; ++area)
127 {
128 // Offset value can be 255.
129 unsigned int areaOffset = device[getHeaderAreaFieldOffset(area)];
130 if (areaOffset == 0)
131 {
132 continue;
133 }
134
135 // MultiRecords are different. area is not tracking section, it's
136 // walking the common header.
137 if (area == fruAreas::fruAreaMultirecord)
138 {
139 hasMultiRecords = true;
140 break;
141 }
142
143 areaOffset *= fruBlockSize;
144
145 if (readBlock(flag, file, address, static_cast<uint16_t>(areaOffset),
146 0x2, blockData.data()) < 0)
147 {
148 std::cerr << "failed to read " << errorHelp << "\n";
149 return {};
150 }
151
152 // Ignore data type (blockData is already unsigned).
153 size_t length = blockData[1] * fruBlockSize;
154 areaOffset += length;
155 fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
156 }
157
158 if (hasMultiRecords)
159 {
160 // device[area count] is the index to the last area because the 0th
161 // entry is not an offset in the common header.
162 unsigned int areaOffset =
163 device[getHeaderAreaFieldOffset(fruAreas::fruAreaMultirecord)];
164 areaOffset *= fruBlockSize;
165
166 // the multi-area record header is 5 bytes long.
167 constexpr size_t multiRecordHeaderSize = 5;
168 constexpr uint8_t multiRecordEndOfListMask = 0x80;
169
170 // Sanity hard-limit to 64KB.
171 while (areaOffset < std::numeric_limits<uint16_t>::max())
172 {
173 // In multi-area, the area offset points to the 0th record, each
174 // record has 3 bytes of the header we care about.
175 if (readBlock(flag, file, address,
176 static_cast<uint16_t>(areaOffset), 0x3,
177 blockData.data()) < 0)
178 {
179 std::cerr << "failed to read " << errorHelp << "\n";
180 return {};
181 }
182
183 // Ok, let's check the record length, which is in bytes (unsigned,
184 // up to 255, so blockData should hold uint8_t not char)
185 size_t recordLength = blockData[2];
186 areaOffset += (recordLength + multiRecordHeaderSize);
187 fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
188
189 // If this is the end of the list bail.
190 if ((blockData[1] & multiRecordEndOfListMask))
191 {
192 break;
193 }
194 }
195 }
196
197 // You already copied these first 8 bytes (the ipmi fru header size)
198 fruLength -= std::min(fruBlockSize, fruLength);
199
200 int readOffset = fruBlockSize;
201
202 while (fruLength > 0)
203 {
204 size_t requestLength =
205 std::min(static_cast<size_t>(I2C_SMBUS_BLOCK_MAX), fruLength);
206
207 if (readBlock(flag, file, address, static_cast<uint16_t>(readOffset),
208 static_cast<uint8_t>(requestLength),
209 blockData.data()) < 0)
210 {
211 std::cerr << "failed to read " << errorHelp << "\n";
212 return {};
213 }
214
215 device.insert(device.end(), blockData.begin(),
216 blockData.begin() + requestLength);
217
218 readOffset += requestLength;
219 fruLength -= std::min(requestLength, fruLength);
220 }
221
222 return device;
223}
224
225unsigned int getHeaderAreaFieldOffset(fruAreas area)
226{
227 return static_cast<unsigned int>(area) + 1;
228}