blob: 3e7cd1a083ab06de3f76fa46f49c7dd1435b055b [file] [log] [blame]
James Feist3cb5fec2018-01-23 14:41:51 -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*/
Brad Bishop1fb9f3f2020-08-28 08:15:13 -040016/// \file FruDevice.cpp
James Feist3cb5fec2018-01-23 14:41:51 -080017
Patrick Venturea49dc332019-10-26 08:32:02 -070018#include "Utils.hpp"
19
James Feist3b860982018-10-02 14:34:07 -070020#include <errno.h>
James Feist3cb5fec2018-01-23 14:41:51 -080021#include <fcntl.h>
James Feist3b860982018-10-02 14:34:07 -070022#include <sys/inotify.h>
23#include <sys/ioctl.h>
24
James Feist3b860982018-10-02 14:34:07 -070025#include <boost/algorithm/string/predicate.hpp>
Brad Bishop82c84bb2020-08-26 14:12:50 -040026#include <boost/asio/deadline_timer.hpp>
27#include <boost/asio/io_service.hpp>
James Feist3b860982018-10-02 14:34:07 -070028#include <boost/container/flat_map.hpp>
James Feist8c505da2020-05-28 10:06:33 -070029#include <nlohmann/json.hpp>
30#include <sdbusplus/asio/connection.hpp>
31#include <sdbusplus/asio/object_server.hpp>
32
33#include <array>
James Feist3b860982018-10-02 14:34:07 -070034#include <chrono>
35#include <ctime>
Patrick Venturee3754002019-08-06 09:39:12 -070036#include <filesystem>
James Feist3cb5fec2018-01-23 14:41:51 -080037#include <fstream>
Patrick Venturebaba7672019-10-26 09:26:41 -070038#include <functional>
James Feist3cb5fec2018-01-23 14:41:51 -080039#include <future>
Patrick Venturec50e1ff2019-08-06 10:22:28 -070040#include <iomanip>
James Feist3cb5fec2018-01-23 14:41:51 -080041#include <iostream>
Patrick Venturee26395d2019-10-29 14:05:16 -070042#include <limits>
James Feist3f8a2782018-02-12 09:24:42 -080043#include <regex>
Patrick Venturec50e1ff2019-08-06 10:22:28 -070044#include <set>
45#include <sstream>
Patrick Venture11f1ff42019-08-01 10:42:12 -070046#include <string>
James Feist3b860982018-10-02 14:34:07 -070047#include <thread>
Jeremy Kerr4e6a62f2020-06-09 11:26:40 +080048#include <utility>
James Feista465ccc2019-02-08 12:51:01 -080049#include <variant>
Patrick Venturec274f2f2019-10-26 09:27:23 -070050#include <vector>
James Feist3b860982018-10-02 14:34:07 -070051
James Feist8c505da2020-05-28 10:06:33 -070052extern "C"
53{
James Feist3b860982018-10-02 14:34:07 -070054#include <i2c/smbus.h>
55#include <linux/i2c-dev.h>
56}
James Feist3cb5fec2018-01-23 14:41:51 -080057
Ed Tanous072e25d2018-12-16 21:45:20 -080058namespace fs = std::filesystem;
James Feist3cb5fec2018-01-23 14:41:51 -080059static constexpr bool DEBUG = false;
60static size_t UNKNOWN_BUS_OBJECT_COUNT = 0;
James Feistb49ffc32018-05-02 11:10:43 -070061constexpr size_t MAX_FRU_SIZE = 512;
62constexpr size_t MAX_EEPROM_PAGE_INDEX = 255;
James Feist26c27ad2018-07-25 15:09:40 -070063constexpr size_t busTimeoutSeconds = 5;
Andrei Kartashev2f0de172020-08-13 14:29:40 +030064constexpr size_t fruBlockSize = 8; // FRU areas are measured in 8-byte blocks
James Feist3cb5fec2018-01-23 14:41:51 -080065
Patrick Venture11f1ff42019-08-01 10:42:12 -070066constexpr const char* blacklistPath = PACKAGE_DIR "blacklist.json";
67
James Feista465ccc2019-02-08 12:51:01 -080068const static constexpr char* BASEBOARD_FRU_LOCATION =
James Feist3cb5fec2018-01-23 14:41:51 -080069 "/etc/fru/baseboard.fru.bin";
70
James Feista465ccc2019-02-08 12:51:01 -080071const static constexpr char* I2C_DEV_LOCATION = "/dev";
James Feist4131aea2018-03-09 09:47:30 -080072
Andrei Kartashevb45324a2020-10-14 17:01:47 +030073enum class resCodes
74{
75 resOK,
76 resWarn,
77 resErr
78};
79
Andrei Kartashev9f0f2d12020-08-20 04:24:37 +030080enum class fruAreas
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +030081{
Andrei Kartashev9f0f2d12020-08-20 04:24:37 +030082 fruAreaInternal = 0,
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +030083 fruAreaChassis,
84 fruAreaBoard,
85 fruAreaProduct,
86 fruAreaMultirecord
87};
Andrei Kartashev9f0f2d12020-08-20 04:24:37 +030088inline fruAreas operator++(fruAreas& x)
89{
90 return x = static_cast<fruAreas>(std::underlying_type<fruAreas>::type(x) +
91 1);
92}
93
94static const std::vector<std::string> FRU_AREA_NAMES = {
James Feist3cb5fec2018-01-23 14:41:51 -080095 "INTERNAL", "CHASSIS", "BOARD", "PRODUCT", "MULTIRECORD"};
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -070096const static std::regex NON_ASCII_REGEX("[^\x01-\x7f]");
Andrei Kartashev1c5b7062020-08-19 14:47:30 +030097using DeviceMap = boost::container::flat_map<int, std::vector<uint8_t>>;
James Feist3cb5fec2018-01-23 14:41:51 -080098using BusMap = boost::container::flat_map<int, std::shared_ptr<DeviceMap>>;
99
James Feist444830e2019-04-05 08:38:16 -0700100static std::set<size_t> busBlacklist;
James Feist6ebf9de2018-05-15 15:01:17 -0700101struct FindDevicesWithCallback;
102
Nikhil Potaded8884f12019-03-27 13:27:13 -0700103static BusMap busMap;
104
James Feistcb5661d2020-06-12 15:05:01 -0700105static bool powerIsOn = false;
106
James Feist8a983922019-10-24 17:11:56 -0700107static boost::container::flat_map<
108 std::pair<size_t, size_t>, std::shared_ptr<sdbusplus::asio::dbus_interface>>
109 foundDevices;
110
James Feist7972bb92019-11-13 15:59:24 -0800111static boost::container::flat_map<size_t, std::set<size_t>> failedAddresses;
112
James Feist5cb06082019-10-24 17:11:13 -0700113boost::asio::io_service io;
114auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
115auto objServer = sdbusplus::asio::object_server(systemBus);
116
Andrei Kartashev6cfb7752020-08-10 19:50:33 +0300117static const std::vector<std::string> CHASSIS_FRU_AREAS = {"PART_NUMBER",
118 "SERIAL_NUMBER"};
Joshi-Mansid73b7442020-03-27 19:10:21 +0530119
Andrei Kartashev2f0de172020-08-13 14:29:40 +0300120static const std::vector<std::string> BOARD_FRU_AREAS = {
Andrei Kartashev6cfb7752020-08-10 19:50:33 +0300121 "MANUFACTURER", "PRODUCT_NAME", "SERIAL_NUMBER", "PART_NUMBER",
122 "FRU_VERSION_ID"};
Joshi-Mansid73b7442020-03-27 19:10:21 +0530123
Andrei Kartashev2f0de172020-08-13 14:29:40 +0300124static const std::vector<std::string> PRODUCT_FRU_AREAS = {
Andrei Kartashev6cfb7752020-08-10 19:50:33 +0300125 "MANUFACTURER", "PRODUCT_NAME", "PART_NUMBER", "VERSION",
126 "SERIAL_NUMBER", "ASSET_TAG", "FRU_VERSION_ID"};
127
128static const std::string FRU_CUSTOM_FIELD_NAME = "INFO_AM";
Joshi-Mansid73b7442020-03-27 19:10:21 +0530129
Andrei Kartashev9f0f2d12020-08-20 04:24:37 +0300130static inline unsigned int getHeaderAreaFieldOffset(fruAreas area)
131{
132 return static_cast<unsigned int>(area) + 1;
133}
134static inline const std::string& getFruAreaName(fruAreas area)
135{
136 return FRU_AREA_NAMES[static_cast<unsigned int>(area)];
137}
Patrick Venturebaba7672019-10-26 09:26:41 -0700138bool validateHeader(const std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData);
Andrei Kartashev2a7e3952020-09-02 20:32:26 +0300139uint8_t calculateChecksum(std::vector<uint8_t>::const_iterator iter,
140 std::vector<uint8_t>::const_iterator end);
Andrei Kartashev2f0de172020-08-13 14:29:40 +0300141bool updateFRUProperty(
Joshi-Mansid73b7442020-03-27 19:10:21 +0530142 const std::string& assetTag, uint32_t bus, uint32_t address,
143 std::string propertyName,
144 boost::container::flat_map<
145 std::pair<size_t, size_t>,
146 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap);
Patrick Venturebaba7672019-10-26 09:26:41 -0700147
Xiang Liu2801a702020-01-20 14:29:34 -0800148using ReadBlockFunc =
149 std::function<int64_t(int flag, int file, uint16_t address, uint16_t offset,
150 uint8_t length, uint8_t* outBuf)>;
Patrick Venturebaba7672019-10-26 09:26:41 -0700151
152// Read and validate FRU contents, given the flag required for raw i2c, the open
153// file handle, a read method, and a helper string for failures.
Andrei Kartashev2f0de172020-08-13 14:29:40 +0300154std::vector<uint8_t> readFRUContents(int flag, int file, uint16_t address,
Andrei Kartashev1c5b7062020-08-19 14:47:30 +0300155 ReadBlockFunc readBlock,
156 const std::string& errorHelp)
Patrick Venturebaba7672019-10-26 09:26:41 -0700157{
158 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData;
159
Xiang Liu2801a702020-01-20 14:29:34 -0800160 if (readBlock(flag, file, address, 0x0, 0x8, blockData.data()) < 0)
Patrick Venturebaba7672019-10-26 09:26:41 -0700161 {
162 std::cerr << "failed to read " << errorHelp << "\n";
163 return {};
164 }
165
166 // check the header checksum
167 if (!validateHeader(blockData))
168 {
169 if (DEBUG)
170 {
171 std::cerr << "Illegal header " << errorHelp << "\n";
172 }
173
174 return {};
175 }
176
Andrei Kartashev1c5b7062020-08-19 14:47:30 +0300177 std::vector<uint8_t> device;
Patrick Venturebaba7672019-10-26 09:26:41 -0700178 device.insert(device.end(), blockData.begin(), blockData.begin() + 8);
179
Patrick Venturee26395d2019-10-29 14:05:16 -0700180 bool hasMultiRecords = false;
Brad Bishopef3cdc92020-10-01 10:36:21 -0400181 size_t fruLength = fruBlockSize; // At least FRU header is present
Andrei Kartashev9f0f2d12020-08-20 04:24:37 +0300182 for (fruAreas area = fruAreas::fruAreaInternal;
183 area <= fruAreas::fruAreaMultirecord; ++area)
Patrick Venturebaba7672019-10-26 09:26:41 -0700184 {
Patrick Venturef2ad76b2019-10-30 08:49:27 -0700185 // Offset value can be 255.
Andrei Kartashev9f0f2d12020-08-20 04:24:37 +0300186 unsigned int areaOffset = device[getHeaderAreaFieldOffset(area)];
Patrick Venturebaba7672019-10-26 09:26:41 -0700187 if (areaOffset == 0)
188 {
189 continue;
190 }
191
Andrei Kartashev9f0f2d12020-08-20 04:24:37 +0300192 // MultiRecords are different. area is not tracking section, it's
193 // walking the common header.
194 if (area == fruAreas::fruAreaMultirecord)
Patrick Venturee26395d2019-10-29 14:05:16 -0700195 {
196 hasMultiRecords = true;
197 break;
198 }
199
Andrei Kartashev2f0de172020-08-13 14:29:40 +0300200 areaOffset *= fruBlockSize;
Patrick Venturebaba7672019-10-26 09:26:41 -0700201
Xiang Liu2801a702020-01-20 14:29:34 -0800202 if (readBlock(flag, file, address, static_cast<uint16_t>(areaOffset),
203 0x2, blockData.data()) < 0)
Patrick Venturebaba7672019-10-26 09:26:41 -0700204 {
205 std::cerr << "failed to read " << errorHelp << "\n";
206 return {};
207 }
208
Patrick Venturef2ad76b2019-10-30 08:49:27 -0700209 // Ignore data type (blockData is already unsigned).
Andrei Kartashev2f0de172020-08-13 14:29:40 +0300210 size_t length = blockData[1] * fruBlockSize;
Patrick Venturebaba7672019-10-26 09:26:41 -0700211 areaOffset += length;
212 fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
213 }
214
Patrick Venturee26395d2019-10-29 14:05:16 -0700215 if (hasMultiRecords)
216 {
217 // device[area count] is the index to the last area because the 0th
218 // entry is not an offset in the common header.
Andrei Kartashev9f0f2d12020-08-20 04:24:37 +0300219 unsigned int areaOffset =
220 device[getHeaderAreaFieldOffset(fruAreas::fruAreaMultirecord)];
Andrei Kartashev2f0de172020-08-13 14:29:40 +0300221 areaOffset *= fruBlockSize;
Patrick Venturee26395d2019-10-29 14:05:16 -0700222
223 // the multi-area record header is 5 bytes long.
Andrei Kartashev2f0de172020-08-13 14:29:40 +0300224 constexpr size_t multiRecordHeaderSize = 5;
225 constexpr uint8_t multiRecordEndOfListMask = 0x80;
Patrick Venturee26395d2019-10-29 14:05:16 -0700226
227 // Sanity hard-limit to 64KB.
228 while (areaOffset < std::numeric_limits<uint16_t>::max())
229 {
230 // In multi-area, the area offset points to the 0th record, each
231 // record has 3 bytes of the header we care about.
Xiang Liu2801a702020-01-20 14:29:34 -0800232 if (readBlock(flag, file, address,
233 static_cast<uint16_t>(areaOffset), 0x3,
Patrick Venturee26395d2019-10-29 14:05:16 -0700234 blockData.data()) < 0)
235 {
236 std::cerr << "failed to read " << errorHelp << "\n";
237 return {};
238 }
239
240 // Ok, let's check the record length, which is in bytes (unsigned,
241 // up to 255, so blockData should hold uint8_t not char)
Andrei Kartashev2f0de172020-08-13 14:29:40 +0300242 size_t recordLength = blockData[2];
Patrick Venturee26395d2019-10-29 14:05:16 -0700243 areaOffset += (recordLength + multiRecordHeaderSize);
244 fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
245
246 // If this is the end of the list bail.
Andrei Kartashev2f0de172020-08-13 14:29:40 +0300247 if ((blockData[1] & multiRecordEndOfListMask))
Patrick Venturee26395d2019-10-29 14:05:16 -0700248 {
249 break;
250 }
251 }
252 }
253
Patrick Venturebaba7672019-10-26 09:26:41 -0700254 // You already copied these first 8 bytes (the ipmi fru header size)
Brad Bishopef3cdc92020-10-01 10:36:21 -0400255 fruLength -= std::min(fruBlockSize, fruLength);
Patrick Venturebaba7672019-10-26 09:26:41 -0700256
Andrei Kartashev2f0de172020-08-13 14:29:40 +0300257 int readOffset = fruBlockSize;
Patrick Venturebaba7672019-10-26 09:26:41 -0700258
259 while (fruLength > 0)
260 {
Brad Bishopef3cdc92020-10-01 10:36:21 -0400261 size_t requestLength =
262 std::min(static_cast<size_t>(I2C_SMBUS_BLOCK_MAX), fruLength);
Patrick Venturebaba7672019-10-26 09:26:41 -0700263
Xiang Liu2801a702020-01-20 14:29:34 -0800264 if (readBlock(flag, file, address, static_cast<uint16_t>(readOffset),
Patrick Venturebaba7672019-10-26 09:26:41 -0700265 static_cast<uint8_t>(requestLength),
266 blockData.data()) < 0)
267 {
268 std::cerr << "failed to read " << errorHelp << "\n";
269 return {};
270 }
271
272 device.insert(device.end(), blockData.begin(),
273 blockData.begin() + requestLength);
274
275 readOffset += requestLength;
Brad Bishopef3cdc92020-10-01 10:36:21 -0400276 fruLength -= std::min(requestLength, fruLength);
Patrick Venturebaba7672019-10-26 09:26:41 -0700277 }
278
279 return device;
280}
281
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700282// Given a bus/address, produce the path in sysfs for an eeprom.
283static std::string getEepromPath(size_t bus, size_t address)
284{
285 std::stringstream output;
286 output << "/sys/bus/i2c/devices/" << bus << "-" << std::right
287 << std::setfill('0') << std::setw(4) << std::hex << address
288 << "/eeprom";
289 return output.str();
290}
291
292static bool hasEepromFile(size_t bus, size_t address)
293{
294 auto path = getEepromPath(bus, address);
295 try
296 {
297 return fs::exists(path);
298 }
299 catch (...)
300 {
301 return false;
302 }
303}
304
Patrick Venture3ac8e4f2019-10-28 13:07:21 -0700305static int64_t readFromEeprom(int flag __attribute__((unused)), int fd,
Xiang Liu2801a702020-01-20 14:29:34 -0800306 uint16_t address __attribute__((unused)),
Patrick Venture3ac8e4f2019-10-28 13:07:21 -0700307 uint16_t offset, uint8_t len, uint8_t* buf)
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700308{
309 auto result = lseek(fd, offset, SEEK_SET);
310 if (result < 0)
311 {
312 std::cerr << "failed to seek\n";
313 return -1;
314 }
315
Patrick Venture3ac8e4f2019-10-28 13:07:21 -0700316 return read(fd, buf, len);
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700317}
318
James Feist5d7f4692020-06-17 18:22:33 -0700319static int busStrToInt(const std::string& busName)
320{
321 auto findBus = busName.rfind("-");
322 if (findBus == std::string::npos)
323 {
324 return -1;
325 }
326 return std::stoi(busName.substr(findBus + 1));
327}
328
James Feist7972bb92019-11-13 15:59:24 -0800329static int getRootBus(size_t bus)
330{
331 auto ec = std::error_code();
332 auto path = std::filesystem::read_symlink(
333 std::filesystem::path("/sys/bus/i2c/devices/i2c-" +
334 std::to_string(bus) + "/mux_device"),
335 ec);
336 if (ec)
337 {
338 return -1;
339 }
340
341 std::string filename = path.filename();
342 auto findBus = filename.find("-");
343 if (findBus == std::string::npos)
344 {
345 return -1;
346 }
347 return std::stoi(filename.substr(0, findBus));
348}
349
James Feistc95cb142018-02-26 10:41:42 -0800350static bool isMuxBus(size_t bus)
351{
Ed Tanous072e25d2018-12-16 21:45:20 -0800352 return is_symlink(std::filesystem::path(
James Feistc95cb142018-02-26 10:41:42 -0800353 "/sys/bus/i2c/devices/i2c-" + std::to_string(bus) + "/mux_device"));
354}
355
James Feist8a983922019-10-24 17:11:56 -0700356static void makeProbeInterface(size_t bus, size_t address)
357{
358 if (isMuxBus(bus))
359 {
360 return; // the mux buses are random, no need to publish
361 }
362 auto [it, success] = foundDevices.emplace(
363 std::make_pair(bus, address),
364 objServer.add_interface(
365 "/xyz/openbmc_project/FruDevice/" + std::to_string(bus) + "_" +
366 std::to_string(address),
367 "xyz.openbmc_project.Inventory.Item.I2CDevice"));
368 if (!success)
369 {
370 return; // already added
371 }
372 it->second->register_property("Bus", bus);
373 it->second->register_property("Address", address);
374 it->second->initialize();
375}
376
Vijay Khemka2d681f62018-11-06 15:51:00 -0800377static int isDevice16Bit(int file)
378{
379 /* Get first byte */
380 int byte1 = i2c_smbus_read_byte_data(file, 0);
381 if (byte1 < 0)
382 {
383 return byte1;
384 }
385 /* Read 7 more bytes, it will read same first byte in case of
386 * 8 bit but it will read next byte in case of 16 bit
387 */
388 for (int i = 0; i < 7; i++)
389 {
390 int byte2 = i2c_smbus_read_byte_data(file, 0);
391 if (byte2 < 0)
392 {
393 return byte2;
394 }
395 if (byte2 != byte1)
396 {
397 return 1;
398 }
399 }
400 return 0;
401}
402
Xiang Liu2801a702020-01-20 14:29:34 -0800403// Issue an I2C transaction to first write to_slave_buf_len bytes,then read
404// from_slave_buf_len bytes.
405static int i2c_smbus_write_then_read(int file, uint16_t address,
406 uint8_t* toSlaveBuf, uint8_t toSlaveBufLen,
407 uint8_t* fromSlaveBuf,
408 uint8_t fromSlaveBufLen)
Vijay Khemka2d681f62018-11-06 15:51:00 -0800409{
Xiang Liu2801a702020-01-20 14:29:34 -0800410 if (toSlaveBuf == NULL || toSlaveBufLen == 0 || fromSlaveBuf == NULL ||
411 fromSlaveBufLen == 0)
412 {
413 return -1;
414 }
Vijay Khemka2d681f62018-11-06 15:51:00 -0800415
Xiang Liu2801a702020-01-20 14:29:34 -0800416#define SMBUS_IOCTL_WRITE_THEN_READ_MSG_COUNT 2
417 struct i2c_msg msgs[SMBUS_IOCTL_WRITE_THEN_READ_MSG_COUNT];
418 struct i2c_rdwr_ioctl_data rdwr;
419
420 msgs[0].addr = address;
421 msgs[0].flags = 0;
422 msgs[0].len = toSlaveBufLen;
423 msgs[0].buf = toSlaveBuf;
424 msgs[1].addr = address;
425 msgs[1].flags = I2C_M_RD;
426 msgs[1].len = fromSlaveBufLen;
427 msgs[1].buf = fromSlaveBuf;
428
429 rdwr.msgs = msgs;
430 rdwr.nmsgs = SMBUS_IOCTL_WRITE_THEN_READ_MSG_COUNT;
431
432 int ret = ioctl(file, I2C_RDWR, &rdwr);
433
434 return (ret == SMBUS_IOCTL_WRITE_THEN_READ_MSG_COUNT) ? ret : -1;
435}
436
437static int64_t readBlockData(int flag, int file, uint16_t address,
438 uint16_t offset, uint8_t len, uint8_t* buf)
439{
Vijay Khemka2d681f62018-11-06 15:51:00 -0800440 if (flag == 0)
441 {
Xiang Liu2801a702020-01-20 14:29:34 -0800442 return i2c_smbus_read_i2c_block_data(file, static_cast<uint8_t>(offset),
443 len, buf);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800444 }
445
Xiang Liu2801a702020-01-20 14:29:34 -0800446 offset = htobe16(offset);
447 return i2c_smbus_write_then_read(
448 file, address, reinterpret_cast<uint8_t*>(&offset), 2, buf, len);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800449}
450
James Feist24bae7a2019-04-03 09:50:56 -0700451bool validateHeader(const std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData)
452{
453 // ipmi spec format version number is currently at 1, verify it
454 if (blockData[0] != 0x1)
455 {
456 return false;
457 }
458
459 // verify pad is set to 0
460 if (blockData[6] != 0x0)
461 {
462 return false;
463 }
464
465 // verify offsets are 0, or don't point to another offset
466 std::set<uint8_t> foundOffsets;
467 for (int ii = 1; ii < 6; ii++)
468 {
469 if (blockData[ii] == 0)
470 {
471 continue;
472 }
James Feist0eb40352019-04-09 14:44:04 -0700473 auto inserted = foundOffsets.insert(blockData[ii]);
474 if (!inserted.second)
James Feist24bae7a2019-04-03 09:50:56 -0700475 {
476 return false;
477 }
478 }
479
480 // validate checksum
481 size_t sum = 0;
482 for (int jj = 0; jj < 7; jj++)
483 {
484 sum += blockData[jj];
485 }
486 sum = (256 - sum) & 0xFF;
487
488 if (sum != blockData[7])
489 {
490 return false;
491 }
492 return true;
493}
494
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700495// TODO: This code is very similar to the non-eeprom version and can be merged
496// with some tweaks.
Andrei Kartashev1c5b7062020-08-19 14:47:30 +0300497static std::vector<uint8_t> processEeprom(int bus, int address)
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700498{
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700499 auto path = getEepromPath(bus, address);
500
501 int file = open(path.c_str(), O_RDONLY);
502 if (file < 0)
503 {
504 std::cerr << "Unable to open eeprom file: " << path << "\n";
Patrick Venturebaba7672019-10-26 09:26:41 -0700505 return {};
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700506 }
507
Patrick Venturebaba7672019-10-26 09:26:41 -0700508 std::string errorMessage = "eeprom at " + std::to_string(bus) +
509 " address " + std::to_string(address);
Andrei Kartashev2f0de172020-08-13 14:29:40 +0300510 std::vector<uint8_t> device = readFRUContents(
Xiang Liu2801a702020-01-20 14:29:34 -0800511 0, file, static_cast<uint16_t>(address), readFromEeprom, errorMessage);
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700512
513 close(file);
514 return device;
515}
516
517std::set<int> findI2CEeproms(int i2cBus, std::shared_ptr<DeviceMap> devices)
518{
519 std::set<int> foundList;
520
521 std::string path = "/sys/bus/i2c/devices/i2c-" + std::to_string(i2cBus);
522
523 // For each file listed under the i2c device
524 // NOTE: This should be faster than just checking for each possible address
525 // path.
526 for (const auto& p : fs::directory_iterator(path))
527 {
528 const std::string node = p.path().string();
529 std::smatch m;
530 bool found =
531 std::regex_match(node, m, std::regex(".+\\d+-([0-9abcdef]+$)"));
532
533 if (!found)
534 {
535 continue;
536 }
537 if (m.size() != 2)
538 {
539 std::cerr << "regex didn't capture\n";
540 continue;
541 }
542
543 std::ssub_match subMatch = m[1];
544 std::string addressString = subMatch.str();
545
546 std::size_t ignored;
547 const int hexBase = 16;
548 int address = std::stoi(addressString, &ignored, hexBase);
549
550 const std::string eeprom = node + "/eeprom";
551
552 try
553 {
554 if (!fs::exists(eeprom))
555 {
556 continue;
557 }
558 }
559 catch (...)
560 {
561 continue;
562 }
563
564 // There is an eeprom file at this address, it may have invalid
565 // contents, but we found it.
566 foundList.insert(address);
567
Andrei Kartashev1c5b7062020-08-19 14:47:30 +0300568 std::vector<uint8_t> device = processEeprom(i2cBus, address);
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700569 if (!device.empty())
570 {
571 devices->emplace(address, device);
572 }
573 }
574
575 return foundList;
576}
577
Andrei Kartashev2f0de172020-08-13 14:29:40 +0300578int getBusFRUs(int file, int first, int last, int bus,
Patrick Venture4f47fe62019-08-08 16:30:38 -0700579 std::shared_ptr<DeviceMap> devices)
James Feist3cb5fec2018-01-23 14:41:51 -0800580{
James Feist3cb5fec2018-01-23 14:41:51 -0800581
James Feist26c27ad2018-07-25 15:09:40 -0700582 std::future<int> future = std::async(std::launch::async, [&]() {
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700583 // NOTE: When reading the devices raw on the bus, it can interfere with
584 // the driver's ability to operate, therefore read eeproms first before
585 // scanning for devices without drivers. Several experiments were run
586 // and it was determined that if there were any devices on the bus
587 // before the eeprom was hit and read, the eeprom driver wouldn't open
588 // while the bus device was open. An experiment was not performed to see
589 // if this issue was resolved if the i2c bus device was closed, but
590 // hexdumps of the eeprom later were successful.
591
592 // Scan for i2c eeproms loaded on this bus.
593 std::set<int> skipList = findI2CEeproms(bus, devices);
James Feist7972bb92019-11-13 15:59:24 -0800594 std::set<size_t>& failedItems = failedAddresses[bus];
595
596 std::set<size_t>* rootFailures = nullptr;
597 int rootBus = getRootBus(bus);
598
599 if (rootBus >= 0)
600 {
601 rootFailures = &(failedAddresses[rootBus]);
602 }
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700603
Rajashekar Gade Reddyfa8d3222020-05-13 08:27:03 +0530604 constexpr int startSkipSlaveAddr = 0;
605 constexpr int endSkipSlaveAddr = 12;
606
James Feist26c27ad2018-07-25 15:09:40 -0700607 for (int ii = first; ii <= last; ii++)
James Feist3cb5fec2018-01-23 14:41:51 -0800608 {
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700609 if (skipList.find(ii) != skipList.end())
610 {
611 continue;
612 }
Rajashekar Gade Reddyfa8d3222020-05-13 08:27:03 +0530613 // skipping since no device is present in this range
614 if (ii >= startSkipSlaveAddr && ii <= endSkipSlaveAddr)
615 {
616 continue;
617 }
James Feist26c27ad2018-07-25 15:09:40 -0700618 // Set slave address
Rajashekar Gade Reddyfa8d3222020-05-13 08:27:03 +0530619 if (ioctl(file, I2C_SLAVE, ii) < 0)
James Feist3cb5fec2018-01-23 14:41:51 -0800620 {
Andrei Kartashevb45324a2020-10-14 17:01:47 +0300621 std::cerr << "device at bus " << bus << " address " << ii
Patrick Venture5d8b61d2019-08-06 12:36:10 -0700622 << " busy\n";
James Feist26c27ad2018-07-25 15:09:40 -0700623 continue;
624 }
625 // probe
626 else if (i2c_smbus_read_byte(file) < 0)
627 {
628 continue;
629 }
James Feist3cb5fec2018-01-23 14:41:51 -0800630
James Feist26c27ad2018-07-25 15:09:40 -0700631 if (DEBUG)
632 {
Patrick Venture98e0cf32019-08-02 11:11:03 -0700633 std::cout << "something at bus " << bus << " addr " << ii
James Feist26c27ad2018-07-25 15:09:40 -0700634 << "\n";
635 }
Vijay Khemka2d681f62018-11-06 15:51:00 -0800636
James Feist8a983922019-10-24 17:11:56 -0700637 makeProbeInterface(bus, ii);
638
James Feist7972bb92019-11-13 15:59:24 -0800639 if (failedItems.find(ii) != failedItems.end())
640 {
641 // if we failed to read it once, unlikely we can read it later
642 continue;
643 }
644
645 if (rootFailures != nullptr)
646 {
647 if (rootFailures->find(ii) != rootFailures->end())
648 {
649 continue;
650 }
651 }
652
Vijay Khemka2d681f62018-11-06 15:51:00 -0800653 /* Check for Device type if it is 8 bit or 16 bit */
654 int flag = isDevice16Bit(file);
655 if (flag < 0)
656 {
657 std::cerr << "failed to read bus " << bus << " address " << ii
658 << "\n";
James Feistcb5661d2020-06-12 15:05:01 -0700659 if (powerIsOn)
660 {
661 failedItems.insert(ii);
662 }
Vijay Khemka2d681f62018-11-06 15:51:00 -0800663 continue;
664 }
665
Patrick Venturebaba7672019-10-26 09:26:41 -0700666 std::string errorMessage =
667 "bus " + std::to_string(bus) + " address " + std::to_string(ii);
Andrei Kartashev1c5b7062020-08-19 14:47:30 +0300668 std::vector<uint8_t> device =
Andrei Kartashev2f0de172020-08-13 14:29:40 +0300669 readFRUContents(flag, file, static_cast<uint16_t>(ii),
Xiang Liu2801a702020-01-20 14:29:34 -0800670 readBlockData, errorMessage);
Patrick Venturebaba7672019-10-26 09:26:41 -0700671 if (device.empty())
James Feist26c27ad2018-07-25 15:09:40 -0700672 {
James Feist26c27ad2018-07-25 15:09:40 -0700673 continue;
674 }
James Feist26c27ad2018-07-25 15:09:40 -0700675
Patrick Venture786f1792019-08-05 16:33:44 -0700676 devices->emplace(ii, device);
James Feist3cb5fec2018-01-23 14:41:51 -0800677 }
James Feist26c27ad2018-07-25 15:09:40 -0700678 return 1;
679 });
680 std::future_status status =
681 future.wait_for(std::chrono::seconds(busTimeoutSeconds));
682 if (status == std::future_status::timeout)
683 {
684 std::cerr << "Error reading bus " << bus << "\n";
James Feistcb5661d2020-06-12 15:05:01 -0700685 if (powerIsOn)
686 {
687 busBlacklist.insert(bus);
688 }
James Feist444830e2019-04-05 08:38:16 -0700689 close(file);
James Feist26c27ad2018-07-25 15:09:40 -0700690 return -1;
James Feist3cb5fec2018-01-23 14:41:51 -0800691 }
692
James Feist444830e2019-04-05 08:38:16 -0700693 close(file);
James Feist26c27ad2018-07-25 15:09:40 -0700694 return future.get();
James Feist3cb5fec2018-01-23 14:41:51 -0800695}
696
Patrick Venture11f1ff42019-08-01 10:42:12 -0700697void loadBlacklist(const char* path)
698{
699 std::ifstream blacklistStream(path);
700 if (!blacklistStream.good())
701 {
702 // File is optional.
703 std::cerr << "Cannot open blacklist file.\n\n";
704 return;
705 }
706
707 nlohmann::json data =
708 nlohmann::json::parse(blacklistStream, nullptr, false);
709 if (data.is_discarded())
710 {
711 std::cerr << "Illegal blacklist file detected, cannot validate JSON, "
712 "exiting\n";
713 std::exit(EXIT_FAILURE);
Patrick Venture11f1ff42019-08-01 10:42:12 -0700714 }
715
716 // It's expected to have at least one field, "buses" that is an array of the
717 // buses by integer. Allow for future options to exclude further aspects,
718 // such as specific addresses or ranges.
719 if (data.type() != nlohmann::json::value_t::object)
720 {
721 std::cerr << "Illegal blacklist, expected to read dictionary\n";
722 std::exit(EXIT_FAILURE);
Patrick Venture11f1ff42019-08-01 10:42:12 -0700723 }
724
725 // If buses field is missing, that's fine.
726 if (data.count("buses") == 1)
727 {
728 // Parse the buses array after a little validation.
729 auto buses = data.at("buses");
730 if (buses.type() != nlohmann::json::value_t::array)
731 {
732 // Buses field present but invalid, therefore this is an error.
733 std::cerr << "Invalid contents for blacklist buses field\n";
734 std::exit(EXIT_FAILURE);
Patrick Venture11f1ff42019-08-01 10:42:12 -0700735 }
736
737 // Catch exception here for type mis-match.
738 try
739 {
740 for (const auto& bus : buses)
741 {
742 busBlacklist.insert(bus.get<size_t>());
743 }
744 }
745 catch (const nlohmann::detail::type_error& e)
746 {
747 // Type mis-match is a critical error.
748 std::cerr << "Invalid bus type: " << e.what() << "\n";
749 std::exit(EXIT_FAILURE);
Patrick Venture11f1ff42019-08-01 10:42:12 -0700750 }
751 }
752
753 return;
754}
755
James Feista465ccc2019-02-08 12:51:01 -0800756static void FindI2CDevices(const std::vector<fs::path>& i2cBuses,
James Feist98132792019-07-09 13:29:09 -0700757 BusMap& busmap)
James Feist3cb5fec2018-01-23 14:41:51 -0800758{
James Feista465ccc2019-02-08 12:51:01 -0800759 for (auto& i2cBus : i2cBuses)
James Feist3cb5fec2018-01-23 14:41:51 -0800760 {
James Feist5d7f4692020-06-17 18:22:33 -0700761 int bus = busStrToInt(i2cBus);
James Feist0c3980a2019-12-19 11:09:27 -0800762
James Feist5d7f4692020-06-17 18:22:33 -0700763 if (bus < 0)
764 {
765 std::cerr << "Cannot translate " << i2cBus << " to int\n";
766 continue;
767 }
James Feist444830e2019-04-05 08:38:16 -0700768 if (busBlacklist.find(bus) != busBlacklist.end())
769 {
770 continue; // skip previously failed busses
771 }
James Feist3cb5fec2018-01-23 14:41:51 -0800772
James Feist0c3980a2019-12-19 11:09:27 -0800773 int rootBus = getRootBus(bus);
774 if (busBlacklist.find(rootBus) != busBlacklist.end())
775 {
776 continue;
777 }
778
James Feist3cb5fec2018-01-23 14:41:51 -0800779 auto file = open(i2cBus.c_str(), O_RDWR);
780 if (file < 0)
781 {
782 std::cerr << "unable to open i2c device " << i2cBus.string()
783 << "\n";
784 continue;
785 }
786 unsigned long funcs = 0;
787
788 if (ioctl(file, I2C_FUNCS, &funcs) < 0)
789 {
790 std::cerr
Patrick Venture98e0cf32019-08-02 11:11:03 -0700791 << "Error: Could not get the adapter functionality matrix bus "
James Feist3cb5fec2018-01-23 14:41:51 -0800792 << bus << "\n";
793 continue;
794 }
795 if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE) ||
796 !(I2C_FUNC_SMBUS_READ_I2C_BLOCK))
797 {
798 std::cerr << "Error: Can't use SMBus Receive Byte command bus "
799 << bus << "\n";
800 continue;
801 }
James Feist98132792019-07-09 13:29:09 -0700802 auto& device = busmap[bus];
James Feist3cb5fec2018-01-23 14:41:51 -0800803 device = std::make_shared<DeviceMap>();
804
Nikhil Potaded8884f12019-03-27 13:27:13 -0700805 // i2cdetect by default uses the range 0x03 to 0x77, as
806 // this is what we have tested with, use this range. Could be
807 // changed in future.
808 if (DEBUG)
James Feistc95cb142018-02-26 10:41:42 -0800809 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700810 std::cerr << "Scanning bus " << bus << "\n";
James Feistc95cb142018-02-26 10:41:42 -0800811 }
Nikhil Potaded8884f12019-03-27 13:27:13 -0700812
813 // fd is closed in this function in case the bus locks up
Andrei Kartashev2f0de172020-08-13 14:29:40 +0300814 getBusFRUs(file, 0x03, 0x77, bus, device);
Nikhil Potaded8884f12019-03-27 13:27:13 -0700815
816 if (DEBUG)
James Feistc95cb142018-02-26 10:41:42 -0800817 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700818 std::cerr << "Done scanning bus " << bus << "\n";
James Feistc95cb142018-02-26 10:41:42 -0800819 }
James Feist3cb5fec2018-01-23 14:41:51 -0800820 }
James Feist3cb5fec2018-01-23 14:41:51 -0800821}
822
James Feist6ebf9de2018-05-15 15:01:17 -0700823// this class allows an async response after all i2c devices are discovered
James Feist8c505da2020-05-28 10:06:33 -0700824struct FindDevicesWithCallback :
825 std::enable_shared_from_this<FindDevicesWithCallback>
James Feist6ebf9de2018-05-15 15:01:17 -0700826{
James Feista465ccc2019-02-08 12:51:01 -0800827 FindDevicesWithCallback(const std::vector<fs::path>& i2cBuses,
James Feist5cb06082019-10-24 17:11:13 -0700828 BusMap& busmap,
James Feista465ccc2019-02-08 12:51:01 -0800829 std::function<void(void)>&& callback) :
James Feist6ebf9de2018-05-15 15:01:17 -0700830 _i2cBuses(i2cBuses),
James Feist5cb06082019-10-24 17:11:13 -0700831 _busMap(busmap), _callback(std::move(callback))
James Feist8c505da2020-05-28 10:06:33 -0700832 {}
James Feist6ebf9de2018-05-15 15:01:17 -0700833 ~FindDevicesWithCallback()
834 {
835 _callback();
836 }
837 void run()
838 {
James Feist98132792019-07-09 13:29:09 -0700839 FindI2CDevices(_i2cBuses, _busMap);
James Feist6ebf9de2018-05-15 15:01:17 -0700840 }
841
James Feista465ccc2019-02-08 12:51:01 -0800842 const std::vector<fs::path>& _i2cBuses;
James Feista465ccc2019-02-08 12:51:01 -0800843 BusMap& _busMap;
James Feist6ebf9de2018-05-15 15:01:17 -0700844 std::function<void(void)> _callback;
845};
846
James Feist3cb5fec2018-01-23 14:41:51 -0800847static const std::tm intelEpoch(void)
848{
James Feist98132792019-07-09 13:29:09 -0700849 std::tm val = {};
James Feist3cb5fec2018-01-23 14:41:51 -0800850 val.tm_year = 1996 - 1900;
851 return val;
852}
853
Jeremy Kerr4e6a62f2020-06-09 11:26:40 +0800854static char sixBitToChar(uint8_t val)
855{
856 return static_cast<char>((val & 0x3f) + ' ');
857}
858
859/* 0xd - 0xf are reserved values, but not fatal; use a placeholder char. */
860static const char bcdHighChars[] = {
861 ' ', '-', '.', 'X', 'X', 'X',
862};
863
864static char bcdPlusToChar(uint8_t val)
865{
866 val &= 0xf;
867 return (val < 10) ? static_cast<char>(val + '0') : bcdHighChars[val - 10];
868}
869
870enum class DecodeState
871{
872 ok,
873 end,
874 err,
875};
876
877enum FRUDataEncoding
878{
879 binary = 0x0,
880 bcdPlus = 0x1,
881 sixBitASCII = 0x2,
882 languageDependent = 0x3,
883};
884
885/* Decode FRU data into a std::string, given an input iterator and end. If the
886 * state returned is fruDataOk, then the resulting string is the decoded FRU
887 * data. The input iterator is advanced past the data consumed.
888 *
889 * On fruDataErr, we have lost synchronisation with the length bytes, so the
890 * iterator is no longer usable.
891 */
892static std::pair<DecodeState, std::string>
Andrei Kartashev2f0de172020-08-13 14:29:40 +0300893 decodeFRUData(std::vector<uint8_t>::const_iterator& iter,
Andrei Kartashev1c5b7062020-08-19 14:47:30 +0300894 const std::vector<uint8_t>::const_iterator& end)
Jeremy Kerr4e6a62f2020-06-09 11:26:40 +0800895{
896 std::string value;
897 unsigned int i;
898
899 /* we need at least one byte to decode the type/len header */
900 if (iter == end)
901 {
902 std::cerr << "Truncated FRU data\n";
903 return make_pair(DecodeState::err, value);
904 }
905
906 uint8_t c = *(iter++);
907
908 /* 0xc1 is the end marker */
909 if (c == 0xc1)
910 {
911 return make_pair(DecodeState::end, value);
912 }
913
914 /* decode type/len byte */
915 uint8_t type = static_cast<uint8_t>(c >> 6);
916 uint8_t len = static_cast<uint8_t>(c & 0x3f);
917
918 /* we should have at least len bytes of data available overall */
919 if (iter + len > end)
920 {
Andrei Kartashevd7b66592020-08-13 15:33:51 +0300921 std::cerr << "FRU data field extends past end of FRU area data\n";
Jeremy Kerr4e6a62f2020-06-09 11:26:40 +0800922 return make_pair(DecodeState::err, value);
923 }
924
925 switch (type)
926 {
927 case FRUDataEncoding::binary:
Andrei Kartashevc994c022020-08-10 18:51:49 +0300928 {
929 std::stringstream ss;
930 ss << std::hex << std::setfill('0');
931 for (i = 0; i < len; i++, iter++)
932 {
933 uint8_t val = static_cast<uint8_t>(*iter);
934 ss << std::setw(2) << static_cast<int>(val);
935 }
936 value = ss.str();
937 break;
938 }
Jeremy Kerr4e6a62f2020-06-09 11:26:40 +0800939 case FRUDataEncoding::languageDependent:
940 /* For language-code dependent encodings, assume 8-bit ASCII */
941 value = std::string(iter, iter + len);
942 iter += len;
943 break;
944
945 case FRUDataEncoding::bcdPlus:
946 value = std::string();
947 for (i = 0; i < len; i++, iter++)
948 {
Andrei Kartashev1c5b7062020-08-19 14:47:30 +0300949 uint8_t val = *iter;
Jeremy Kerr4e6a62f2020-06-09 11:26:40 +0800950 value.push_back(bcdPlusToChar(val >> 4));
951 value.push_back(bcdPlusToChar(val & 0xf));
952 }
953 break;
954
955 case FRUDataEncoding::sixBitASCII:
956 {
Andrei Kartasheve707de62020-08-10 18:33:29 +0300957 unsigned int accum = 0;
Jeremy Kerr4e6a62f2020-06-09 11:26:40 +0800958 unsigned int accumBitLen = 0;
959 value = std::string();
960 for (i = 0; i < len; i++, iter++)
961 {
Andrei Kartashev1c5b7062020-08-19 14:47:30 +0300962 accum |= *iter << accumBitLen;
Jeremy Kerr4e6a62f2020-06-09 11:26:40 +0800963 accumBitLen += 8;
964 while (accumBitLen >= 6)
965 {
966 value.push_back(sixBitToChar(accum & 0x3f));
967 accum >>= 6;
968 accumBitLen -= 6;
969 }
970 }
971 }
972 break;
973 }
974
975 return make_pair(DecodeState::ok, value);
976}
977
Andrei Kartashev2a7e3952020-09-02 20:32:26 +0300978static void checkLang(uint8_t lang)
979{
980 // If Lang is not English then the encoding is defined as 2-byte UNICODE,
981 // but we don't support that.
982 if (lang && lang != 25)
983 {
984 std::cerr << "Warning: language other then English is not "
985 "supported \n";
986 }
987}
988
Andrei Kartashevb45324a2020-10-14 17:01:47 +0300989resCodes formatFRU(const std::vector<uint8_t>& fruBytes,
990 boost::container::flat_map<std::string, std::string>& result)
James Feist3cb5fec2018-01-23 14:41:51 -0800991{
Andrei Kartashevb45324a2020-10-14 17:01:47 +0300992 resCodes ret = resCodes::resOK;
Andrei Kartashev2f0de172020-08-13 14:29:40 +0300993 if (fruBytes.size() <= fruBlockSize)
James Feist3cb5fec2018-01-23 14:41:51 -0800994 {
Andrei Kartashevd7b66592020-08-13 15:33:51 +0300995 std::cerr << "Error: trying to parse empty FRU \n";
Andrei Kartashevb45324a2020-10-14 17:01:47 +0300996 return resCodes::resErr;
James Feist3cb5fec2018-01-23 14:41:51 -0800997 }
James Feist9eb0b582018-04-27 12:15:46 -0700998 result["Common_Format_Version"] =
Andrei Kartashevd7b66592020-08-13 15:33:51 +0300999 std::to_string(static_cast<int>(*fruBytes.begin()));
James Feist3cb5fec2018-01-23 14:41:51 -08001000
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001001 const std::vector<std::string>* fruAreaFieldNames;
James Feist3cb5fec2018-01-23 14:41:51 -08001002
Andrei Kartashevd7b66592020-08-13 15:33:51 +03001003 // Don't parse Internal and Multirecord areas
1004 for (fruAreas area = fruAreas::fruAreaChassis;
1005 area <= fruAreas::fruAreaProduct; ++area)
James Feist3cb5fec2018-01-23 14:41:51 -08001006 {
Andrei Kartashevd7b66592020-08-13 15:33:51 +03001007
1008 size_t offset = *(fruBytes.begin() + getHeaderAreaFieldOffset(area));
1009 if (offset == 0)
James Feist3cb5fec2018-01-23 14:41:51 -08001010 {
Andrei Kartashevd7b66592020-08-13 15:33:51 +03001011 continue;
1012 }
1013 offset *= fruBlockSize;
1014 std::vector<uint8_t>::const_iterator fruBytesIter =
1015 fruBytes.begin() + offset;
1016 if (fruBytesIter + fruBlockSize >= fruBytes.end())
1017 {
1018 std::cerr << "Not enough data to parse \n";
Andrei Kartashevb45324a2020-10-14 17:01:47 +03001019 return resCodes::resErr;
James Feist3cb5fec2018-01-23 14:41:51 -08001020 }
Andrei Kartashevd7b66592020-08-13 15:33:51 +03001021 // check for format version 1
1022 if (*fruBytesIter != 0x01)
James Feist3cb5fec2018-01-23 14:41:51 -08001023 {
Andrei Kartashevd7b66592020-08-13 15:33:51 +03001024 std::cerr << "Unexpected version " << *fruBytesIter << "\n";
Andrei Kartashevb45324a2020-10-14 17:01:47 +03001025 return resCodes::resErr;
Andrei Kartashevd7b66592020-08-13 15:33:51 +03001026 }
1027 ++fruBytesIter;
1028 uint8_t fruAreaSize = *fruBytesIter * fruBlockSize;
1029 std::vector<uint8_t>::const_iterator fruBytesIterEndArea =
1030 fruBytes.begin() + offset + fruAreaSize - 1;
1031 ++fruBytesIter;
James Feist3cb5fec2018-01-23 14:41:51 -08001032
Andrei Kartashev272bafd2020-10-09 12:05:32 +03001033 uint8_t fruComputedChecksum =
1034 calculateChecksum(fruBytes.begin() + offset, fruBytesIterEndArea);
1035 if (fruComputedChecksum != *fruBytesIterEndArea)
Andrei Kartashev2a7e3952020-09-02 20:32:26 +03001036 {
Andrei Kartashev272bafd2020-10-09 12:05:32 +03001037 std::stringstream ss;
1038 ss << std::hex << std::setfill('0');
1039 ss << "Checksum error in FRU area " << getFruAreaName(area) << "\n";
1040 ss << "\tComputed checksum: 0x" << std::setw(2)
1041 << static_cast<int>(fruComputedChecksum) << "\n";
1042 ss << "\tThe read checksum: 0x" << std::setw(2)
1043 << static_cast<int>(*fruBytesIterEndArea) << "\n";
1044 std::cerr << ss.str();
Andrei Kartashevb45324a2020-10-14 17:01:47 +03001045 ret = resCodes::resWarn;
Andrei Kartashev2a7e3952020-09-02 20:32:26 +03001046 }
1047
Andrei Kartashevd7b66592020-08-13 15:33:51 +03001048 switch (area)
1049 {
1050 case fruAreas::fruAreaChassis:
James Feist3cb5fec2018-01-23 14:41:51 -08001051 {
1052 result["CHASSIS_TYPE"] =
1053 std::to_string(static_cast<int>(*fruBytesIter));
1054 fruBytesIter += 1;
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001055 fruAreaFieldNames = &CHASSIS_FRU_AREAS;
Andrei Kartashevd7b66592020-08-13 15:33:51 +03001056 break;
James Feist3cb5fec2018-01-23 14:41:51 -08001057 }
Andrei Kartashevd7b66592020-08-13 15:33:51 +03001058 case fruAreas::fruAreaBoard:
James Feist3cb5fec2018-01-23 14:41:51 -08001059 {
Andrei Kartashev2a7e3952020-09-02 20:32:26 +03001060 uint8_t lang = *fruBytesIter;
James Feist3cb5fec2018-01-23 14:41:51 -08001061 result["BOARD_LANGUAGE_CODE"] =
Andrei Kartashev2a7e3952020-09-02 20:32:26 +03001062 std::to_string(static_cast<int>(lang));
1063 checkLang(lang);
James Feist3cb5fec2018-01-23 14:41:51 -08001064 fruBytesIter += 1;
James Feist3cb5fec2018-01-23 14:41:51 -08001065
1066 unsigned int minutes = *fruBytesIter |
1067 *(fruBytesIter + 1) << 8 |
1068 *(fruBytesIter + 2) << 16;
1069 std::tm fruTime = intelEpoch();
Patrick Venturee0e6f5f2019-08-12 19:00:11 -07001070 std::time_t timeValue = std::mktime(&fruTime);
James Feist3cb5fec2018-01-23 14:41:51 -08001071 timeValue += minutes * 60;
Patrick Venturee0e6f5f2019-08-12 19:00:11 -07001072 fruTime = *std::gmtime(&timeValue);
James Feist3cb5fec2018-01-23 14:41:51 -08001073
Patrick Venturee0e6f5f2019-08-12 19:00:11 -07001074 // Tue Nov 20 23:08:00 2018
1075 char timeString[32] = {0};
1076 auto bytes = std::strftime(timeString, sizeof(timeString),
Patrick Venturefff050a2019-08-13 11:44:30 -07001077 "%Y-%m-%d - %H:%M:%S", &fruTime);
Patrick Venturee0e6f5f2019-08-12 19:00:11 -07001078 if (bytes == 0)
1079 {
1080 std::cerr << "invalid time string encountered\n";
Andrei Kartashevb45324a2020-10-14 17:01:47 +03001081 return resCodes::resErr;
Patrick Venturee0e6f5f2019-08-12 19:00:11 -07001082 }
1083
1084 result["BOARD_MANUFACTURE_DATE"] = std::string(timeString);
James Feist3cb5fec2018-01-23 14:41:51 -08001085 fruBytesIter += 3;
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001086 fruAreaFieldNames = &BOARD_FRU_AREAS;
Andrei Kartashevd7b66592020-08-13 15:33:51 +03001087 break;
James Feist3cb5fec2018-01-23 14:41:51 -08001088 }
Andrei Kartashevd7b66592020-08-13 15:33:51 +03001089 case fruAreas::fruAreaProduct:
James Feist3cb5fec2018-01-23 14:41:51 -08001090 {
Andrei Kartashev2a7e3952020-09-02 20:32:26 +03001091 uint8_t lang = *fruBytesIter;
James Feist3cb5fec2018-01-23 14:41:51 -08001092 result["PRODUCT_LANGUAGE_CODE"] =
Andrei Kartashev2a7e3952020-09-02 20:32:26 +03001093 std::to_string(static_cast<int>(lang));
1094 checkLang(lang);
James Feist3cb5fec2018-01-23 14:41:51 -08001095 fruBytesIter += 1;
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001096 fruAreaFieldNames = &PRODUCT_FRU_AREAS;
Andrei Kartashevd7b66592020-08-13 15:33:51 +03001097 break;
1098 }
1099 default:
1100 {
1101 std::cerr << "Internal error: unexpected FRU area index: "
1102 << static_cast<int>(area) << " \n";
Andrei Kartashevb45324a2020-10-14 17:01:47 +03001103 return resCodes::resErr;
Andrei Kartashevd7b66592020-08-13 15:33:51 +03001104 }
1105 }
1106 size_t fieldIndex = 0;
1107 DecodeState state;
1108 do
1109 {
1110 auto res = decodeFRUData(fruBytesIter, fruBytesIterEndArea);
1111 state = res.first;
1112 std::string value = res.second;
1113 std::string name;
1114 if (fieldIndex < fruAreaFieldNames->size())
1115 {
1116 name = std::string(getFruAreaName(area)) + "_" +
1117 fruAreaFieldNames->at(fieldIndex);
James Feist3cb5fec2018-01-23 14:41:51 -08001118 }
1119 else
1120 {
Andrei Kartashevd7b66592020-08-13 15:33:51 +03001121 name =
1122 std::string(getFruAreaName(area)) + "_" +
1123 FRU_CUSTOM_FIELD_NAME +
1124 std::to_string(fieldIndex - fruAreaFieldNames->size() + 1);
James Feist3cb5fec2018-01-23 14:41:51 -08001125 }
Andrei Kartashevd7b66592020-08-13 15:33:51 +03001126
1127 if (state == DecodeState::ok)
James Feist3cb5fec2018-01-23 14:41:51 -08001128 {
Ed Tanous2147e672019-02-27 13:59:56 -08001129 // Strip non null characters from the end
1130 value.erase(std::find_if(value.rbegin(), value.rend(),
1131 [](char ch) { return ch != 0; })
1132 .base(),
1133 value.end());
1134
Jeremy Kerr4e6a62f2020-06-09 11:26:40 +08001135 result[name] = std::move(value);
Andrei Kartashev6cfb7752020-08-10 19:50:33 +03001136 ++fieldIndex;
James Feist3cb5fec2018-01-23 14:41:51 -08001137 }
Andrei Kartashevd7b66592020-08-13 15:33:51 +03001138 else if (state == DecodeState::err)
1139 {
1140 std::cerr << "Error while parsing " << name << "\n";
Andrei Kartashevb45324a2020-10-14 17:01:47 +03001141 ret = resCodes::resWarn;
Andrei Kartashevd7b66592020-08-13 15:33:51 +03001142 // Cancel decoding if failed to parse any of mandatory
1143 // fields
1144 if (fieldIndex < fruAreaFieldNames->size())
1145 {
1146 std::cerr << "Failed to parse mandatory field \n";
Andrei Kartashevb45324a2020-10-14 17:01:47 +03001147 return resCodes::resErr;
Andrei Kartashevd7b66592020-08-13 15:33:51 +03001148 }
1149 }
Andrei Kartashev2a7e3952020-09-02 20:32:26 +03001150 else
1151 {
1152 if (fieldIndex < fruAreaFieldNames->size())
1153 {
1154 std::cerr << "Mandatory fields absent in FRU area "
1155 << getFruAreaName(area) << " after " << name
1156 << "\n";
Andrei Kartashevb45324a2020-10-14 17:01:47 +03001157 ret = resCodes::resWarn;
Andrei Kartashev2a7e3952020-09-02 20:32:26 +03001158 }
1159 }
Andrei Kartashevd7b66592020-08-13 15:33:51 +03001160 } while (state == DecodeState::ok);
Andrei Kartashev2a7e3952020-09-02 20:32:26 +03001161 for (; fruBytesIter < fruBytesIterEndArea; fruBytesIter++)
1162 {
1163 uint8_t c = *fruBytesIter;
1164 if (c)
1165 {
1166 std::cerr << "Non-zero byte after EndOfFields in FRU area "
1167 << getFruAreaName(area) << "\n";
Andrei Kartashevb45324a2020-10-14 17:01:47 +03001168 ret = resCodes::resWarn;
Andrei Kartashev2a7e3952020-09-02 20:32:26 +03001169 break;
1170 }
1171 }
James Feist3cb5fec2018-01-23 14:41:51 -08001172 }
1173
Andrei Kartashevb45324a2020-10-14 17:01:47 +03001174 return ret;
James Feist3cb5fec2018-01-23 14:41:51 -08001175}
1176
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001177std::vector<uint8_t>& getFRUInfo(const uint8_t& bus, const uint8_t& address)
Nikhil Potaded8884f12019-03-27 13:27:13 -07001178{
1179 auto deviceMap = busMap.find(bus);
1180 if (deviceMap == busMap.end())
1181 {
1182 throw std::invalid_argument("Invalid Bus.");
1183 }
1184 auto device = deviceMap->second->find(address);
1185 if (device == deviceMap->second->end())
1186 {
1187 throw std::invalid_argument("Invalid Address.");
1188 }
Andrei Kartashev1c5b7062020-08-19 14:47:30 +03001189 std::vector<uint8_t>& ret = device->second;
Nikhil Potaded8884f12019-03-27 13:27:13 -07001190
1191 return ret;
1192}
1193
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001194void AddFRUObjectToDbus(
Andrei Kartashev1c5b7062020-08-19 14:47:30 +03001195 std::vector<uint8_t>& device,
James Feista465ccc2019-02-08 12:51:01 -08001196 boost::container::flat_map<
1197 std::pair<size_t, size_t>,
1198 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
James Feist13b86d62018-05-29 11:24:35 -07001199 uint32_t bus, uint32_t address)
James Feist3cb5fec2018-01-23 14:41:51 -08001200{
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001201 boost::container::flat_map<std::string, std::string> formattedFRU;
Andrei Kartashevb45324a2020-10-14 17:01:47 +03001202 resCodes res = formatFRU(device, formattedFRU);
1203 if (res == resCodes::resErr)
James Feist3cb5fec2018-01-23 14:41:51 -08001204 {
Andrei Kartashevb45324a2020-10-14 17:01:47 +03001205 std::cerr << "failed to parse FRU for device at bus " << bus
Patrick Ventureb755c832019-08-07 11:09:14 -07001206 << " address " << address << "\n";
James Feist3cb5fec2018-01-23 14:41:51 -08001207 return;
1208 }
Andrei Kartashevb45324a2020-10-14 17:01:47 +03001209 else if (res == resCodes::resWarn)
1210 {
1211 std::cerr << "there were warnings while parsing FRU for device at bus "
1212 << bus << " address " << address << "\n";
1213 }
Patrick Venture96cdaef2019-07-30 13:30:52 -07001214
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001215 auto productNameFind = formattedFRU.find("BOARD_PRODUCT_NAME");
James Feist3cb5fec2018-01-23 14:41:51 -08001216 std::string productName;
Patrick Venture96cdaef2019-07-30 13:30:52 -07001217 // Not found under Board section or an empty string.
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001218 if (productNameFind == formattedFRU.end() ||
Patrick Venture96cdaef2019-07-30 13:30:52 -07001219 productNameFind->second.empty())
James Feist3cb5fec2018-01-23 14:41:51 -08001220 {
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001221 productNameFind = formattedFRU.find("PRODUCT_PRODUCT_NAME");
James Feist3cb5fec2018-01-23 14:41:51 -08001222 }
Patrick Venture96cdaef2019-07-30 13:30:52 -07001223 // Found under Product section and not an empty string.
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001224 if (productNameFind != formattedFRU.end() &&
Patrick Venture96cdaef2019-07-30 13:30:52 -07001225 !productNameFind->second.empty())
James Feist3cb5fec2018-01-23 14:41:51 -08001226 {
1227 productName = productNameFind->second;
James Feist3f8a2782018-02-12 09:24:42 -08001228 std::regex illegalObject("[^A-Za-z0-9_]");
1229 productName = std::regex_replace(productName, illegalObject, "_");
James Feist3cb5fec2018-01-23 14:41:51 -08001230 }
1231 else
1232 {
1233 productName = "UNKNOWN" + std::to_string(UNKNOWN_BUS_OBJECT_COUNT);
1234 UNKNOWN_BUS_OBJECT_COUNT++;
1235 }
1236
James Feist918e18c2018-02-13 15:51:07 -08001237 productName = "/xyz/openbmc_project/FruDevice/" + productName;
James Feist918e18c2018-02-13 15:51:07 -08001238 // avoid duplicates by checking to see if on a mux
James Feist79e9c0b2018-03-15 15:21:17 -07001239 if (bus > 0)
James Feist918e18c2018-02-13 15:51:07 -08001240 {
Patrick Venture015fb0a2019-08-16 09:33:31 -07001241 int highest = -1;
1242 bool found = false;
1243
James Feista465ccc2019-02-08 12:51:01 -08001244 for (auto const& busIface : dbusInterfaceMap)
James Feist918e18c2018-02-13 15:51:07 -08001245 {
Patrick Venture015fb0a2019-08-16 09:33:31 -07001246 std::string path = busIface.second->get_object_path();
1247 if (std::regex_match(path, std::regex(productName + "(_\\d+|)$")))
James Feist918e18c2018-02-13 15:51:07 -08001248 {
Nikhil Potaded8884f12019-03-27 13:27:13 -07001249 if (isMuxBus(bus) && address == busIface.first.second &&
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001250 (getFRUInfo(static_cast<uint8_t>(busIface.first.first),
James Feist98132792019-07-09 13:29:09 -07001251 static_cast<uint8_t>(busIface.first.second)) ==
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001252 getFRUInfo(static_cast<uint8_t>(bus),
James Feist98132792019-07-09 13:29:09 -07001253 static_cast<uint8_t>(address))))
James Feist79e9c0b2018-03-15 15:21:17 -07001254 {
Nikhil Potaded8884f12019-03-27 13:27:13 -07001255 // This device is already added to the lower numbered bus,
1256 // do not replicate it.
1257 return;
James Feist79e9c0b2018-03-15 15:21:17 -07001258 }
Patrick Venture015fb0a2019-08-16 09:33:31 -07001259
1260 // Check if the match named has extra information.
1261 found = true;
1262 std::smatch base_match;
1263
1264 bool match = std::regex_match(
1265 path, base_match, std::regex(productName + "_(\\d+)$"));
1266 if (match)
James Feist79e9c0b2018-03-15 15:21:17 -07001267 {
Patrick Venture015fb0a2019-08-16 09:33:31 -07001268 if (base_match.size() == 2)
1269 {
1270 std::ssub_match base_sub_match = base_match[1];
1271 std::string base = base_sub_match.str();
1272
1273 int value = std::stoi(base);
1274 highest = (value > highest) ? value : highest;
1275 }
James Feist79e9c0b2018-03-15 15:21:17 -07001276 }
James Feist918e18c2018-02-13 15:51:07 -08001277 }
Patrick Venture015fb0a2019-08-16 09:33:31 -07001278 } // end searching objects
1279
1280 if (found)
1281 {
1282 // We found something with the same name. If highest was still -1,
1283 // it means this new entry will be _0.
1284 productName += "_";
1285 productName += std::to_string(++highest);
James Feist918e18c2018-02-13 15:51:07 -08001286 }
1287 }
James Feist3cb5fec2018-01-23 14:41:51 -08001288
James Feist9eb0b582018-04-27 12:15:46 -07001289 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
1290 objServer.add_interface(productName, "xyz.openbmc_project.FruDevice");
1291 dbusInterfaceMap[std::pair<size_t, size_t>(bus, address)] = iface;
1292
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001293 for (auto& property : formattedFRU)
James Feist3cb5fec2018-01-23 14:41:51 -08001294 {
James Feist9eb0b582018-04-27 12:15:46 -07001295
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -07001296 std::regex_replace(property.second.begin(), property.second.begin(),
1297 property.second.end(), NON_ASCII_REGEX, "_");
Joshi-Mansid73b7442020-03-27 19:10:21 +05301298 if (property.second.empty() && property.first != "PRODUCT_ASSET_TAG")
James Feist9eb0b582018-04-27 12:15:46 -07001299 {
1300 continue;
1301 }
1302 std::string key =
1303 std::regex_replace(property.first, NON_ASCII_REGEX, "_");
Joshi-Mansid73b7442020-03-27 19:10:21 +05301304
1305 if (property.first == "PRODUCT_ASSET_TAG")
1306 {
1307 std::string propertyName = property.first;
1308 iface->register_property(
1309 key, property.second + '\0',
1310 [bus, address, propertyName,
1311 &dbusInterfaceMap](const std::string& req, std::string& resp) {
1312 if (strcmp(req.c_str(), resp.c_str()))
1313 {
1314 // call the method which will update
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001315 if (updateFRUProperty(req, bus, address, propertyName,
Joshi-Mansid73b7442020-03-27 19:10:21 +05301316 dbusInterfaceMap))
1317 {
1318 resp = req;
1319 }
1320 else
1321 {
1322 throw std::invalid_argument(
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001323 "FRU property update failed.");
Joshi-Mansid73b7442020-03-27 19:10:21 +05301324 }
1325 }
1326 return 1;
1327 });
1328 }
1329 else if (!iface->register_property(key, property.second + '\0'))
James Feist9eb0b582018-04-27 12:15:46 -07001330 {
1331 std::cerr << "illegal key: " << key << "\n";
1332 }
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -07001333 if (DEBUG)
1334 {
1335 std::cout << property.first << ": " << property.second << "\n";
1336 }
James Feist3cb5fec2018-01-23 14:41:51 -08001337 }
James Feist2a9d6db2018-04-27 15:48:28 -07001338
1339 // baseboard will be 0, 0
James Feist13b86d62018-05-29 11:24:35 -07001340 iface->register_property("BUS", bus);
1341 iface->register_property("ADDRESS", address);
James Feist2a9d6db2018-04-27 15:48:28 -07001342
James Feist9eb0b582018-04-27 12:15:46 -07001343 iface->initialize();
James Feist3cb5fec2018-01-23 14:41:51 -08001344}
1345
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001346static bool readBaseboardFRU(std::vector<uint8_t>& baseboardFRU)
James Feist3cb5fec2018-01-23 14:41:51 -08001347{
1348 // try to read baseboard fru from file
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001349 std::ifstream baseboardFRUFile(BASEBOARD_FRU_LOCATION, std::ios::binary);
1350 if (baseboardFRUFile.good())
James Feist3cb5fec2018-01-23 14:41:51 -08001351 {
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001352 baseboardFRUFile.seekg(0, std::ios_base::end);
1353 size_t fileSize = static_cast<size_t>(baseboardFRUFile.tellg());
1354 baseboardFRU.resize(fileSize);
1355 baseboardFRUFile.seekg(0, std::ios_base::beg);
1356 baseboardFRUFile.read(reinterpret_cast<char*>(baseboardFRU.data()),
Andrei Kartashev1c5b7062020-08-19 14:47:30 +03001357 fileSize);
James Feist3cb5fec2018-01-23 14:41:51 -08001358 }
1359 else
1360 {
1361 return false;
1362 }
1363 return true;
1364}
1365
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001366bool writeFRU(uint8_t bus, uint8_t address, const std::vector<uint8_t>& fru)
James Feistb49ffc32018-05-02 11:10:43 -07001367{
1368 boost::container::flat_map<std::string, std::string> tmp;
1369 if (fru.size() > MAX_FRU_SIZE)
1370 {
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001371 std::cerr << "Invalid fru.size() during writeFRU\n";
James Feistb49ffc32018-05-02 11:10:43 -07001372 return false;
1373 }
1374 // verify legal fru by running it through fru parsing logic
Andrei Kartashevb45324a2020-10-14 17:01:47 +03001375 if (formatFRU(fru, tmp) != resCodes::resOK)
James Feistb49ffc32018-05-02 11:10:43 -07001376 {
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001377 std::cerr << "Invalid fru format during writeFRU\n";
James Feistb49ffc32018-05-02 11:10:43 -07001378 return false;
1379 }
1380 // baseboard fru
1381 if (bus == 0 && address == 0)
1382 {
1383 std::ofstream file(BASEBOARD_FRU_LOCATION, std::ios_base::binary);
1384 if (!file.good())
1385 {
1386 std::cerr << "Error opening file " << BASEBOARD_FRU_LOCATION
1387 << "\n";
James Feistddb78302018-09-06 11:45:42 -07001388 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001389 return false;
1390 }
James Feista465ccc2019-02-08 12:51:01 -08001391 file.write(reinterpret_cast<const char*>(fru.data()), fru.size());
James Feistb49ffc32018-05-02 11:10:43 -07001392 return file.good();
1393 }
1394 else
1395 {
Patrick Venturec50e1ff2019-08-06 10:22:28 -07001396 if (hasEepromFile(bus, address))
1397 {
1398 auto path = getEepromPath(bus, address);
1399 int eeprom = open(path.c_str(), O_RDWR | O_CLOEXEC);
1400 if (eeprom < 0)
1401 {
1402 std::cerr << "unable to open i2c device " << path << "\n";
1403 throw DBusInternalError();
1404 return false;
1405 }
1406
1407 ssize_t writtenBytes = write(eeprom, fru.data(), fru.size());
1408 if (writtenBytes < 0)
1409 {
1410 std::cerr << "unable to write to i2c device " << path << "\n";
1411 close(eeprom);
1412 throw DBusInternalError();
1413 return false;
1414 }
1415
1416 close(eeprom);
1417 return true;
1418 }
1419
James Feistb49ffc32018-05-02 11:10:43 -07001420 std::string i2cBus = "/dev/i2c-" + std::to_string(bus);
1421
1422 int file = open(i2cBus.c_str(), O_RDWR | O_CLOEXEC);
1423 if (file < 0)
1424 {
1425 std::cerr << "unable to open i2c device " << i2cBus << "\n";
James Feistddb78302018-09-06 11:45:42 -07001426 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001427 return false;
1428 }
1429 if (ioctl(file, I2C_SLAVE_FORCE, address) < 0)
1430 {
1431 std::cerr << "unable to set device address\n";
1432 close(file);
James Feistddb78302018-09-06 11:45:42 -07001433 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001434 return false;
1435 }
1436
1437 constexpr const size_t RETRY_MAX = 2;
1438 uint16_t index = 0;
1439 size_t retries = RETRY_MAX;
1440 while (index < fru.size())
1441 {
1442 if ((index && ((index % (MAX_EEPROM_PAGE_INDEX + 1)) == 0)) &&
1443 (retries == RETRY_MAX))
1444 {
1445 // The 4K EEPROM only uses the A2 and A1 device address bits
1446 // with the third bit being a memory page address bit.
1447 if (ioctl(file, I2C_SLAVE_FORCE, ++address) < 0)
1448 {
1449 std::cerr << "unable to set device address\n";
1450 close(file);
James Feistddb78302018-09-06 11:45:42 -07001451 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001452 return false;
1453 }
1454 }
1455
James Feist98132792019-07-09 13:29:09 -07001456 if (i2c_smbus_write_byte_data(file, static_cast<uint8_t>(index),
1457 fru[index]) < 0)
James Feistb49ffc32018-05-02 11:10:43 -07001458 {
1459 if (!retries--)
1460 {
1461 std::cerr << "error writing fru: " << strerror(errno)
1462 << "\n";
1463 close(file);
James Feistddb78302018-09-06 11:45:42 -07001464 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001465 return false;
1466 }
1467 }
1468 else
1469 {
1470 retries = RETRY_MAX;
1471 index++;
1472 }
1473 // most eeproms require 5-10ms between writes
1474 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1475 }
1476 close(file);
1477 return true;
1478 }
1479}
1480
Cheng C Yangd5b87fb2020-01-23 10:10:45 +08001481void rescanOneBus(
1482 BusMap& busmap, uint8_t busNum,
1483 boost::container::flat_map<
1484 std::pair<size_t, size_t>,
James Feist5d7f4692020-06-17 18:22:33 -07001485 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
1486 bool dbusCall)
Cheng C Yangd5b87fb2020-01-23 10:10:45 +08001487{
Cheng C Yangd5b87fb2020-01-23 10:10:45 +08001488 for (auto& [pair, interface] : foundDevices)
1489 {
1490 if (pair.first == static_cast<size_t>(busNum))
1491 {
1492 objServer.remove_interface(interface);
1493 foundDevices.erase(pair);
1494 }
1495 }
1496
James Feist5d7f4692020-06-17 18:22:33 -07001497 fs::path busPath = fs::path("/dev/i2c-" + std::to_string(busNum));
1498 if (!fs::exists(busPath))
1499 {
1500 if (dbusCall)
1501 {
1502 std::cerr << "Unable to access i2c bus " << static_cast<int>(busNum)
1503 << "\n";
1504 throw std::invalid_argument("Invalid Bus.");
1505 }
1506 return;
1507 }
1508
1509 std::vector<fs::path> i2cBuses;
1510 i2cBuses.emplace_back(busPath);
1511
Cheng C Yangd5b87fb2020-01-23 10:10:45 +08001512 auto scan = std::make_shared<FindDevicesWithCallback>(
1513 i2cBuses, busmap, [busNum, &busmap, &dbusInterfaceMap]() {
1514 for (auto& busIface : dbusInterfaceMap)
1515 {
1516 if (busIface.first.first == static_cast<size_t>(busNum))
1517 {
1518 objServer.remove_interface(busIface.second);
1519 }
1520 }
Wludzik, Jozefc1136b72020-06-24 11:58:32 +02001521 auto found = busmap.find(busNum);
1522 if (found == busmap.end() || found->second == nullptr)
1523 {
1524 return;
1525 }
1526 for (auto& device : *(found->second))
Cheng C Yangd5b87fb2020-01-23 10:10:45 +08001527 {
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001528 AddFRUObjectToDbus(device.second, dbusInterfaceMap,
Cheng C Yangd5b87fb2020-01-23 10:10:45 +08001529 static_cast<uint32_t>(busNum), device.first);
1530 }
1531 });
1532 scan->run();
1533}
1534
James Feist9eb0b582018-04-27 12:15:46 -07001535void rescanBusses(
James Feist5cb06082019-10-24 17:11:13 -07001536 BusMap& busmap,
James Feista465ccc2019-02-08 12:51:01 -08001537 boost::container::flat_map<
1538 std::pair<size_t, size_t>,
James Feist5cb06082019-10-24 17:11:13 -07001539 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap)
James Feist918e18c2018-02-13 15:51:07 -08001540{
James Feist6ebf9de2018-05-15 15:01:17 -07001541 static boost::asio::deadline_timer timer(io);
1542 timer.expires_from_now(boost::posix_time::seconds(1));
James Feist918e18c2018-02-13 15:51:07 -08001543
Gunnar Mills6f0ae942018-08-31 12:38:03 -05001544 // setup an async wait in case we get flooded with requests
James Feist98132792019-07-09 13:29:09 -07001545 timer.async_wait([&](const boost::system::error_code&) {
James Feist4131aea2018-03-09 09:47:30 -08001546 auto devDir = fs::path("/dev/");
James Feist4131aea2018-03-09 09:47:30 -08001547 std::vector<fs::path> i2cBuses;
James Feist918e18c2018-02-13 15:51:07 -08001548
Nikhil Potaded8884f12019-03-27 13:27:13 -07001549 boost::container::flat_map<size_t, fs::path> busPaths;
1550 if (!getI2cDevicePaths(devDir, busPaths))
James Feist918e18c2018-02-13 15:51:07 -08001551 {
James Feist4131aea2018-03-09 09:47:30 -08001552 std::cerr << "unable to find i2c devices\n";
1553 return;
James Feist918e18c2018-02-13 15:51:07 -08001554 }
Nikhil Potaded8884f12019-03-27 13:27:13 -07001555
1556 for (auto busPath : busPaths)
1557 {
1558 i2cBuses.emplace_back(busPath.second);
1559 }
James Feist4131aea2018-03-09 09:47:30 -08001560
James Feist98132792019-07-09 13:29:09 -07001561 busmap.clear();
James Feist8a983922019-10-24 17:11:56 -07001562 for (auto& [pair, interface] : foundDevices)
1563 {
1564 objServer.remove_interface(interface);
1565 }
1566 foundDevices.clear();
1567
James Feist5cb06082019-10-24 17:11:13 -07001568 auto scan =
1569 std::make_shared<FindDevicesWithCallback>(i2cBuses, busmap, [&]() {
James Feista465ccc2019-02-08 12:51:01 -08001570 for (auto& busIface : dbusInterfaceMap)
James Feist6ebf9de2018-05-15 15:01:17 -07001571 {
1572 objServer.remove_interface(busIface.second);
1573 }
James Feist4131aea2018-03-09 09:47:30 -08001574
James Feist6ebf9de2018-05-15 15:01:17 -07001575 dbusInterfaceMap.clear();
1576 UNKNOWN_BUS_OBJECT_COUNT = 0;
James Feist4131aea2018-03-09 09:47:30 -08001577
James Feist6ebf9de2018-05-15 15:01:17 -07001578 // todo, get this from a more sensable place
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001579 std::vector<uint8_t> baseboardFRU;
1580 if (readBaseboardFRU(baseboardFRU))
James Feist6ebf9de2018-05-15 15:01:17 -07001581 {
Andrei Kartashev1c5b7062020-08-19 14:47:30 +03001582 boost::container::flat_map<int, std::vector<uint8_t>>
James Feist6ebf9de2018-05-15 15:01:17 -07001583 baseboardDev;
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001584 baseboardDev.emplace(0, baseboardFRU);
James Feist98132792019-07-09 13:29:09 -07001585 busmap[0] = std::make_shared<DeviceMap>(baseboardDev);
James Feist6ebf9de2018-05-15 15:01:17 -07001586 }
James Feist98132792019-07-09 13:29:09 -07001587 for (auto& devicemap : busmap)
James Feist6ebf9de2018-05-15 15:01:17 -07001588 {
James Feista465ccc2019-02-08 12:51:01 -08001589 for (auto& device : *devicemap.second)
James Feist6ebf9de2018-05-15 15:01:17 -07001590 {
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001591 AddFRUObjectToDbus(device.second, dbusInterfaceMap,
James Feist5cb06082019-10-24 17:11:13 -07001592 devicemap.first, device.first);
James Feist6ebf9de2018-05-15 15:01:17 -07001593 }
1594 }
1595 });
1596 scan->run();
1597 });
James Feist918e18c2018-02-13 15:51:07 -08001598}
1599
Joshi-Mansid73b7442020-03-27 19:10:21 +05301600// Calculate new checksum for fru info area
Andrei Kartashev2a7e3952020-09-02 20:32:26 +03001601uint8_t calculateChecksum(std::vector<uint8_t>::const_iterator iter,
1602 std::vector<uint8_t>::const_iterator end)
Joshi-Mansid73b7442020-03-27 19:10:21 +05301603{
1604 constexpr int checksumMod = 256;
1605 constexpr uint8_t modVal = 0xFF;
Andrei Kartashev2a7e3952020-09-02 20:32:26 +03001606 int sum = std::accumulate(iter, end, 0);
1607 int checksum = (checksumMod - sum) & modVal;
1608 return static_cast<uint8_t>(checksum);
1609}
1610uint8_t calculateChecksum(std::vector<uint8_t>& fruAreaData)
1611{
1612 return calculateChecksum(fruAreaData.begin(), fruAreaData.end());
Joshi-Mansid73b7442020-03-27 19:10:21 +05301613}
1614
1615// Update new fru area length &
1616// Update checksum at new checksum location
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +03001617// Return the offset of the area checksum byte
1618static unsigned int updateFRUAreaLenAndChecksum(std::vector<uint8_t>& fruData,
1619 size_t fruAreaStart,
1620 size_t fruAreaEndOfFieldsOffset,
1621 size_t fruAreaEndOffset)
Joshi-Mansid73b7442020-03-27 19:10:21 +05301622{
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001623 size_t traverseFRUAreaIndex = fruAreaEndOfFieldsOffset - fruAreaStart;
Joshi-Mansid73b7442020-03-27 19:10:21 +05301624
1625 // fill zeros for any remaining unused space
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +03001626 std::fill(fruData.begin() + fruAreaEndOfFieldsOffset,
1627 fruData.begin() + fruAreaEndOffset, 0);
Joshi-Mansid73b7442020-03-27 19:10:21 +05301628
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001629 size_t mod = traverseFRUAreaIndex % fruBlockSize;
Joshi-Mansid73b7442020-03-27 19:10:21 +05301630 size_t checksumLoc;
1631 if (!mod)
1632 {
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001633 traverseFRUAreaIndex += (fruBlockSize);
1634 checksumLoc = fruAreaEndOfFieldsOffset + (fruBlockSize - 1);
Joshi-Mansid73b7442020-03-27 19:10:21 +05301635 }
1636 else
1637 {
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001638 traverseFRUAreaIndex += (fruBlockSize - mod);
1639 checksumLoc = fruAreaEndOfFieldsOffset + (fruBlockSize - mod - 1);
Joshi-Mansid73b7442020-03-27 19:10:21 +05301640 }
1641
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001642 size_t newFRUAreaLen = (traverseFRUAreaIndex / fruBlockSize) +
1643 ((traverseFRUAreaIndex % fruBlockSize) != 0);
1644 size_t fruAreaLengthLoc = fruAreaStart + 1;
1645 fruData[fruAreaLengthLoc] = static_cast<uint8_t>(newFRUAreaLen);
Joshi-Mansid73b7442020-03-27 19:10:21 +05301646
1647 // Calculate new checksum
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001648 std::vector<uint8_t> finalFRUData;
1649 std::copy_n(fruData.begin() + fruAreaStart, checksumLoc - fruAreaStart,
1650 std::back_inserter(finalFRUData));
Joshi-Mansid73b7442020-03-27 19:10:21 +05301651
Andrei Kartashev2a7e3952020-09-02 20:32:26 +03001652 fruData[checksumLoc] = calculateChecksum(finalFRUData);
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +03001653 return checksumLoc;
Joshi-Mansid73b7442020-03-27 19:10:21 +05301654}
1655
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +03001656static ssize_t getFieldLength(uint8_t fruFieldTypeLenValue)
Joshi-Mansid73b7442020-03-27 19:10:21 +05301657{
1658 constexpr uint8_t typeLenMask = 0x3F;
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +03001659 constexpr uint8_t endOfFields = 0xC1;
1660 if (fruFieldTypeLenValue == endOfFields)
1661 {
1662 return -1;
1663 }
1664 else
1665 {
1666 return fruFieldTypeLenValue & typeLenMask;
1667 }
Joshi-Mansid73b7442020-03-27 19:10:21 +05301668}
1669
1670// Details with example of Asset Tag Update
1671// To find location of Product Info Area asset tag as per FRU specification
1672// 1. Find product Info area starting offset (*8 - as header will be in
1673// multiple of 8 bytes).
1674// 2. Skip 3 bytes of product info area (like format version, area length,
1675// and language code).
1676// 3. Traverse manufacturer name, product name, product version, & product
1677// serial number, by reading type/length code to reach the Asset Tag.
1678// 4. Update the Asset Tag, reposition the product Info area in multiple of
1679// 8 bytes. Update the Product area length and checksum.
1680
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001681bool updateFRUProperty(
Joshi-Mansid73b7442020-03-27 19:10:21 +05301682 const std::string& updatePropertyReq, uint32_t bus, uint32_t address,
1683 std::string propertyName,
1684 boost::container::flat_map<
1685 std::pair<size_t, size_t>,
1686 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap)
1687{
1688 size_t updatePropertyReqLen = updatePropertyReq.length();
1689 if (updatePropertyReqLen == 1 || updatePropertyReqLen > 63)
1690 {
1691 std::cerr
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001692 << "FRU field data cannot be of 1 char or more than 63 chars. "
Joshi-Mansid73b7442020-03-27 19:10:21 +05301693 "Invalid Length "
1694 << updatePropertyReqLen << "\n";
1695 return false;
1696 }
1697
1698 std::vector<uint8_t> fruData;
1699 try
1700 {
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001701 fruData = getFRUInfo(static_cast<uint8_t>(bus),
Joshi-Mansid73b7442020-03-27 19:10:21 +05301702 static_cast<uint8_t>(address));
1703 }
1704 catch (std::invalid_argument& e)
1705 {
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001706 std::cerr << "Failure getting FRU Info" << e.what() << "\n";
Joshi-Mansid73b7442020-03-27 19:10:21 +05301707 return false;
1708 }
1709
1710 if (fruData.empty())
1711 {
1712 return false;
1713 }
1714
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001715 const std::vector<std::string>* fruAreaFieldNames;
Joshi-Mansid73b7442020-03-27 19:10:21 +05301716
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +03001717 uint8_t fruAreaOffsetFieldValue = 0;
Joshi-Mansid73b7442020-03-27 19:10:21 +05301718 size_t offset = 0;
Andrei Kartashev9f0f2d12020-08-20 04:24:37 +03001719 std::string areaName = propertyName.substr(0, propertyName.find("_"));
Andrei Kartashev6cfb7752020-08-10 19:50:33 +03001720 std::string propertyNamePrefix = areaName + "_";
Andrei Kartashev9f0f2d12020-08-20 04:24:37 +03001721 auto it = std::find(FRU_AREA_NAMES.begin(), FRU_AREA_NAMES.end(), areaName);
1722 if (it == FRU_AREA_NAMES.end())
Joshi-Mansid73b7442020-03-27 19:10:21 +05301723 {
Andrei Kartashev9f0f2d12020-08-20 04:24:37 +03001724 std::cerr << "Can't parse area name for property " << propertyName
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +03001725 << " \n";
1726 return false;
1727 }
Andrei Kartashev9f0f2d12020-08-20 04:24:37 +03001728 fruAreas fruAreaToUpdate =
1729 static_cast<fruAreas>(it - FRU_AREA_NAMES.begin());
1730 fruAreaOffsetFieldValue =
1731 fruData[getHeaderAreaFieldOffset(fruAreaToUpdate)];
1732 switch (fruAreaToUpdate)
1733 {
1734 case fruAreas::fruAreaChassis:
1735 offset = 3; // chassis part number offset. Skip fixed first 3 bytes
1736 fruAreaFieldNames = &CHASSIS_FRU_AREAS;
1737 break;
1738 case fruAreas::fruAreaBoard:
1739 offset = 6; // board manufacturer offset. Skip fixed first 6 bytes
1740 fruAreaFieldNames = &BOARD_FRU_AREAS;
1741 break;
1742 case fruAreas::fruAreaProduct:
1743 // Manufacturer name offset. Skip fixed first 3 product fru bytes
1744 // i.e. version, area length and language code
1745 offset = 3;
1746 fruAreaFieldNames = &PRODUCT_FRU_AREAS;
1747 break;
1748 default:
1749 std::cerr << "Don't know how to handle property " << propertyName
1750 << " \n";
1751 return false;
1752 }
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +03001753 if (fruAreaOffsetFieldValue == 0)
1754 {
1755 std::cerr << "FRU Area for " << propertyName << " not present \n";
Joshi-Mansid73b7442020-03-27 19:10:21 +05301756 return false;
1757 }
1758
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001759 size_t fruAreaStart = fruAreaOffsetFieldValue * fruBlockSize;
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +03001760 size_t fruAreaSize = fruData[fruAreaStart + 1] * fruBlockSize;
1761 size_t fruAreaEnd = fruAreaStart + fruAreaSize;
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001762 size_t fruDataIter = fruAreaStart + offset;
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +03001763 size_t fruUpdateFieldLoc = fruDataIter;
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001764 size_t skipToFRUUpdateField = 0;
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +03001765 ssize_t fieldLength;
Joshi-Mansid73b7442020-03-27 19:10:21 +05301766
Andrei Kartashev6cfb7752020-08-10 19:50:33 +03001767 bool found = false;
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001768 for (auto& field : *fruAreaFieldNames)
Joshi-Mansid73b7442020-03-27 19:10:21 +05301769 {
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001770 skipToFRUUpdateField++;
Andrei Kartashev6cfb7752020-08-10 19:50:33 +03001771 if (propertyName == propertyNamePrefix + field)
Joshi-Mansid73b7442020-03-27 19:10:21 +05301772 {
Andrei Kartashev6cfb7752020-08-10 19:50:33 +03001773 found = true;
Joshi-Mansid73b7442020-03-27 19:10:21 +05301774 break;
1775 }
1776 }
Andrei Kartashev6cfb7752020-08-10 19:50:33 +03001777 if (!found)
1778 {
1779 std::size_t pos = propertyName.find(FRU_CUSTOM_FIELD_NAME);
1780 if (pos == std::string::npos)
1781 {
1782 std::cerr << "PropertyName doesn't exist in FRU Area Vectors: "
1783 << propertyName << "\n";
1784 return false;
1785 }
1786 std::string fieldNumStr =
1787 propertyName.substr(pos + FRU_CUSTOM_FIELD_NAME.length());
1788 size_t fieldNum = std::stoi(fieldNumStr);
1789 if (fieldNum == 0)
1790 {
1791 std::cerr << "PropertyName not recognized: " << propertyName
1792 << "\n";
1793 return false;
1794 }
1795 skipToFRUUpdateField += fieldNum;
1796 }
Joshi-Mansid73b7442020-03-27 19:10:21 +05301797
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001798 for (size_t i = 1; i < skipToFRUUpdateField; i++)
Joshi-Mansid73b7442020-03-27 19:10:21 +05301799 {
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001800 fieldLength = getFieldLength(fruData[fruDataIter]);
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +03001801 if (fieldLength < 0)
1802 {
1803 break;
1804 }
1805 fruDataIter += 1 + fieldLength;
Joshi-Mansid73b7442020-03-27 19:10:21 +05301806 }
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +03001807 fruUpdateFieldLoc = fruDataIter;
Joshi-Mansid73b7442020-03-27 19:10:21 +05301808
1809 // Push post update fru field bytes to a vector
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001810 fieldLength = getFieldLength(fruData[fruUpdateFieldLoc]);
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +03001811 if (fieldLength < 0)
Joshi-Mansid73b7442020-03-27 19:10:21 +05301812 {
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +03001813 std::cerr << "Property " << propertyName << " not present \n";
1814 return false;
1815 }
1816 fruDataIter += 1 + fieldLength;
1817 size_t restFRUFieldsLoc = fruDataIter;
1818 size_t endOfFieldsLoc = 0;
1819 while ((fieldLength = getFieldLength(fruData[fruDataIter])) >= 0)
1820 {
1821 if (fruDataIter >= (fruAreaStart + fruAreaSize))
Joshi-Mansid73b7442020-03-27 19:10:21 +05301822 {
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +03001823 fruDataIter = fruAreaStart + fruAreaSize;
Joshi-Mansid73b7442020-03-27 19:10:21 +05301824 break;
1825 }
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +03001826 fruDataIter += 1 + fieldLength;
Joshi-Mansid73b7442020-03-27 19:10:21 +05301827 }
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +03001828 endOfFieldsLoc = fruDataIter;
Joshi-Mansid73b7442020-03-27 19:10:21 +05301829
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001830 std::vector<uint8_t> restFRUAreaFieldsData;
1831 std::copy_n(fruData.begin() + restFRUFieldsLoc,
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +03001832 endOfFieldsLoc - restFRUFieldsLoc + 1,
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001833 std::back_inserter(restFRUAreaFieldsData));
Joshi-Mansid73b7442020-03-27 19:10:21 +05301834
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +03001835 // Push post update fru areas if any
1836 unsigned int nextFRUAreaLoc = 0;
Andrei Kartashev9f0f2d12020-08-20 04:24:37 +03001837 for (fruAreas nextFRUArea = fruAreas::fruAreaInternal;
1838 nextFRUArea <= fruAreas::fruAreaMultirecord; ++nextFRUArea)
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +03001839 {
1840 unsigned int fruAreaLoc =
Andrei Kartashev9f0f2d12020-08-20 04:24:37 +03001841 fruData[getHeaderAreaFieldOffset(nextFRUArea)] * fruBlockSize;
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +03001842 if ((fruAreaLoc > endOfFieldsLoc) &&
1843 ((nextFRUAreaLoc == 0) || (fruAreaLoc < nextFRUAreaLoc)))
1844 {
1845 nextFRUAreaLoc = fruAreaLoc;
1846 }
1847 }
1848 std::vector<uint8_t> restFRUAreasData;
1849 if (nextFRUAreaLoc)
1850 {
1851 std::copy_n(fruData.begin() + nextFRUAreaLoc,
1852 fruData.size() - nextFRUAreaLoc,
1853 std::back_inserter(restFRUAreasData));
1854 }
1855
1856 // check FRU area size
1857 size_t fruAreaDataSize =
1858 ((fruUpdateFieldLoc - fruAreaStart + 1) + restFRUAreaFieldsData.size());
1859 size_t fruAreaAvailableSize = fruAreaSize - fruAreaDataSize;
1860 if ((updatePropertyReqLen + 1) > fruAreaAvailableSize)
1861 {
1862
1863#ifdef ENABLE_FRU_AREA_RESIZE
1864 size_t newFRUAreaSize = fruAreaDataSize + updatePropertyReqLen + 1;
1865 // round size to 8-byte blocks
1866 newFRUAreaSize =
1867 ((newFRUAreaSize - 1) / fruBlockSize + 1) * fruBlockSize;
1868 size_t newFRUDataSize = fruData.size() + newFRUAreaSize - fruAreaSize;
1869 fruData.resize(newFRUDataSize);
1870 fruAreaSize = newFRUAreaSize;
1871 fruAreaEnd = fruAreaStart + fruAreaSize;
1872#else
1873 std::cerr << "FRU field length: " << updatePropertyReqLen + 1
1874 << " should not be greater than available FRU area size: "
1875 << fruAreaAvailableSize << "\n";
1876 return false;
1877#endif // ENABLE_FRU_AREA_RESIZE
1878 }
1879
Joshi-Mansid73b7442020-03-27 19:10:21 +05301880 // write new requested property field length and data
1881 constexpr uint8_t newTypeLenMask = 0xC0;
1882 fruData[fruUpdateFieldLoc] =
1883 static_cast<uint8_t>(updatePropertyReqLen | newTypeLenMask);
1884 fruUpdateFieldLoc++;
1885 std::copy(updatePropertyReq.begin(), updatePropertyReq.end(),
1886 fruData.begin() + fruUpdateFieldLoc);
1887
1888 // Copy remaining data to main fru area - post updated fru field vector
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001889 restFRUFieldsLoc = fruUpdateFieldLoc + updatePropertyReqLen;
1890 size_t fruAreaDataEnd = restFRUFieldsLoc + restFRUAreaFieldsData.size();
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001891 std::copy(restFRUAreaFieldsData.begin(), restFRUAreaFieldsData.end(),
1892 fruData.begin() + restFRUFieldsLoc);
Joshi-Mansid73b7442020-03-27 19:10:21 +05301893
1894 // Update final fru with new fru area length and checksum
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +03001895 unsigned int nextFRUAreaNewLoc = updateFRUAreaLenAndChecksum(
1896 fruData, fruAreaStart, fruAreaDataEnd, fruAreaEnd);
Joshi-Mansid73b7442020-03-27 19:10:21 +05301897
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +03001898#ifdef ENABLE_FRU_AREA_RESIZE
1899 ++nextFRUAreaNewLoc;
1900 ssize_t nextFRUAreaOffsetDiff =
1901 (nextFRUAreaNewLoc - nextFRUAreaLoc) / fruBlockSize;
1902 // Append rest FRU Areas if size changed and there were other sections after
1903 // updated one
1904 if (nextFRUAreaOffsetDiff && nextFRUAreaLoc)
1905 {
1906 std::copy(restFRUAreasData.begin(), restFRUAreasData.end(),
1907 fruData.begin() + nextFRUAreaNewLoc);
1908 // Update Common Header
Andrei Kartashev9f0f2d12020-08-20 04:24:37 +03001909 for (int fruArea = fruAreaInternal; fruArea <= fruAreaMultirecord;
1910 fruArea++)
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +03001911 {
Andrei Kartashev9f0f2d12020-08-20 04:24:37 +03001912 unsigned int fruAreaOffsetField = getHeaderAreaFieldOffset(fruArea);
Andrei Kartashev6b3d4c52020-08-10 19:24:17 +03001913 size_t curFRUAreaOffset = fruData[fruAreaOffsetField];
1914 if (curFRUAreaOffset > fruAreaOffsetFieldValue)
1915 {
1916 fruData[fruAreaOffsetField] = static_cast<int8_t>(
1917 curFRUAreaOffset + nextFRUAreaOffsetDiff);
1918 }
1919 }
1920 // Calculate new checksum
1921 std::vector<uint8_t> headerFRUData;
1922 std::copy_n(fruData.begin(), 7, std::back_inserter(headerFRUData));
1923 size_t checksumVal = calculateChecksum(headerFRUData);
1924 fruData[7] = static_cast<uint8_t>(checksumVal);
1925 // fill zeros if FRU Area size decreased
1926 if (nextFRUAreaOffsetDiff < 0)
1927 {
1928 std::fill(fruData.begin() + nextFRUAreaNewLoc +
1929 restFRUAreasData.size(),
1930 fruData.end(), 0);
1931 }
1932 }
1933#else
1934 // this is to avoid "unused variable" warning
1935 (void)nextFRUAreaNewLoc;
1936#endif // ENABLE_FRU_AREA_RESIZE
Joshi-Mansid73b7442020-03-27 19:10:21 +05301937 if (fruData.empty())
1938 {
1939 return false;
1940 }
1941
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001942 if (!writeFRU(static_cast<uint8_t>(bus), static_cast<uint8_t>(address),
Joshi-Mansid73b7442020-03-27 19:10:21 +05301943 fruData))
1944 {
1945 return false;
1946 }
1947
1948 // Rescan the bus so that GetRawFru dbus-call fetches updated values
1949 rescanBusses(busMap, dbusInterfaceMap);
1950 return true;
1951}
1952
James Feist98132792019-07-09 13:29:09 -07001953int main()
James Feist3cb5fec2018-01-23 14:41:51 -08001954{
1955 auto devDir = fs::path("/dev/");
James Feistc9dff1b2019-02-13 13:33:13 -08001956 auto matchString = std::string(R"(i2c-\d+$)");
James Feist3cb5fec2018-01-23 14:41:51 -08001957 std::vector<fs::path> i2cBuses;
1958
James Feista3c180a2018-08-09 16:06:04 -07001959 if (!findFiles(devDir, matchString, i2cBuses))
James Feist3cb5fec2018-01-23 14:41:51 -08001960 {
1961 std::cerr << "unable to find i2c devices\n";
1962 return 1;
1963 }
James Feist3cb5fec2018-01-23 14:41:51 -08001964
Patrick Venture11f1ff42019-08-01 10:42:12 -07001965 // check for and load blacklist with initial buses.
1966 loadBlacklist(blacklistPath);
1967
Vijay Khemka065f6d92018-12-18 10:37:47 -08001968 systemBus->request_name("xyz.openbmc_project.FruDevice");
James Feist3cb5fec2018-01-23 14:41:51 -08001969
James Feist6ebf9de2018-05-15 15:01:17 -07001970 // this is a map with keys of pair(bus number, address) and values of
1971 // the object on dbus
James Feist3cb5fec2018-01-23 14:41:51 -08001972 boost::container::flat_map<std::pair<size_t, size_t>,
James Feist9eb0b582018-04-27 12:15:46 -07001973 std::shared_ptr<sdbusplus::asio::dbus_interface>>
1974 dbusInterfaceMap;
James Feist3cb5fec2018-01-23 14:41:51 -08001975
James Feist9eb0b582018-04-27 12:15:46 -07001976 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
1977 objServer.add_interface("/xyz/openbmc_project/FruDevice",
1978 "xyz.openbmc_project.FruDeviceManager");
James Feist3cb5fec2018-01-23 14:41:51 -08001979
James Feist5cb06082019-10-24 17:11:13 -07001980 iface->register_method("ReScan",
1981 [&]() { rescanBusses(busMap, dbusInterfaceMap); });
James Feist2a9d6db2018-04-27 15:48:28 -07001982
Cheng C Yangd5b87fb2020-01-23 10:10:45 +08001983 iface->register_method("ReScanBus", [&](uint8_t bus) {
James Feist5d7f4692020-06-17 18:22:33 -07001984 rescanOneBus(busMap, bus, dbusInterfaceMap, true);
Cheng C Yangd5b87fb2020-01-23 10:10:45 +08001985 });
1986
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001987 iface->register_method("GetRawFru", getFRUInfo);
James Feistb49ffc32018-05-02 11:10:43 -07001988
1989 iface->register_method("WriteFru", [&](const uint8_t bus,
1990 const uint8_t address,
James Feista465ccc2019-02-08 12:51:01 -08001991 const std::vector<uint8_t>& data) {
Andrei Kartashev2f0de172020-08-13 14:29:40 +03001992 if (!writeFRU(bus, address, data))
James Feistb49ffc32018-05-02 11:10:43 -07001993 {
James Feistddb78302018-09-06 11:45:42 -07001994 throw std::invalid_argument("Invalid Arguments.");
James Feistb49ffc32018-05-02 11:10:43 -07001995 return;
1996 }
1997 // schedule rescan on success
James Feist5cb06082019-10-24 17:11:13 -07001998 rescanBusses(busMap, dbusInterfaceMap);
James Feistb49ffc32018-05-02 11:10:43 -07001999 });
James Feist9eb0b582018-04-27 12:15:46 -07002000 iface->initialize();
James Feist3cb5fec2018-01-23 14:41:51 -08002001
James Feist9eb0b582018-04-27 12:15:46 -07002002 std::function<void(sdbusplus::message::message & message)> eventHandler =
James Feista465ccc2019-02-08 12:51:01 -08002003 [&](sdbusplus::message::message& message) {
James Feist918e18c2018-02-13 15:51:07 -08002004 std::string objectName;
James Feist9eb0b582018-04-27 12:15:46 -07002005 boost::container::flat_map<
James Feista465ccc2019-02-08 12:51:01 -08002006 std::string,
2007 std::variant<std::string, bool, int64_t, uint64_t, double>>
James Feist9eb0b582018-04-27 12:15:46 -07002008 values;
2009 message.read(objectName, values);
James Feist0eeb79c2019-10-09 13:35:29 -07002010 auto findState = values.find("CurrentHostState");
James Feist0eeb79c2019-10-09 13:35:29 -07002011 if (findState != values.end())
James Feist918e18c2018-02-13 15:51:07 -08002012 {
James Feistcb5661d2020-06-12 15:05:01 -07002013 powerIsOn = boost::ends_with(
2014 std::get<std::string>(findState->second), "Running");
James Feist0eeb79c2019-10-09 13:35:29 -07002015 }
James Feist6ebf9de2018-05-15 15:01:17 -07002016
James Feistcb5661d2020-06-12 15:05:01 -07002017 if (powerIsOn)
James Feist0eeb79c2019-10-09 13:35:29 -07002018 {
James Feist5cb06082019-10-24 17:11:13 -07002019 rescanBusses(busMap, dbusInterfaceMap);
James Feist918e18c2018-02-13 15:51:07 -08002020 }
James Feist918e18c2018-02-13 15:51:07 -08002021 };
James Feist9eb0b582018-04-27 12:15:46 -07002022
2023 sdbusplus::bus::match::match powerMatch = sdbusplus::bus::match::match(
James Feista465ccc2019-02-08 12:51:01 -08002024 static_cast<sdbusplus::bus::bus&>(*systemBus),
James Feist7bcd3f22019-03-18 16:04:04 -07002025 "type='signal',interface='org.freedesktop.DBus.Properties',path='/xyz/"
James Feist0eeb79c2019-10-09 13:35:29 -07002026 "openbmc_project/state/"
2027 "host0',arg0='xyz.openbmc_project.State.Host'",
James Feist9eb0b582018-04-27 12:15:46 -07002028 eventHandler);
2029
James Feist4131aea2018-03-09 09:47:30 -08002030 int fd = inotify_init();
James Feist0eb40352019-04-09 14:44:04 -07002031 inotify_add_watch(fd, I2C_DEV_LOCATION,
2032 IN_CREATE | IN_MOVED_TO | IN_DELETE);
James Feist4131aea2018-03-09 09:47:30 -08002033 std::array<char, 4096> readBuffer;
2034 std::string pendingBuffer;
2035 // monitor for new i2c devices
2036 boost::asio::posix::stream_descriptor dirWatch(io, fd);
2037 std::function<void(const boost::system::error_code, std::size_t)>
James Feista465ccc2019-02-08 12:51:01 -08002038 watchI2cBusses = [&](const boost::system::error_code& ec,
James Feist4131aea2018-03-09 09:47:30 -08002039 std::size_t bytes_transferred) {
2040 if (ec)
2041 {
2042 std::cout << "Callback Error " << ec << "\n";
2043 return;
2044 }
2045 pendingBuffer += std::string(readBuffer.data(), bytes_transferred);
James Feist4131aea2018-03-09 09:47:30 -08002046 while (pendingBuffer.size() > sizeof(inotify_event))
2047 {
James Feista465ccc2019-02-08 12:51:01 -08002048 const inotify_event* iEvent =
2049 reinterpret_cast<const inotify_event*>(
James Feist4131aea2018-03-09 09:47:30 -08002050 pendingBuffer.data());
2051 switch (iEvent->mask)
2052 {
James Feist9eb0b582018-04-27 12:15:46 -07002053 case IN_CREATE:
2054 case IN_MOVED_TO:
2055 case IN_DELETE:
James Feist5d7f4692020-06-17 18:22:33 -07002056 std::string name(iEvent->name);
2057 if (boost::starts_with(name, "i2c"))
James Feist9eb0b582018-04-27 12:15:46 -07002058 {
James Feist5d7f4692020-06-17 18:22:33 -07002059 int bus = busStrToInt(name);
2060 if (bus < 0)
2061 {
2062 std::cerr << "Could not parse bus " << name
2063 << "\n";
2064 continue;
2065 }
2066 rescanOneBus(busMap, static_cast<uint8_t>(bus),
2067 dbusInterfaceMap, false);
James Feist9eb0b582018-04-27 12:15:46 -07002068 }
James Feist4131aea2018-03-09 09:47:30 -08002069 }
2070
2071 pendingBuffer.erase(0, sizeof(inotify_event) + iEvent->len);
2072 }
James Feist6ebf9de2018-05-15 15:01:17 -07002073
James Feist4131aea2018-03-09 09:47:30 -08002074 dirWatch.async_read_some(boost::asio::buffer(readBuffer),
2075 watchI2cBusses);
2076 };
2077
2078 dirWatch.async_read_some(boost::asio::buffer(readBuffer), watchI2cBusses);
Gunnar Millsb3e42fe2018-06-13 15:48:27 -05002079 // run the initial scan
James Feist5cb06082019-10-24 17:11:13 -07002080 rescanBusses(busMap, dbusInterfaceMap);
James Feist918e18c2018-02-13 15:51:07 -08002081
James Feist3cb5fec2018-01-23 14:41:51 -08002082 io.run();
2083 return 0;
2084}