blob: 68f1659f2014ee196bf9354a23e57e8d90e41920 [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*/
16
Patrick Venturea49dc332019-10-26 08:32:02 -070017#include "Utils.hpp"
18
James Feist3b860982018-10-02 14:34:07 -070019#include <errno.h>
James Feist3cb5fec2018-01-23 14:41:51 -080020#include <fcntl.h>
James Feist3b860982018-10-02 14:34:07 -070021#include <sys/inotify.h>
22#include <sys/ioctl.h>
23
Patrick Venturec274f2f2019-10-26 09:27:23 -070024#include <array>
James Feist3b860982018-10-02 14:34:07 -070025#include <boost/algorithm/string/predicate.hpp>
26#include <boost/container/flat_map.hpp>
27#include <chrono>
28#include <ctime>
Patrick Venturee3754002019-08-06 09:39:12 -070029#include <filesystem>
James Feist3cb5fec2018-01-23 14:41:51 -080030#include <fstream>
Patrick Venturebaba7672019-10-26 09:26:41 -070031#include <functional>
James Feist3cb5fec2018-01-23 14:41:51 -080032#include <future>
Patrick Venturec50e1ff2019-08-06 10:22:28 -070033#include <iomanip>
James Feist3cb5fec2018-01-23 14:41:51 -080034#include <iostream>
Patrick Venturee26395d2019-10-29 14:05:16 -070035#include <limits>
Patrick Venture11f1ff42019-08-01 10:42:12 -070036#include <nlohmann/json.hpp>
James Feist3f8a2782018-02-12 09:24:42 -080037#include <regex>
James Feist3b860982018-10-02 14:34:07 -070038#include <sdbusplus/asio/connection.hpp>
39#include <sdbusplus/asio/object_server.hpp>
Patrick Venturec50e1ff2019-08-06 10:22:28 -070040#include <set>
41#include <sstream>
Patrick Venture11f1ff42019-08-01 10:42:12 -070042#include <string>
James Feist3b860982018-10-02 14:34:07 -070043#include <thread>
James Feista465ccc2019-02-08 12:51:01 -080044#include <variant>
Patrick Venturec274f2f2019-10-26 09:27:23 -070045#include <vector>
James Feist3b860982018-10-02 14:34:07 -070046
47extern "C" {
48#include <i2c/smbus.h>
49#include <linux/i2c-dev.h>
50}
James Feist3cb5fec2018-01-23 14:41:51 -080051
Ed Tanous072e25d2018-12-16 21:45:20 -080052namespace fs = std::filesystem;
James Feist3cb5fec2018-01-23 14:41:51 -080053static constexpr bool DEBUG = false;
54static size_t UNKNOWN_BUS_OBJECT_COUNT = 0;
James Feistb49ffc32018-05-02 11:10:43 -070055constexpr size_t MAX_FRU_SIZE = 512;
56constexpr size_t MAX_EEPROM_PAGE_INDEX = 255;
James Feist26c27ad2018-07-25 15:09:40 -070057constexpr size_t busTimeoutSeconds = 5;
James Feist3cb5fec2018-01-23 14:41:51 -080058
Patrick Venture11f1ff42019-08-01 10:42:12 -070059constexpr const char* blacklistPath = PACKAGE_DIR "blacklist.json";
60
James Feista465ccc2019-02-08 12:51:01 -080061const static constexpr char* BASEBOARD_FRU_LOCATION =
James Feist3cb5fec2018-01-23 14:41:51 -080062 "/etc/fru/baseboard.fru.bin";
63
James Feista465ccc2019-02-08 12:51:01 -080064const static constexpr char* I2C_DEV_LOCATION = "/dev";
James Feist4131aea2018-03-09 09:47:30 -080065
James Feista465ccc2019-02-08 12:51:01 -080066static constexpr std::array<const char*, 5> FRU_AREAS = {
James Feist3cb5fec2018-01-23 14:41:51 -080067 "INTERNAL", "CHASSIS", "BOARD", "PRODUCT", "MULTIRECORD"};
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -070068const static std::regex NON_ASCII_REGEX("[^\x01-\x7f]");
James Feist3cb5fec2018-01-23 14:41:51 -080069using DeviceMap = boost::container::flat_map<int, std::vector<char>>;
70using BusMap = boost::container::flat_map<int, std::shared_ptr<DeviceMap>>;
71
James Feist444830e2019-04-05 08:38:16 -070072static std::set<size_t> busBlacklist;
James Feist6ebf9de2018-05-15 15:01:17 -070073struct FindDevicesWithCallback;
74
Nikhil Potaded8884f12019-03-27 13:27:13 -070075static BusMap busMap;
76
James Feist8a983922019-10-24 17:11:56 -070077static boost::container::flat_map<
78 std::pair<size_t, size_t>, std::shared_ptr<sdbusplus::asio::dbus_interface>>
79 foundDevices;
80
James Feist7972bb92019-11-13 15:59:24 -080081static boost::container::flat_map<size_t, std::set<size_t>> failedAddresses;
82
James Feist5cb06082019-10-24 17:11:13 -070083boost::asio::io_service io;
84auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
85auto objServer = sdbusplus::asio::object_server(systemBus);
86
Patrick Venturebaba7672019-10-26 09:26:41 -070087bool validateHeader(const std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData);
88
89using ReadBlockFunc = std::function<int64_t(int flag, int file, uint16_t offset,
90 uint8_t length, uint8_t* outBuf)>;
91
92// Read and validate FRU contents, given the flag required for raw i2c, the open
93// file handle, a read method, and a helper string for failures.
94std::vector<char> readFruContents(int flag, int file, ReadBlockFunc readBlock,
95 const std::string& errorHelp)
96{
97 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData;
98
99 if (readBlock(flag, file, 0x0, 0x8, blockData.data()) < 0)
100 {
101 std::cerr << "failed to read " << errorHelp << "\n";
102 return {};
103 }
104
105 // check the header checksum
106 if (!validateHeader(blockData))
107 {
108 if (DEBUG)
109 {
110 std::cerr << "Illegal header " << errorHelp << "\n";
111 }
112
113 return {};
114 }
115
116 std::vector<char> device;
117 device.insert(device.end(), blockData.begin(), blockData.begin() + 8);
118
Patrick Venturee26395d2019-10-29 14:05:16 -0700119 bool hasMultiRecords = false;
Patrick Venturebaba7672019-10-26 09:26:41 -0700120 int fruLength = 0;
121 for (size_t jj = 1; jj <= FRU_AREAS.size(); jj++)
122 {
Patrick Venturef2ad76b2019-10-30 08:49:27 -0700123 // Offset value can be 255.
124 int areaOffset = static_cast<uint8_t>(device[jj]);
Patrick Venturebaba7672019-10-26 09:26:41 -0700125 if (areaOffset == 0)
126 {
127 continue;
128 }
129
Patrick Venturee26395d2019-10-29 14:05:16 -0700130 // MultiRecords are different. jj is not tracking section, it's walking
131 // the common header.
132 if (std::string(FRU_AREAS[jj - 1]) == std::string("MULTIRECORD"))
133 {
134 hasMultiRecords = true;
135 break;
136 }
137
Patrick Venturebaba7672019-10-26 09:26:41 -0700138 areaOffset *= 8;
139
140 if (readBlock(flag, file, static_cast<uint16_t>(areaOffset), 0x2,
141 blockData.data()) < 0)
142 {
143 std::cerr << "failed to read " << errorHelp << "\n";
144 return {};
145 }
146
Patrick Venturef2ad76b2019-10-30 08:49:27 -0700147 // Ignore data type (blockData is already unsigned).
Patrick Venturebaba7672019-10-26 09:26:41 -0700148 int length = blockData[1] * 8;
149 areaOffset += length;
150 fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
151 }
152
Patrick Venturee26395d2019-10-29 14:05:16 -0700153 if (hasMultiRecords)
154 {
155 // device[area count] is the index to the last area because the 0th
156 // entry is not an offset in the common header.
Patrick Venturef2ad76b2019-10-30 08:49:27 -0700157 int areaOffset = static_cast<uint8_t>(device[FRU_AREAS.size()]);
Patrick Venturee26395d2019-10-29 14:05:16 -0700158 areaOffset *= 8;
159
160 // the multi-area record header is 5 bytes long.
161 constexpr int multiRecordHeaderSize = 5;
162 constexpr int multiRecordEndOfList = 0x80;
163
164 // Sanity hard-limit to 64KB.
165 while (areaOffset < std::numeric_limits<uint16_t>::max())
166 {
167 // In multi-area, the area offset points to the 0th record, each
168 // record has 3 bytes of the header we care about.
169 if (readBlock(flag, file, static_cast<uint16_t>(areaOffset), 0x3,
170 blockData.data()) < 0)
171 {
172 std::cerr << "failed to read " << errorHelp << "\n";
173 return {};
174 }
175
176 // Ok, let's check the record length, which is in bytes (unsigned,
177 // up to 255, so blockData should hold uint8_t not char)
178 int recordLength = blockData[2];
179 areaOffset += (recordLength + multiRecordHeaderSize);
180 fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
181
182 // If this is the end of the list bail.
183 if ((blockData[1] & multiRecordEndOfList))
184 {
185 break;
186 }
187 }
188 }
189
Patrick Venturebaba7672019-10-26 09:26:41 -0700190 // You already copied these first 8 bytes (the ipmi fru header size)
191 fruLength -= 8;
192
193 int readOffset = 8;
194
195 while (fruLength > 0)
196 {
197 int requestLength = std::min(I2C_SMBUS_BLOCK_MAX, fruLength);
198
199 if (readBlock(flag, file, static_cast<uint16_t>(readOffset),
200 static_cast<uint8_t>(requestLength),
201 blockData.data()) < 0)
202 {
203 std::cerr << "failed to read " << errorHelp << "\n";
204 return {};
205 }
206
207 device.insert(device.end(), blockData.begin(),
208 blockData.begin() + requestLength);
209
210 readOffset += requestLength;
211 fruLength -= requestLength;
212 }
213
214 return device;
215}
216
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700217// Given a bus/address, produce the path in sysfs for an eeprom.
218static std::string getEepromPath(size_t bus, size_t address)
219{
220 std::stringstream output;
221 output << "/sys/bus/i2c/devices/" << bus << "-" << std::right
222 << std::setfill('0') << std::setw(4) << std::hex << address
223 << "/eeprom";
224 return output.str();
225}
226
227static bool hasEepromFile(size_t bus, size_t address)
228{
229 auto path = getEepromPath(bus, address);
230 try
231 {
232 return fs::exists(path);
233 }
234 catch (...)
235 {
236 return false;
237 }
238}
239
Patrick Venture3ac8e4f2019-10-28 13:07:21 -0700240static int64_t readFromEeprom(int flag __attribute__((unused)), int fd,
241 uint16_t offset, uint8_t len, uint8_t* buf)
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700242{
243 auto result = lseek(fd, offset, SEEK_SET);
244 if (result < 0)
245 {
246 std::cerr << "failed to seek\n";
247 return -1;
248 }
249
Patrick Venture3ac8e4f2019-10-28 13:07:21 -0700250 return read(fd, buf, len);
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700251}
252
James Feist7972bb92019-11-13 15:59:24 -0800253static int getRootBus(size_t bus)
254{
255 auto ec = std::error_code();
256 auto path = std::filesystem::read_symlink(
257 std::filesystem::path("/sys/bus/i2c/devices/i2c-" +
258 std::to_string(bus) + "/mux_device"),
259 ec);
260 if (ec)
261 {
262 return -1;
263 }
264
265 std::string filename = path.filename();
266 auto findBus = filename.find("-");
267 if (findBus == std::string::npos)
268 {
269 return -1;
270 }
271 return std::stoi(filename.substr(0, findBus));
272}
273
James Feistc95cb142018-02-26 10:41:42 -0800274static bool isMuxBus(size_t bus)
275{
Ed Tanous072e25d2018-12-16 21:45:20 -0800276 return is_symlink(std::filesystem::path(
James Feistc95cb142018-02-26 10:41:42 -0800277 "/sys/bus/i2c/devices/i2c-" + std::to_string(bus) + "/mux_device"));
278}
279
James Feist8a983922019-10-24 17:11:56 -0700280static void makeProbeInterface(size_t bus, size_t address)
281{
282 if (isMuxBus(bus))
283 {
284 return; // the mux buses are random, no need to publish
285 }
286 auto [it, success] = foundDevices.emplace(
287 std::make_pair(bus, address),
288 objServer.add_interface(
289 "/xyz/openbmc_project/FruDevice/" + std::to_string(bus) + "_" +
290 std::to_string(address),
291 "xyz.openbmc_project.Inventory.Item.I2CDevice"));
292 if (!success)
293 {
294 return; // already added
295 }
296 it->second->register_property("Bus", bus);
297 it->second->register_property("Address", address);
298 it->second->initialize();
299}
300
Vijay Khemka2d681f62018-11-06 15:51:00 -0800301static int isDevice16Bit(int file)
302{
303 /* Get first byte */
304 int byte1 = i2c_smbus_read_byte_data(file, 0);
305 if (byte1 < 0)
306 {
307 return byte1;
308 }
309 /* Read 7 more bytes, it will read same first byte in case of
310 * 8 bit but it will read next byte in case of 16 bit
311 */
312 for (int i = 0; i < 7; i++)
313 {
314 int byte2 = i2c_smbus_read_byte_data(file, 0);
315 if (byte2 < 0)
316 {
317 return byte2;
318 }
319 if (byte2 != byte1)
320 {
321 return 1;
322 }
323 }
324 return 0;
325}
326
Patrick Venture3ac8e4f2019-10-28 13:07:21 -0700327static int64_t readBlockData(int flag, int file, uint16_t offset, uint8_t len,
328 uint8_t* buf)
Vijay Khemka2d681f62018-11-06 15:51:00 -0800329{
Patrick Venture4f47fe62019-08-08 16:30:38 -0700330 uint8_t lowAddr = static_cast<uint8_t>(offset);
331 uint8_t highAddr = static_cast<uint8_t>(offset >> 8);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800332
333 if (flag == 0)
334 {
Patrick Venture4f47fe62019-08-08 16:30:38 -0700335 return i2c_smbus_read_i2c_block_data(file, lowAddr, len, buf);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800336 }
337
338 /* This is for 16 bit addressing EEPROM device. First an offset
339 * needs to be written before read data from a offset
340 */
Patrick Venture4f47fe62019-08-08 16:30:38 -0700341 int ret = i2c_smbus_write_byte_data(file, 0, lowAddr);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800342 if (ret < 0)
343 {
344 return ret;
345 }
346
Patrick Venture4f47fe62019-08-08 16:30:38 -0700347 return i2c_smbus_read_i2c_block_data(file, highAddr, len, buf);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800348}
349
James Feist24bae7a2019-04-03 09:50:56 -0700350bool validateHeader(const std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData)
351{
352 // ipmi spec format version number is currently at 1, verify it
353 if (blockData[0] != 0x1)
354 {
355 return false;
356 }
357
358 // verify pad is set to 0
359 if (blockData[6] != 0x0)
360 {
361 return false;
362 }
363
364 // verify offsets are 0, or don't point to another offset
365 std::set<uint8_t> foundOffsets;
366 for (int ii = 1; ii < 6; ii++)
367 {
368 if (blockData[ii] == 0)
369 {
370 continue;
371 }
James Feist0eb40352019-04-09 14:44:04 -0700372 auto inserted = foundOffsets.insert(blockData[ii]);
373 if (!inserted.second)
James Feist24bae7a2019-04-03 09:50:56 -0700374 {
375 return false;
376 }
377 }
378
379 // validate checksum
380 size_t sum = 0;
381 for (int jj = 0; jj < 7; jj++)
382 {
383 sum += blockData[jj];
384 }
385 sum = (256 - sum) & 0xFF;
386
387 if (sum != blockData[7])
388 {
389 return false;
390 }
391 return true;
392}
393
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700394// TODO: This code is very similar to the non-eeprom version and can be merged
395// with some tweaks.
396static std::vector<char> processEeprom(int bus, int address)
397{
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700398 auto path = getEepromPath(bus, address);
399
400 int file = open(path.c_str(), O_RDONLY);
401 if (file < 0)
402 {
403 std::cerr << "Unable to open eeprom file: " << path << "\n";
Patrick Venturebaba7672019-10-26 09:26:41 -0700404 return {};
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700405 }
406
Patrick Venturebaba7672019-10-26 09:26:41 -0700407 std::string errorMessage = "eeprom at " + std::to_string(bus) +
408 " address " + std::to_string(address);
409 std::vector<char> device =
410 readFruContents(0, file, readFromEeprom, errorMessage);
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700411
412 close(file);
413 return device;
414}
415
416std::set<int> findI2CEeproms(int i2cBus, std::shared_ptr<DeviceMap> devices)
417{
418 std::set<int> foundList;
419
420 std::string path = "/sys/bus/i2c/devices/i2c-" + std::to_string(i2cBus);
421
422 // For each file listed under the i2c device
423 // NOTE: This should be faster than just checking for each possible address
424 // path.
425 for (const auto& p : fs::directory_iterator(path))
426 {
427 const std::string node = p.path().string();
428 std::smatch m;
429 bool found =
430 std::regex_match(node, m, std::regex(".+\\d+-([0-9abcdef]+$)"));
431
432 if (!found)
433 {
434 continue;
435 }
436 if (m.size() != 2)
437 {
438 std::cerr << "regex didn't capture\n";
439 continue;
440 }
441
442 std::ssub_match subMatch = m[1];
443 std::string addressString = subMatch.str();
444
445 std::size_t ignored;
446 const int hexBase = 16;
447 int address = std::stoi(addressString, &ignored, hexBase);
448
449 const std::string eeprom = node + "/eeprom";
450
451 try
452 {
453 if (!fs::exists(eeprom))
454 {
455 continue;
456 }
457 }
458 catch (...)
459 {
460 continue;
461 }
462
463 // There is an eeprom file at this address, it may have invalid
464 // contents, but we found it.
465 foundList.insert(address);
466
467 std::vector<char> device = processEeprom(i2cBus, address);
468 if (!device.empty())
469 {
470 devices->emplace(address, device);
471 }
472 }
473
474 return foundList;
475}
476
Patrick Venture4f47fe62019-08-08 16:30:38 -0700477int getBusFrus(int file, int first, int last, int bus,
478 std::shared_ptr<DeviceMap> devices)
James Feist3cb5fec2018-01-23 14:41:51 -0800479{
James Feist3cb5fec2018-01-23 14:41:51 -0800480
James Feist26c27ad2018-07-25 15:09:40 -0700481 std::future<int> future = std::async(std::launch::async, [&]() {
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700482 // NOTE: When reading the devices raw on the bus, it can interfere with
483 // the driver's ability to operate, therefore read eeproms first before
484 // scanning for devices without drivers. Several experiments were run
485 // and it was determined that if there were any devices on the bus
486 // before the eeprom was hit and read, the eeprom driver wouldn't open
487 // while the bus device was open. An experiment was not performed to see
488 // if this issue was resolved if the i2c bus device was closed, but
489 // hexdumps of the eeprom later were successful.
490
491 // Scan for i2c eeproms loaded on this bus.
492 std::set<int> skipList = findI2CEeproms(bus, devices);
James Feist7972bb92019-11-13 15:59:24 -0800493 std::set<size_t>& failedItems = failedAddresses[bus];
494
495 std::set<size_t>* rootFailures = nullptr;
496 int rootBus = getRootBus(bus);
497
498 if (rootBus >= 0)
499 {
500 rootFailures = &(failedAddresses[rootBus]);
501 }
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700502
James Feist26c27ad2018-07-25 15:09:40 -0700503 for (int ii = first; ii <= last; ii++)
James Feist3cb5fec2018-01-23 14:41:51 -0800504 {
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700505 if (skipList.find(ii) != skipList.end())
506 {
507 continue;
508 }
James Feist3cb5fec2018-01-23 14:41:51 -0800509
James Feist26c27ad2018-07-25 15:09:40 -0700510 // Set slave address
511 if (ioctl(file, I2C_SLAVE_FORCE, ii) < 0)
James Feist3cb5fec2018-01-23 14:41:51 -0800512 {
Patrick Venture98e0cf32019-08-02 11:11:03 -0700513 std::cerr << "device at bus " << bus << " register " << ii
Patrick Venture5d8b61d2019-08-06 12:36:10 -0700514 << " busy\n";
James Feist26c27ad2018-07-25 15:09:40 -0700515 continue;
516 }
517 // probe
518 else if (i2c_smbus_read_byte(file) < 0)
519 {
520 continue;
521 }
James Feist3cb5fec2018-01-23 14:41:51 -0800522
James Feist26c27ad2018-07-25 15:09:40 -0700523 if (DEBUG)
524 {
Patrick Venture98e0cf32019-08-02 11:11:03 -0700525 std::cout << "something at bus " << bus << " addr " << ii
James Feist26c27ad2018-07-25 15:09:40 -0700526 << "\n";
527 }
Vijay Khemka2d681f62018-11-06 15:51:00 -0800528
James Feist8a983922019-10-24 17:11:56 -0700529 makeProbeInterface(bus, ii);
530
James Feist7972bb92019-11-13 15:59:24 -0800531 if (failedItems.find(ii) != failedItems.end())
532 {
533 // if we failed to read it once, unlikely we can read it later
534 continue;
535 }
536
537 if (rootFailures != nullptr)
538 {
539 if (rootFailures->find(ii) != rootFailures->end())
540 {
541 continue;
542 }
543 }
544
Vijay Khemka2d681f62018-11-06 15:51:00 -0800545 /* Check for Device type if it is 8 bit or 16 bit */
546 int flag = isDevice16Bit(file);
547 if (flag < 0)
548 {
549 std::cerr << "failed to read bus " << bus << " address " << ii
550 << "\n";
James Feist7972bb92019-11-13 15:59:24 -0800551 failedItems.insert(ii);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800552 continue;
553 }
554
Patrick Venturebaba7672019-10-26 09:26:41 -0700555 std::string errorMessage =
556 "bus " + std::to_string(bus) + " address " + std::to_string(ii);
557 std::vector<char> device =
558 readFruContents(flag, file, readBlockData, errorMessage);
559 if (device.empty())
James Feist26c27ad2018-07-25 15:09:40 -0700560 {
James Feist26c27ad2018-07-25 15:09:40 -0700561 continue;
562 }
James Feist26c27ad2018-07-25 15:09:40 -0700563
Patrick Venture786f1792019-08-05 16:33:44 -0700564 devices->emplace(ii, device);
James Feist3cb5fec2018-01-23 14:41:51 -0800565 }
James Feist26c27ad2018-07-25 15:09:40 -0700566 return 1;
567 });
568 std::future_status status =
569 future.wait_for(std::chrono::seconds(busTimeoutSeconds));
570 if (status == std::future_status::timeout)
571 {
572 std::cerr << "Error reading bus " << bus << "\n";
James Feist444830e2019-04-05 08:38:16 -0700573 busBlacklist.insert(bus);
574 close(file);
James Feist26c27ad2018-07-25 15:09:40 -0700575 return -1;
James Feist3cb5fec2018-01-23 14:41:51 -0800576 }
577
James Feist444830e2019-04-05 08:38:16 -0700578 close(file);
James Feist26c27ad2018-07-25 15:09:40 -0700579 return future.get();
James Feist3cb5fec2018-01-23 14:41:51 -0800580}
581
Patrick Venture11f1ff42019-08-01 10:42:12 -0700582void loadBlacklist(const char* path)
583{
584 std::ifstream blacklistStream(path);
585 if (!blacklistStream.good())
586 {
587 // File is optional.
588 std::cerr << "Cannot open blacklist file.\n\n";
589 return;
590 }
591
592 nlohmann::json data =
593 nlohmann::json::parse(blacklistStream, nullptr, false);
594 if (data.is_discarded())
595 {
596 std::cerr << "Illegal blacklist file detected, cannot validate JSON, "
597 "exiting\n";
598 std::exit(EXIT_FAILURE);
Patrick Venture11f1ff42019-08-01 10:42:12 -0700599 }
600
601 // It's expected to have at least one field, "buses" that is an array of the
602 // buses by integer. Allow for future options to exclude further aspects,
603 // such as specific addresses or ranges.
604 if (data.type() != nlohmann::json::value_t::object)
605 {
606 std::cerr << "Illegal blacklist, expected to read dictionary\n";
607 std::exit(EXIT_FAILURE);
Patrick Venture11f1ff42019-08-01 10:42:12 -0700608 }
609
610 // If buses field is missing, that's fine.
611 if (data.count("buses") == 1)
612 {
613 // Parse the buses array after a little validation.
614 auto buses = data.at("buses");
615 if (buses.type() != nlohmann::json::value_t::array)
616 {
617 // Buses field present but invalid, therefore this is an error.
618 std::cerr << "Invalid contents for blacklist buses field\n";
619 std::exit(EXIT_FAILURE);
Patrick Venture11f1ff42019-08-01 10:42:12 -0700620 }
621
622 // Catch exception here for type mis-match.
623 try
624 {
625 for (const auto& bus : buses)
626 {
627 busBlacklist.insert(bus.get<size_t>());
628 }
629 }
630 catch (const nlohmann::detail::type_error& e)
631 {
632 // Type mis-match is a critical error.
633 std::cerr << "Invalid bus type: " << e.what() << "\n";
634 std::exit(EXIT_FAILURE);
Patrick Venture11f1ff42019-08-01 10:42:12 -0700635 }
636 }
637
638 return;
639}
640
James Feista465ccc2019-02-08 12:51:01 -0800641static void FindI2CDevices(const std::vector<fs::path>& i2cBuses,
James Feist98132792019-07-09 13:29:09 -0700642 BusMap& busmap)
James Feist3cb5fec2018-01-23 14:41:51 -0800643{
James Feista465ccc2019-02-08 12:51:01 -0800644 for (auto& i2cBus : i2cBuses)
James Feist3cb5fec2018-01-23 14:41:51 -0800645 {
646 auto busnum = i2cBus.string();
647 auto lastDash = busnum.rfind(std::string("-"));
648 // delete everything before dash inclusive
649 if (lastDash != std::string::npos)
650 {
651 busnum.erase(0, lastDash + 1);
652 }
James Feist0c3980a2019-12-19 11:09:27 -0800653
James Feist3cb5fec2018-01-23 14:41:51 -0800654 auto bus = std::stoi(busnum);
James Feist444830e2019-04-05 08:38:16 -0700655 if (busBlacklist.find(bus) != busBlacklist.end())
656 {
657 continue; // skip previously failed busses
658 }
James Feist3cb5fec2018-01-23 14:41:51 -0800659
James Feist0c3980a2019-12-19 11:09:27 -0800660 int rootBus = getRootBus(bus);
661 if (busBlacklist.find(rootBus) != busBlacklist.end())
662 {
663 continue;
664 }
665
James Feist3cb5fec2018-01-23 14:41:51 -0800666 auto file = open(i2cBus.c_str(), O_RDWR);
667 if (file < 0)
668 {
669 std::cerr << "unable to open i2c device " << i2cBus.string()
670 << "\n";
671 continue;
672 }
673 unsigned long funcs = 0;
674
675 if (ioctl(file, I2C_FUNCS, &funcs) < 0)
676 {
677 std::cerr
Patrick Venture98e0cf32019-08-02 11:11:03 -0700678 << "Error: Could not get the adapter functionality matrix bus "
James Feist3cb5fec2018-01-23 14:41:51 -0800679 << bus << "\n";
680 continue;
681 }
682 if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE) ||
683 !(I2C_FUNC_SMBUS_READ_I2C_BLOCK))
684 {
685 std::cerr << "Error: Can't use SMBus Receive Byte command bus "
686 << bus << "\n";
687 continue;
688 }
James Feist98132792019-07-09 13:29:09 -0700689 auto& device = busmap[bus];
James Feist3cb5fec2018-01-23 14:41:51 -0800690 device = std::make_shared<DeviceMap>();
691
Nikhil Potaded8884f12019-03-27 13:27:13 -0700692 // i2cdetect by default uses the range 0x03 to 0x77, as
693 // this is what we have tested with, use this range. Could be
694 // changed in future.
695 if (DEBUG)
James Feistc95cb142018-02-26 10:41:42 -0800696 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700697 std::cerr << "Scanning bus " << bus << "\n";
James Feistc95cb142018-02-26 10:41:42 -0800698 }
Nikhil Potaded8884f12019-03-27 13:27:13 -0700699
700 // fd is closed in this function in case the bus locks up
Patrick Venture4f47fe62019-08-08 16:30:38 -0700701 getBusFrus(file, 0x03, 0x77, bus, device);
Nikhil Potaded8884f12019-03-27 13:27:13 -0700702
703 if (DEBUG)
James Feistc95cb142018-02-26 10:41:42 -0800704 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700705 std::cerr << "Done scanning bus " << bus << "\n";
James Feistc95cb142018-02-26 10:41:42 -0800706 }
James Feist3cb5fec2018-01-23 14:41:51 -0800707 }
James Feist3cb5fec2018-01-23 14:41:51 -0800708}
709
James Feist6ebf9de2018-05-15 15:01:17 -0700710// this class allows an async response after all i2c devices are discovered
711struct FindDevicesWithCallback
712 : std::enable_shared_from_this<FindDevicesWithCallback>
713{
James Feista465ccc2019-02-08 12:51:01 -0800714 FindDevicesWithCallback(const std::vector<fs::path>& i2cBuses,
James Feist5cb06082019-10-24 17:11:13 -0700715 BusMap& busmap,
James Feista465ccc2019-02-08 12:51:01 -0800716 std::function<void(void)>&& callback) :
James Feist6ebf9de2018-05-15 15:01:17 -0700717 _i2cBuses(i2cBuses),
James Feist5cb06082019-10-24 17:11:13 -0700718 _busMap(busmap), _callback(std::move(callback))
James Feist6ebf9de2018-05-15 15:01:17 -0700719 {
720 }
721 ~FindDevicesWithCallback()
722 {
723 _callback();
724 }
725 void run()
726 {
James Feist98132792019-07-09 13:29:09 -0700727 FindI2CDevices(_i2cBuses, _busMap);
James Feist6ebf9de2018-05-15 15:01:17 -0700728 }
729
James Feista465ccc2019-02-08 12:51:01 -0800730 const std::vector<fs::path>& _i2cBuses;
James Feista465ccc2019-02-08 12:51:01 -0800731 BusMap& _busMap;
James Feist6ebf9de2018-05-15 15:01:17 -0700732 std::function<void(void)> _callback;
733};
734
James Feist3cb5fec2018-01-23 14:41:51 -0800735static const std::tm intelEpoch(void)
736{
James Feist98132792019-07-09 13:29:09 -0700737 std::tm val = {};
James Feist3cb5fec2018-01-23 14:41:51 -0800738 val.tm_year = 1996 - 1900;
739 return val;
740}
741
James Feista465ccc2019-02-08 12:51:01 -0800742bool formatFru(const std::vector<char>& fruBytes,
743 boost::container::flat_map<std::string, std::string>& result)
James Feist3cb5fec2018-01-23 14:41:51 -0800744{
James Feista465ccc2019-02-08 12:51:01 -0800745 static const std::vector<const char*> CHASSIS_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800746 "PART_NUMBER", "SERIAL_NUMBER", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800747
James Feista465ccc2019-02-08 12:51:01 -0800748 static const std::vector<const char*> BOARD_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800749 "MANUFACTURER", "PRODUCT_NAME", "SERIAL_NUMBER", "PART_NUMBER",
750 "FRU_VERSION_ID", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800751
James Feista465ccc2019-02-08 12:51:01 -0800752 static const std::vector<const char*> PRODUCT_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800753 "MANUFACTURER", "PRODUCT_NAME", "PART_NUMBER",
754 "VERSION", "SERIAL_NUMBER", "ASSET_TAG",
755 "FRU_VERSION_ID", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800756
James Feistd068e932018-09-20 10:53:07 -0700757 if (fruBytes.size() <= 8)
James Feist3cb5fec2018-01-23 14:41:51 -0800758 {
759 return false;
760 }
761 std::vector<char>::const_iterator fruAreaOffsetField = fruBytes.begin();
James Feist9eb0b582018-04-27 12:15:46 -0700762 result["Common_Format_Version"] =
James Feist3cb5fec2018-01-23 14:41:51 -0800763 std::to_string(static_cast<int>(*fruAreaOffsetField));
764
James Feista465ccc2019-02-08 12:51:01 -0800765 const std::vector<const char*>* fieldData;
James Feist3cb5fec2018-01-23 14:41:51 -0800766
James Feist0eb40352019-04-09 14:44:04 -0700767 for (const std::string& area : FRU_AREAS)
James Feist3cb5fec2018-01-23 14:41:51 -0800768 {
Patrick Venture4b7a7452019-10-28 08:41:02 -0700769 ++fruAreaOffsetField;
James Feist3cb5fec2018-01-23 14:41:51 -0800770 if (fruAreaOffsetField >= fruBytes.end())
771 {
772 return false;
773 }
774 size_t offset = (*fruAreaOffsetField) * 8;
775
776 if (offset > 1)
777 {
778 // +2 to skip format and length
779 std::vector<char>::const_iterator fruBytesIter =
780 fruBytes.begin() + offset + 2;
781
782 if (fruBytesIter >= fruBytes.end())
783 {
784 return false;
785 }
786
787 if (area == "CHASSIS")
788 {
789 result["CHASSIS_TYPE"] =
790 std::to_string(static_cast<int>(*fruBytesIter));
791 fruBytesIter += 1;
792 fieldData = &CHASSIS_FRU_AREAS;
793 }
794 else if (area == "BOARD")
795 {
796 result["BOARD_LANGUAGE_CODE"] =
797 std::to_string(static_cast<int>(*fruBytesIter));
798 fruBytesIter += 1;
799 if (fruBytesIter >= fruBytes.end())
800 {
801 return false;
802 }
803
804 unsigned int minutes = *fruBytesIter |
805 *(fruBytesIter + 1) << 8 |
806 *(fruBytesIter + 2) << 16;
807 std::tm fruTime = intelEpoch();
Patrick Venturee0e6f5f2019-08-12 19:00:11 -0700808 std::time_t timeValue = std::mktime(&fruTime);
James Feist3cb5fec2018-01-23 14:41:51 -0800809 timeValue += minutes * 60;
Patrick Venturee0e6f5f2019-08-12 19:00:11 -0700810 fruTime = *std::gmtime(&timeValue);
James Feist3cb5fec2018-01-23 14:41:51 -0800811
Patrick Venturee0e6f5f2019-08-12 19:00:11 -0700812 // Tue Nov 20 23:08:00 2018
813 char timeString[32] = {0};
814 auto bytes = std::strftime(timeString, sizeof(timeString),
Patrick Venturefff050a2019-08-13 11:44:30 -0700815 "%Y-%m-%d - %H:%M:%S", &fruTime);
Patrick Venturee0e6f5f2019-08-12 19:00:11 -0700816 if (bytes == 0)
817 {
818 std::cerr << "invalid time string encountered\n";
819 return false;
820 }
821
822 result["BOARD_MANUFACTURE_DATE"] = std::string(timeString);
James Feist3cb5fec2018-01-23 14:41:51 -0800823 fruBytesIter += 3;
824 fieldData = &BOARD_FRU_AREAS;
825 }
826 else if (area == "PRODUCT")
827 {
828 result["PRODUCT_LANGUAGE_CODE"] =
829 std::to_string(static_cast<int>(*fruBytesIter));
830 fruBytesIter += 1;
831 fieldData = &PRODUCT_FRU_AREAS;
832 }
833 else
834 {
835 continue;
836 }
James Feista465ccc2019-02-08 12:51:01 -0800837 for (auto& field : *fieldData)
James Feist3cb5fec2018-01-23 14:41:51 -0800838 {
839 if (fruBytesIter >= fruBytes.end())
840 {
841 return false;
842 }
843
Vijay Khemka5d5de442018-11-07 10:51:25 -0800844 /* Checking for last byte C1 to indicate that no more
845 * field to be read */
James Feist98132792019-07-09 13:29:09 -0700846 if (static_cast<uint8_t>(*fruBytesIter) == 0xC1)
Vijay Khemka5d5de442018-11-07 10:51:25 -0800847 {
848 break;
849 }
850
Ed Tanous2147e672019-02-27 13:59:56 -0800851 size_t length = *fruBytesIter & 0x3f;
852 fruBytesIter += 1;
853
James Feist3cb5fec2018-01-23 14:41:51 -0800854 if (fruBytesIter >= fruBytes.end())
855 {
856 return false;
857 }
Ed Tanous2147e672019-02-27 13:59:56 -0800858 std::string value(fruBytesIter, fruBytesIter + length);
James Feist3cb5fec2018-01-23 14:41:51 -0800859
Ed Tanous2147e672019-02-27 13:59:56 -0800860 // Strip non null characters from the end
861 value.erase(std::find_if(value.rbegin(), value.rend(),
862 [](char ch) { return ch != 0; })
863 .base(),
864 value.end());
865
James Feist0eb40352019-04-09 14:44:04 -0700866 result[area + "_" + field] = std::move(value);
Ed Tanous2147e672019-02-27 13:59:56 -0800867
James Feist3cb5fec2018-01-23 14:41:51 -0800868 fruBytesIter += length;
869 if (fruBytesIter >= fruBytes.end())
870 {
871 std::cerr << "Warning Fru Length Mismatch:\n ";
James Feista465ccc2019-02-08 12:51:01 -0800872 for (auto& c : fruBytes)
James Feist3cb5fec2018-01-23 14:41:51 -0800873 {
874 std::cerr << c;
875 }
876 std::cerr << "\n";
877 if (DEBUG)
878 {
James Feista465ccc2019-02-08 12:51:01 -0800879 for (auto& keyPair : result)
James Feist3cb5fec2018-01-23 14:41:51 -0800880 {
881 std::cerr << keyPair.first << " : "
882 << keyPair.second << "\n";
883 }
884 }
885 return false;
886 }
887 }
888 }
889 }
890
891 return true;
892}
893
Nikhil Potaded8884f12019-03-27 13:27:13 -0700894std::vector<uint8_t>& getFruInfo(const uint8_t& bus, const uint8_t& address)
895{
896 auto deviceMap = busMap.find(bus);
897 if (deviceMap == busMap.end())
898 {
899 throw std::invalid_argument("Invalid Bus.");
900 }
901 auto device = deviceMap->second->find(address);
902 if (device == deviceMap->second->end())
903 {
904 throw std::invalid_argument("Invalid Address.");
905 }
906 std::vector<uint8_t>& ret =
907 reinterpret_cast<std::vector<uint8_t>&>(device->second);
908
909 return ret;
910}
911
James Feist3cb5fec2018-01-23 14:41:51 -0800912void AddFruObjectToDbus(
James Feist5cb06082019-10-24 17:11:13 -0700913 std::vector<char>& device,
James Feista465ccc2019-02-08 12:51:01 -0800914 boost::container::flat_map<
915 std::pair<size_t, size_t>,
916 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
James Feist13b86d62018-05-29 11:24:35 -0700917 uint32_t bus, uint32_t address)
James Feist3cb5fec2018-01-23 14:41:51 -0800918{
919 boost::container::flat_map<std::string, std::string> formattedFru;
920 if (!formatFru(device, formattedFru))
921 {
Patrick Ventureb755c832019-08-07 11:09:14 -0700922 std::cerr << "failed to format fru for device at bus " << bus
923 << " address " << address << "\n";
James Feist3cb5fec2018-01-23 14:41:51 -0800924 return;
925 }
Patrick Venture96cdaef2019-07-30 13:30:52 -0700926
James Feist3cb5fec2018-01-23 14:41:51 -0800927 auto productNameFind = formattedFru.find("BOARD_PRODUCT_NAME");
928 std::string productName;
Patrick Venture96cdaef2019-07-30 13:30:52 -0700929 // Not found under Board section or an empty string.
930 if (productNameFind == formattedFru.end() ||
931 productNameFind->second.empty())
James Feist3cb5fec2018-01-23 14:41:51 -0800932 {
933 productNameFind = formattedFru.find("PRODUCT_PRODUCT_NAME");
934 }
Patrick Venture96cdaef2019-07-30 13:30:52 -0700935 // Found under Product section and not an empty string.
936 if (productNameFind != formattedFru.end() &&
937 !productNameFind->second.empty())
James Feist3cb5fec2018-01-23 14:41:51 -0800938 {
939 productName = productNameFind->second;
James Feist3f8a2782018-02-12 09:24:42 -0800940 std::regex illegalObject("[^A-Za-z0-9_]");
941 productName = std::regex_replace(productName, illegalObject, "_");
James Feist3cb5fec2018-01-23 14:41:51 -0800942 }
943 else
944 {
945 productName = "UNKNOWN" + std::to_string(UNKNOWN_BUS_OBJECT_COUNT);
946 UNKNOWN_BUS_OBJECT_COUNT++;
947 }
948
James Feist918e18c2018-02-13 15:51:07 -0800949 productName = "/xyz/openbmc_project/FruDevice/" + productName;
James Feist918e18c2018-02-13 15:51:07 -0800950 // avoid duplicates by checking to see if on a mux
James Feist79e9c0b2018-03-15 15:21:17 -0700951 if (bus > 0)
James Feist918e18c2018-02-13 15:51:07 -0800952 {
Patrick Venture015fb0a2019-08-16 09:33:31 -0700953 int highest = -1;
954 bool found = false;
955
James Feista465ccc2019-02-08 12:51:01 -0800956 for (auto const& busIface : dbusInterfaceMap)
James Feist918e18c2018-02-13 15:51:07 -0800957 {
Patrick Venture015fb0a2019-08-16 09:33:31 -0700958 std::string path = busIface.second->get_object_path();
959 if (std::regex_match(path, std::regex(productName + "(_\\d+|)$")))
James Feist918e18c2018-02-13 15:51:07 -0800960 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700961 if (isMuxBus(bus) && address == busIface.first.second &&
James Feist98132792019-07-09 13:29:09 -0700962 (getFruInfo(static_cast<uint8_t>(busIface.first.first),
963 static_cast<uint8_t>(busIface.first.second)) ==
964 getFruInfo(static_cast<uint8_t>(bus),
965 static_cast<uint8_t>(address))))
James Feist79e9c0b2018-03-15 15:21:17 -0700966 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700967 // This device is already added to the lower numbered bus,
968 // do not replicate it.
969 return;
James Feist79e9c0b2018-03-15 15:21:17 -0700970 }
Patrick Venture015fb0a2019-08-16 09:33:31 -0700971
972 // Check if the match named has extra information.
973 found = true;
974 std::smatch base_match;
975
976 bool match = std::regex_match(
977 path, base_match, std::regex(productName + "_(\\d+)$"));
978 if (match)
James Feist79e9c0b2018-03-15 15:21:17 -0700979 {
Patrick Venture015fb0a2019-08-16 09:33:31 -0700980 if (base_match.size() == 2)
981 {
982 std::ssub_match base_sub_match = base_match[1];
983 std::string base = base_sub_match.str();
984
985 int value = std::stoi(base);
986 highest = (value > highest) ? value : highest;
987 }
James Feist79e9c0b2018-03-15 15:21:17 -0700988 }
James Feist918e18c2018-02-13 15:51:07 -0800989 }
Patrick Venture015fb0a2019-08-16 09:33:31 -0700990 } // end searching objects
991
992 if (found)
993 {
994 // We found something with the same name. If highest was still -1,
995 // it means this new entry will be _0.
996 productName += "_";
997 productName += std::to_string(++highest);
James Feist918e18c2018-02-13 15:51:07 -0800998 }
999 }
James Feist3cb5fec2018-01-23 14:41:51 -08001000
James Feist9eb0b582018-04-27 12:15:46 -07001001 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
1002 objServer.add_interface(productName, "xyz.openbmc_project.FruDevice");
1003 dbusInterfaceMap[std::pair<size_t, size_t>(bus, address)] = iface;
1004
James Feista465ccc2019-02-08 12:51:01 -08001005 for (auto& property : formattedFru)
James Feist3cb5fec2018-01-23 14:41:51 -08001006 {
James Feist9eb0b582018-04-27 12:15:46 -07001007
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -07001008 std::regex_replace(property.second.begin(), property.second.begin(),
1009 property.second.end(), NON_ASCII_REGEX, "_");
James Feist9eb0b582018-04-27 12:15:46 -07001010 if (property.second.empty())
1011 {
1012 continue;
1013 }
1014 std::string key =
1015 std::regex_replace(property.first, NON_ASCII_REGEX, "_");
1016 if (!iface->register_property(key, property.second + '\0'))
1017 {
1018 std::cerr << "illegal key: " << key << "\n";
1019 }
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -07001020 if (DEBUG)
1021 {
1022 std::cout << property.first << ": " << property.second << "\n";
1023 }
James Feist3cb5fec2018-01-23 14:41:51 -08001024 }
James Feist2a9d6db2018-04-27 15:48:28 -07001025
1026 // baseboard will be 0, 0
James Feist13b86d62018-05-29 11:24:35 -07001027 iface->register_property("BUS", bus);
1028 iface->register_property("ADDRESS", address);
James Feist2a9d6db2018-04-27 15:48:28 -07001029
James Feist9eb0b582018-04-27 12:15:46 -07001030 iface->initialize();
James Feist3cb5fec2018-01-23 14:41:51 -08001031}
1032
James Feista465ccc2019-02-08 12:51:01 -08001033static bool readBaseboardFru(std::vector<char>& baseboardFru)
James Feist3cb5fec2018-01-23 14:41:51 -08001034{
1035 // try to read baseboard fru from file
1036 std::ifstream baseboardFruFile(BASEBOARD_FRU_LOCATION, std::ios::binary);
1037 if (baseboardFruFile.good())
1038 {
1039 baseboardFruFile.seekg(0, std::ios_base::end);
James Feist98132792019-07-09 13:29:09 -07001040 size_t fileSize = static_cast<size_t>(baseboardFruFile.tellg());
James Feist3cb5fec2018-01-23 14:41:51 -08001041 baseboardFru.resize(fileSize);
1042 baseboardFruFile.seekg(0, std::ios_base::beg);
1043 baseboardFruFile.read(baseboardFru.data(), fileSize);
1044 }
1045 else
1046 {
1047 return false;
1048 }
1049 return true;
1050}
1051
James Feista465ccc2019-02-08 12:51:01 -08001052bool writeFru(uint8_t bus, uint8_t address, const std::vector<uint8_t>& fru)
James Feistb49ffc32018-05-02 11:10:43 -07001053{
1054 boost::container::flat_map<std::string, std::string> tmp;
1055 if (fru.size() > MAX_FRU_SIZE)
1056 {
1057 std::cerr << "Invalid fru.size() during writeFru\n";
1058 return false;
1059 }
1060 // verify legal fru by running it through fru parsing logic
James Feista465ccc2019-02-08 12:51:01 -08001061 if (!formatFru(reinterpret_cast<const std::vector<char>&>(fru), tmp))
James Feistb49ffc32018-05-02 11:10:43 -07001062 {
1063 std::cerr << "Invalid fru format during writeFru\n";
1064 return false;
1065 }
1066 // baseboard fru
1067 if (bus == 0 && address == 0)
1068 {
1069 std::ofstream file(BASEBOARD_FRU_LOCATION, std::ios_base::binary);
1070 if (!file.good())
1071 {
1072 std::cerr << "Error opening file " << BASEBOARD_FRU_LOCATION
1073 << "\n";
James Feistddb78302018-09-06 11:45:42 -07001074 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001075 return false;
1076 }
James Feista465ccc2019-02-08 12:51:01 -08001077 file.write(reinterpret_cast<const char*>(fru.data()), fru.size());
James Feistb49ffc32018-05-02 11:10:43 -07001078 return file.good();
1079 }
1080 else
1081 {
Patrick Venturec50e1ff2019-08-06 10:22:28 -07001082 if (hasEepromFile(bus, address))
1083 {
1084 auto path = getEepromPath(bus, address);
1085 int eeprom = open(path.c_str(), O_RDWR | O_CLOEXEC);
1086 if (eeprom < 0)
1087 {
1088 std::cerr << "unable to open i2c device " << path << "\n";
1089 throw DBusInternalError();
1090 return false;
1091 }
1092
1093 ssize_t writtenBytes = write(eeprom, fru.data(), fru.size());
1094 if (writtenBytes < 0)
1095 {
1096 std::cerr << "unable to write to i2c device " << path << "\n";
1097 close(eeprom);
1098 throw DBusInternalError();
1099 return false;
1100 }
1101
1102 close(eeprom);
1103 return true;
1104 }
1105
James Feistb49ffc32018-05-02 11:10:43 -07001106 std::string i2cBus = "/dev/i2c-" + std::to_string(bus);
1107
1108 int file = open(i2cBus.c_str(), O_RDWR | O_CLOEXEC);
1109 if (file < 0)
1110 {
1111 std::cerr << "unable to open i2c device " << i2cBus << "\n";
James Feistddb78302018-09-06 11:45:42 -07001112 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001113 return false;
1114 }
1115 if (ioctl(file, I2C_SLAVE_FORCE, address) < 0)
1116 {
1117 std::cerr << "unable to set device address\n";
1118 close(file);
James Feistddb78302018-09-06 11:45:42 -07001119 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001120 return false;
1121 }
1122
1123 constexpr const size_t RETRY_MAX = 2;
1124 uint16_t index = 0;
1125 size_t retries = RETRY_MAX;
1126 while (index < fru.size())
1127 {
1128 if ((index && ((index % (MAX_EEPROM_PAGE_INDEX + 1)) == 0)) &&
1129 (retries == RETRY_MAX))
1130 {
1131 // The 4K EEPROM only uses the A2 and A1 device address bits
1132 // with the third bit being a memory page address bit.
1133 if (ioctl(file, I2C_SLAVE_FORCE, ++address) < 0)
1134 {
1135 std::cerr << "unable to set device address\n";
1136 close(file);
James Feistddb78302018-09-06 11:45:42 -07001137 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001138 return false;
1139 }
1140 }
1141
James Feist98132792019-07-09 13:29:09 -07001142 if (i2c_smbus_write_byte_data(file, static_cast<uint8_t>(index),
1143 fru[index]) < 0)
James Feistb49ffc32018-05-02 11:10:43 -07001144 {
1145 if (!retries--)
1146 {
1147 std::cerr << "error writing fru: " << strerror(errno)
1148 << "\n";
1149 close(file);
James Feistddb78302018-09-06 11:45:42 -07001150 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001151 return false;
1152 }
1153 }
1154 else
1155 {
1156 retries = RETRY_MAX;
1157 index++;
1158 }
1159 // most eeproms require 5-10ms between writes
1160 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1161 }
1162 close(file);
1163 return true;
1164 }
1165}
1166
Cheng C Yangd5b87fb2020-01-23 10:10:45 +08001167void rescanOneBus(
1168 BusMap& busmap, uint8_t busNum,
1169 boost::container::flat_map<
1170 std::pair<size_t, size_t>,
1171 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap)
1172{
1173 fs::path busPath = fs::path("/dev/i2c-" + std::to_string(busNum));
1174 if (!fs::exists(busPath))
1175 {
1176 std::cerr << "Unable to access i2c bus " << static_cast<int>(busNum)
1177 << "\n";
1178 throw std::invalid_argument("Invalid Bus.");
1179 }
1180
1181 std::vector<fs::path> i2cBuses;
1182 i2cBuses.emplace_back(busPath);
1183
1184 for (auto& [pair, interface] : foundDevices)
1185 {
1186 if (pair.first == static_cast<size_t>(busNum))
1187 {
1188 objServer.remove_interface(interface);
1189 foundDevices.erase(pair);
1190 }
1191 }
1192
1193 auto scan = std::make_shared<FindDevicesWithCallback>(
1194 i2cBuses, busmap, [busNum, &busmap, &dbusInterfaceMap]() {
1195 for (auto& busIface : dbusInterfaceMap)
1196 {
1197 if (busIface.first.first == static_cast<size_t>(busNum))
1198 {
1199 objServer.remove_interface(busIface.second);
1200 }
1201 }
1202 auto& devicemap = busmap[busNum];
1203 for (auto& device : *devicemap)
1204 {
1205 AddFruObjectToDbus(device.second, dbusInterfaceMap,
1206 static_cast<uint32_t>(busNum), device.first);
1207 }
1208 });
1209 scan->run();
1210}
1211
James Feist9eb0b582018-04-27 12:15:46 -07001212void rescanBusses(
James Feist5cb06082019-10-24 17:11:13 -07001213 BusMap& busmap,
James Feista465ccc2019-02-08 12:51:01 -08001214 boost::container::flat_map<
1215 std::pair<size_t, size_t>,
James Feist5cb06082019-10-24 17:11:13 -07001216 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap)
James Feist918e18c2018-02-13 15:51:07 -08001217{
James Feist6ebf9de2018-05-15 15:01:17 -07001218 static boost::asio::deadline_timer timer(io);
1219 timer.expires_from_now(boost::posix_time::seconds(1));
James Feist918e18c2018-02-13 15:51:07 -08001220
Gunnar Mills6f0ae942018-08-31 12:38:03 -05001221 // setup an async wait in case we get flooded with requests
James Feist98132792019-07-09 13:29:09 -07001222 timer.async_wait([&](const boost::system::error_code&) {
James Feist4131aea2018-03-09 09:47:30 -08001223 auto devDir = fs::path("/dev/");
James Feist4131aea2018-03-09 09:47:30 -08001224 std::vector<fs::path> i2cBuses;
James Feist918e18c2018-02-13 15:51:07 -08001225
Nikhil Potaded8884f12019-03-27 13:27:13 -07001226 boost::container::flat_map<size_t, fs::path> busPaths;
1227 if (!getI2cDevicePaths(devDir, busPaths))
James Feist918e18c2018-02-13 15:51:07 -08001228 {
James Feist4131aea2018-03-09 09:47:30 -08001229 std::cerr << "unable to find i2c devices\n";
1230 return;
James Feist918e18c2018-02-13 15:51:07 -08001231 }
Nikhil Potaded8884f12019-03-27 13:27:13 -07001232
1233 for (auto busPath : busPaths)
1234 {
1235 i2cBuses.emplace_back(busPath.second);
1236 }
James Feist4131aea2018-03-09 09:47:30 -08001237
James Feist98132792019-07-09 13:29:09 -07001238 busmap.clear();
James Feist8a983922019-10-24 17:11:56 -07001239 for (auto& [pair, interface] : foundDevices)
1240 {
1241 objServer.remove_interface(interface);
1242 }
1243 foundDevices.clear();
1244
James Feist5cb06082019-10-24 17:11:13 -07001245 auto scan =
1246 std::make_shared<FindDevicesWithCallback>(i2cBuses, busmap, [&]() {
James Feista465ccc2019-02-08 12:51:01 -08001247 for (auto& busIface : dbusInterfaceMap)
James Feist6ebf9de2018-05-15 15:01:17 -07001248 {
1249 objServer.remove_interface(busIface.second);
1250 }
James Feist4131aea2018-03-09 09:47:30 -08001251
James Feist6ebf9de2018-05-15 15:01:17 -07001252 dbusInterfaceMap.clear();
1253 UNKNOWN_BUS_OBJECT_COUNT = 0;
James Feist4131aea2018-03-09 09:47:30 -08001254
James Feist6ebf9de2018-05-15 15:01:17 -07001255 // todo, get this from a more sensable place
1256 std::vector<char> baseboardFru;
1257 if (readBaseboardFru(baseboardFru))
1258 {
1259 boost::container::flat_map<int, std::vector<char>>
1260 baseboardDev;
1261 baseboardDev.emplace(0, baseboardFru);
James Feist98132792019-07-09 13:29:09 -07001262 busmap[0] = std::make_shared<DeviceMap>(baseboardDev);
James Feist6ebf9de2018-05-15 15:01:17 -07001263 }
James Feist98132792019-07-09 13:29:09 -07001264 for (auto& devicemap : busmap)
James Feist6ebf9de2018-05-15 15:01:17 -07001265 {
James Feista465ccc2019-02-08 12:51:01 -08001266 for (auto& device : *devicemap.second)
James Feist6ebf9de2018-05-15 15:01:17 -07001267 {
James Feist5cb06082019-10-24 17:11:13 -07001268 AddFruObjectToDbus(device.second, dbusInterfaceMap,
1269 devicemap.first, device.first);
James Feist6ebf9de2018-05-15 15:01:17 -07001270 }
1271 }
1272 });
1273 scan->run();
1274 });
James Feist918e18c2018-02-13 15:51:07 -08001275}
1276
James Feist98132792019-07-09 13:29:09 -07001277int main()
James Feist3cb5fec2018-01-23 14:41:51 -08001278{
1279 auto devDir = fs::path("/dev/");
James Feistc9dff1b2019-02-13 13:33:13 -08001280 auto matchString = std::string(R"(i2c-\d+$)");
James Feist3cb5fec2018-01-23 14:41:51 -08001281 std::vector<fs::path> i2cBuses;
1282
James Feista3c180a2018-08-09 16:06:04 -07001283 if (!findFiles(devDir, matchString, i2cBuses))
James Feist3cb5fec2018-01-23 14:41:51 -08001284 {
1285 std::cerr << "unable to find i2c devices\n";
1286 return 1;
1287 }
James Feist3cb5fec2018-01-23 14:41:51 -08001288
Patrick Venture11f1ff42019-08-01 10:42:12 -07001289 // check for and load blacklist with initial buses.
1290 loadBlacklist(blacklistPath);
1291
Vijay Khemka065f6d92018-12-18 10:37:47 -08001292 systemBus->request_name("xyz.openbmc_project.FruDevice");
James Feist3cb5fec2018-01-23 14:41:51 -08001293
James Feist6ebf9de2018-05-15 15:01:17 -07001294 // this is a map with keys of pair(bus number, address) and values of
1295 // the object on dbus
James Feist3cb5fec2018-01-23 14:41:51 -08001296 boost::container::flat_map<std::pair<size_t, size_t>,
James Feist9eb0b582018-04-27 12:15:46 -07001297 std::shared_ptr<sdbusplus::asio::dbus_interface>>
1298 dbusInterfaceMap;
James Feist3cb5fec2018-01-23 14:41:51 -08001299
James Feist9eb0b582018-04-27 12:15:46 -07001300 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
1301 objServer.add_interface("/xyz/openbmc_project/FruDevice",
1302 "xyz.openbmc_project.FruDeviceManager");
James Feist3cb5fec2018-01-23 14:41:51 -08001303
James Feist5cb06082019-10-24 17:11:13 -07001304 iface->register_method("ReScan",
1305 [&]() { rescanBusses(busMap, dbusInterfaceMap); });
James Feist2a9d6db2018-04-27 15:48:28 -07001306
Cheng C Yangd5b87fb2020-01-23 10:10:45 +08001307 iface->register_method("ReScanBus", [&](uint8_t bus) {
1308 rescanOneBus(busMap, bus, dbusInterfaceMap);
1309 });
1310
Nikhil Potaded8884f12019-03-27 13:27:13 -07001311 iface->register_method("GetRawFru", getFruInfo);
James Feistb49ffc32018-05-02 11:10:43 -07001312
1313 iface->register_method("WriteFru", [&](const uint8_t bus,
1314 const uint8_t address,
James Feista465ccc2019-02-08 12:51:01 -08001315 const std::vector<uint8_t>& data) {
James Feistb49ffc32018-05-02 11:10:43 -07001316 if (!writeFru(bus, address, data))
1317 {
James Feistddb78302018-09-06 11:45:42 -07001318 throw std::invalid_argument("Invalid Arguments.");
James Feistb49ffc32018-05-02 11:10:43 -07001319 return;
1320 }
1321 // schedule rescan on success
James Feist5cb06082019-10-24 17:11:13 -07001322 rescanBusses(busMap, dbusInterfaceMap);
James Feistb49ffc32018-05-02 11:10:43 -07001323 });
James Feist9eb0b582018-04-27 12:15:46 -07001324 iface->initialize();
James Feist3cb5fec2018-01-23 14:41:51 -08001325
James Feist9eb0b582018-04-27 12:15:46 -07001326 std::function<void(sdbusplus::message::message & message)> eventHandler =
James Feista465ccc2019-02-08 12:51:01 -08001327 [&](sdbusplus::message::message& message) {
James Feist918e18c2018-02-13 15:51:07 -08001328 std::string objectName;
James Feist9eb0b582018-04-27 12:15:46 -07001329 boost::container::flat_map<
James Feista465ccc2019-02-08 12:51:01 -08001330 std::string,
1331 std::variant<std::string, bool, int64_t, uint64_t, double>>
James Feist9eb0b582018-04-27 12:15:46 -07001332 values;
1333 message.read(objectName, values);
James Feist0eeb79c2019-10-09 13:35:29 -07001334 auto findState = values.find("CurrentHostState");
1335 bool on = false;
1336 if (findState != values.end())
James Feist918e18c2018-02-13 15:51:07 -08001337 {
James Feist0eeb79c2019-10-09 13:35:29 -07001338 on = boost::ends_with(std::get<std::string>(findState->second),
1339 "Running");
1340 }
James Feist6ebf9de2018-05-15 15:01:17 -07001341
James Feist0eeb79c2019-10-09 13:35:29 -07001342 if (on)
1343 {
James Feist5cb06082019-10-24 17:11:13 -07001344 rescanBusses(busMap, dbusInterfaceMap);
James Feist918e18c2018-02-13 15:51:07 -08001345 }
James Feist918e18c2018-02-13 15:51:07 -08001346 };
James Feist9eb0b582018-04-27 12:15:46 -07001347
1348 sdbusplus::bus::match::match powerMatch = sdbusplus::bus::match::match(
James Feista465ccc2019-02-08 12:51:01 -08001349 static_cast<sdbusplus::bus::bus&>(*systemBus),
James Feist7bcd3f22019-03-18 16:04:04 -07001350 "type='signal',interface='org.freedesktop.DBus.Properties',path='/xyz/"
James Feist0eeb79c2019-10-09 13:35:29 -07001351 "openbmc_project/state/"
1352 "host0',arg0='xyz.openbmc_project.State.Host'",
James Feist9eb0b582018-04-27 12:15:46 -07001353 eventHandler);
1354
James Feist4131aea2018-03-09 09:47:30 -08001355 int fd = inotify_init();
James Feist0eb40352019-04-09 14:44:04 -07001356 inotify_add_watch(fd, I2C_DEV_LOCATION,
1357 IN_CREATE | IN_MOVED_TO | IN_DELETE);
James Feist4131aea2018-03-09 09:47:30 -08001358 std::array<char, 4096> readBuffer;
1359 std::string pendingBuffer;
1360 // monitor for new i2c devices
1361 boost::asio::posix::stream_descriptor dirWatch(io, fd);
1362 std::function<void(const boost::system::error_code, std::size_t)>
James Feista465ccc2019-02-08 12:51:01 -08001363 watchI2cBusses = [&](const boost::system::error_code& ec,
James Feist4131aea2018-03-09 09:47:30 -08001364 std::size_t bytes_transferred) {
1365 if (ec)
1366 {
1367 std::cout << "Callback Error " << ec << "\n";
1368 return;
1369 }
1370 pendingBuffer += std::string(readBuffer.data(), bytes_transferred);
1371 bool devChange = false;
1372 while (pendingBuffer.size() > sizeof(inotify_event))
1373 {
James Feista465ccc2019-02-08 12:51:01 -08001374 const inotify_event* iEvent =
1375 reinterpret_cast<const inotify_event*>(
James Feist4131aea2018-03-09 09:47:30 -08001376 pendingBuffer.data());
1377 switch (iEvent->mask)
1378 {
James Feist9eb0b582018-04-27 12:15:46 -07001379 case IN_CREATE:
1380 case IN_MOVED_TO:
1381 case IN_DELETE:
1382 if (boost::starts_with(std::string(iEvent->name),
1383 "i2c"))
1384 {
1385 devChange = true;
1386 }
James Feist4131aea2018-03-09 09:47:30 -08001387 }
1388
1389 pendingBuffer.erase(0, sizeof(inotify_event) + iEvent->len);
1390 }
James Feist6ebf9de2018-05-15 15:01:17 -07001391 if (devChange)
James Feist4131aea2018-03-09 09:47:30 -08001392 {
James Feist5cb06082019-10-24 17:11:13 -07001393 rescanBusses(busMap, dbusInterfaceMap);
James Feist4131aea2018-03-09 09:47:30 -08001394 }
James Feist6ebf9de2018-05-15 15:01:17 -07001395
James Feist4131aea2018-03-09 09:47:30 -08001396 dirWatch.async_read_some(boost::asio::buffer(readBuffer),
1397 watchI2cBusses);
1398 };
1399
1400 dirWatch.async_read_some(boost::asio::buffer(readBuffer), watchI2cBusses);
Gunnar Millsb3e42fe2018-06-13 15:48:27 -05001401 // run the initial scan
James Feist5cb06082019-10-24 17:11:13 -07001402 rescanBusses(busMap, dbusInterfaceMap);
James Feist918e18c2018-02-13 15:51:07 -08001403
James Feist3cb5fec2018-01-23 14:41:51 -08001404 io.run();
1405 return 0;
1406}