blob: 42ca3113f95fd7fe3c15a9531d9e760897d87bfe [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 }
653 auto bus = std::stoi(busnum);
James Feist444830e2019-04-05 08:38:16 -0700654 if (busBlacklist.find(bus) != busBlacklist.end())
655 {
656 continue; // skip previously failed busses
657 }
James Feist3cb5fec2018-01-23 14:41:51 -0800658
659 auto file = open(i2cBus.c_str(), O_RDWR);
660 if (file < 0)
661 {
662 std::cerr << "unable to open i2c device " << i2cBus.string()
663 << "\n";
664 continue;
665 }
666 unsigned long funcs = 0;
667
668 if (ioctl(file, I2C_FUNCS, &funcs) < 0)
669 {
670 std::cerr
Patrick Venture98e0cf32019-08-02 11:11:03 -0700671 << "Error: Could not get the adapter functionality matrix bus "
James Feist3cb5fec2018-01-23 14:41:51 -0800672 << bus << "\n";
673 continue;
674 }
675 if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE) ||
676 !(I2C_FUNC_SMBUS_READ_I2C_BLOCK))
677 {
678 std::cerr << "Error: Can't use SMBus Receive Byte command bus "
679 << bus << "\n";
680 continue;
681 }
James Feist98132792019-07-09 13:29:09 -0700682 auto& device = busmap[bus];
James Feist3cb5fec2018-01-23 14:41:51 -0800683 device = std::make_shared<DeviceMap>();
684
Nikhil Potaded8884f12019-03-27 13:27:13 -0700685 // i2cdetect by default uses the range 0x03 to 0x77, as
686 // this is what we have tested with, use this range. Could be
687 // changed in future.
688 if (DEBUG)
James Feistc95cb142018-02-26 10:41:42 -0800689 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700690 std::cerr << "Scanning bus " << bus << "\n";
James Feistc95cb142018-02-26 10:41:42 -0800691 }
Nikhil Potaded8884f12019-03-27 13:27:13 -0700692
693 // fd is closed in this function in case the bus locks up
Patrick Venture4f47fe62019-08-08 16:30:38 -0700694 getBusFrus(file, 0x03, 0x77, bus, device);
Nikhil Potaded8884f12019-03-27 13:27:13 -0700695
696 if (DEBUG)
James Feistc95cb142018-02-26 10:41:42 -0800697 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700698 std::cerr << "Done scanning bus " << bus << "\n";
James Feistc95cb142018-02-26 10:41:42 -0800699 }
James Feist3cb5fec2018-01-23 14:41:51 -0800700 }
James Feist3cb5fec2018-01-23 14:41:51 -0800701}
702
James Feist6ebf9de2018-05-15 15:01:17 -0700703// this class allows an async response after all i2c devices are discovered
704struct FindDevicesWithCallback
705 : std::enable_shared_from_this<FindDevicesWithCallback>
706{
James Feista465ccc2019-02-08 12:51:01 -0800707 FindDevicesWithCallback(const std::vector<fs::path>& i2cBuses,
James Feist5cb06082019-10-24 17:11:13 -0700708 BusMap& busmap,
James Feista465ccc2019-02-08 12:51:01 -0800709 std::function<void(void)>&& callback) :
James Feist6ebf9de2018-05-15 15:01:17 -0700710 _i2cBuses(i2cBuses),
James Feist5cb06082019-10-24 17:11:13 -0700711 _busMap(busmap), _callback(std::move(callback))
James Feist6ebf9de2018-05-15 15:01:17 -0700712 {
713 }
714 ~FindDevicesWithCallback()
715 {
716 _callback();
717 }
718 void run()
719 {
James Feist98132792019-07-09 13:29:09 -0700720 FindI2CDevices(_i2cBuses, _busMap);
James Feist6ebf9de2018-05-15 15:01:17 -0700721 }
722
James Feista465ccc2019-02-08 12:51:01 -0800723 const std::vector<fs::path>& _i2cBuses;
James Feista465ccc2019-02-08 12:51:01 -0800724 BusMap& _busMap;
James Feist6ebf9de2018-05-15 15:01:17 -0700725 std::function<void(void)> _callback;
726};
727
James Feist3cb5fec2018-01-23 14:41:51 -0800728static const std::tm intelEpoch(void)
729{
James Feist98132792019-07-09 13:29:09 -0700730 std::tm val = {};
James Feist3cb5fec2018-01-23 14:41:51 -0800731 val.tm_year = 1996 - 1900;
732 return val;
733}
734
James Feista465ccc2019-02-08 12:51:01 -0800735bool formatFru(const std::vector<char>& fruBytes,
736 boost::container::flat_map<std::string, std::string>& result)
James Feist3cb5fec2018-01-23 14:41:51 -0800737{
James Feista465ccc2019-02-08 12:51:01 -0800738 static const std::vector<const char*> CHASSIS_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800739 "PART_NUMBER", "SERIAL_NUMBER", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800740
James Feista465ccc2019-02-08 12:51:01 -0800741 static const std::vector<const char*> BOARD_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800742 "MANUFACTURER", "PRODUCT_NAME", "SERIAL_NUMBER", "PART_NUMBER",
743 "FRU_VERSION_ID", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800744
James Feista465ccc2019-02-08 12:51:01 -0800745 static const std::vector<const char*> PRODUCT_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800746 "MANUFACTURER", "PRODUCT_NAME", "PART_NUMBER",
747 "VERSION", "SERIAL_NUMBER", "ASSET_TAG",
748 "FRU_VERSION_ID", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800749
James Feistd068e932018-09-20 10:53:07 -0700750 if (fruBytes.size() <= 8)
James Feist3cb5fec2018-01-23 14:41:51 -0800751 {
752 return false;
753 }
754 std::vector<char>::const_iterator fruAreaOffsetField = fruBytes.begin();
James Feist9eb0b582018-04-27 12:15:46 -0700755 result["Common_Format_Version"] =
James Feist3cb5fec2018-01-23 14:41:51 -0800756 std::to_string(static_cast<int>(*fruAreaOffsetField));
757
James Feista465ccc2019-02-08 12:51:01 -0800758 const std::vector<const char*>* fieldData;
James Feist3cb5fec2018-01-23 14:41:51 -0800759
James Feist0eb40352019-04-09 14:44:04 -0700760 for (const std::string& area : FRU_AREAS)
James Feist3cb5fec2018-01-23 14:41:51 -0800761 {
Patrick Venture4b7a7452019-10-28 08:41:02 -0700762 ++fruAreaOffsetField;
James Feist3cb5fec2018-01-23 14:41:51 -0800763 if (fruAreaOffsetField >= fruBytes.end())
764 {
765 return false;
766 }
767 size_t offset = (*fruAreaOffsetField) * 8;
768
769 if (offset > 1)
770 {
771 // +2 to skip format and length
772 std::vector<char>::const_iterator fruBytesIter =
773 fruBytes.begin() + offset + 2;
774
775 if (fruBytesIter >= fruBytes.end())
776 {
777 return false;
778 }
779
780 if (area == "CHASSIS")
781 {
782 result["CHASSIS_TYPE"] =
783 std::to_string(static_cast<int>(*fruBytesIter));
784 fruBytesIter += 1;
785 fieldData = &CHASSIS_FRU_AREAS;
786 }
787 else if (area == "BOARD")
788 {
789 result["BOARD_LANGUAGE_CODE"] =
790 std::to_string(static_cast<int>(*fruBytesIter));
791 fruBytesIter += 1;
792 if (fruBytesIter >= fruBytes.end())
793 {
794 return false;
795 }
796
797 unsigned int minutes = *fruBytesIter |
798 *(fruBytesIter + 1) << 8 |
799 *(fruBytesIter + 2) << 16;
800 std::tm fruTime = intelEpoch();
Patrick Venturee0e6f5f2019-08-12 19:00:11 -0700801 std::time_t timeValue = std::mktime(&fruTime);
James Feist3cb5fec2018-01-23 14:41:51 -0800802 timeValue += minutes * 60;
Patrick Venturee0e6f5f2019-08-12 19:00:11 -0700803 fruTime = *std::gmtime(&timeValue);
James Feist3cb5fec2018-01-23 14:41:51 -0800804
Patrick Venturee0e6f5f2019-08-12 19:00:11 -0700805 // Tue Nov 20 23:08:00 2018
806 char timeString[32] = {0};
807 auto bytes = std::strftime(timeString, sizeof(timeString),
Patrick Venturefff050a2019-08-13 11:44:30 -0700808 "%Y-%m-%d - %H:%M:%S", &fruTime);
Patrick Venturee0e6f5f2019-08-12 19:00:11 -0700809 if (bytes == 0)
810 {
811 std::cerr << "invalid time string encountered\n";
812 return false;
813 }
814
815 result["BOARD_MANUFACTURE_DATE"] = std::string(timeString);
James Feist3cb5fec2018-01-23 14:41:51 -0800816 fruBytesIter += 3;
817 fieldData = &BOARD_FRU_AREAS;
818 }
819 else if (area == "PRODUCT")
820 {
821 result["PRODUCT_LANGUAGE_CODE"] =
822 std::to_string(static_cast<int>(*fruBytesIter));
823 fruBytesIter += 1;
824 fieldData = &PRODUCT_FRU_AREAS;
825 }
826 else
827 {
828 continue;
829 }
James Feista465ccc2019-02-08 12:51:01 -0800830 for (auto& field : *fieldData)
James Feist3cb5fec2018-01-23 14:41:51 -0800831 {
832 if (fruBytesIter >= fruBytes.end())
833 {
834 return false;
835 }
836
Vijay Khemka5d5de442018-11-07 10:51:25 -0800837 /* Checking for last byte C1 to indicate that no more
838 * field to be read */
James Feist98132792019-07-09 13:29:09 -0700839 if (static_cast<uint8_t>(*fruBytesIter) == 0xC1)
Vijay Khemka5d5de442018-11-07 10:51:25 -0800840 {
841 break;
842 }
843
Ed Tanous2147e672019-02-27 13:59:56 -0800844 size_t length = *fruBytesIter & 0x3f;
845 fruBytesIter += 1;
846
James Feist3cb5fec2018-01-23 14:41:51 -0800847 if (fruBytesIter >= fruBytes.end())
848 {
849 return false;
850 }
Ed Tanous2147e672019-02-27 13:59:56 -0800851 std::string value(fruBytesIter, fruBytesIter + length);
James Feist3cb5fec2018-01-23 14:41:51 -0800852
Ed Tanous2147e672019-02-27 13:59:56 -0800853 // Strip non null characters from the end
854 value.erase(std::find_if(value.rbegin(), value.rend(),
855 [](char ch) { return ch != 0; })
856 .base(),
857 value.end());
858
James Feist0eb40352019-04-09 14:44:04 -0700859 result[area + "_" + field] = std::move(value);
Ed Tanous2147e672019-02-27 13:59:56 -0800860
James Feist3cb5fec2018-01-23 14:41:51 -0800861 fruBytesIter += length;
862 if (fruBytesIter >= fruBytes.end())
863 {
864 std::cerr << "Warning Fru Length Mismatch:\n ";
James Feista465ccc2019-02-08 12:51:01 -0800865 for (auto& c : fruBytes)
James Feist3cb5fec2018-01-23 14:41:51 -0800866 {
867 std::cerr << c;
868 }
869 std::cerr << "\n";
870 if (DEBUG)
871 {
James Feista465ccc2019-02-08 12:51:01 -0800872 for (auto& keyPair : result)
James Feist3cb5fec2018-01-23 14:41:51 -0800873 {
874 std::cerr << keyPair.first << " : "
875 << keyPair.second << "\n";
876 }
877 }
878 return false;
879 }
880 }
881 }
882 }
883
884 return true;
885}
886
Nikhil Potaded8884f12019-03-27 13:27:13 -0700887std::vector<uint8_t>& getFruInfo(const uint8_t& bus, const uint8_t& address)
888{
889 auto deviceMap = busMap.find(bus);
890 if (deviceMap == busMap.end())
891 {
892 throw std::invalid_argument("Invalid Bus.");
893 }
894 auto device = deviceMap->second->find(address);
895 if (device == deviceMap->second->end())
896 {
897 throw std::invalid_argument("Invalid Address.");
898 }
899 std::vector<uint8_t>& ret =
900 reinterpret_cast<std::vector<uint8_t>&>(device->second);
901
902 return ret;
903}
904
James Feist3cb5fec2018-01-23 14:41:51 -0800905void AddFruObjectToDbus(
James Feist5cb06082019-10-24 17:11:13 -0700906 std::vector<char>& device,
James Feista465ccc2019-02-08 12:51:01 -0800907 boost::container::flat_map<
908 std::pair<size_t, size_t>,
909 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
James Feist13b86d62018-05-29 11:24:35 -0700910 uint32_t bus, uint32_t address)
James Feist3cb5fec2018-01-23 14:41:51 -0800911{
912 boost::container::flat_map<std::string, std::string> formattedFru;
913 if (!formatFru(device, formattedFru))
914 {
Patrick Ventureb755c832019-08-07 11:09:14 -0700915 std::cerr << "failed to format fru for device at bus " << bus
916 << " address " << address << "\n";
James Feist3cb5fec2018-01-23 14:41:51 -0800917 return;
918 }
Patrick Venture96cdaef2019-07-30 13:30:52 -0700919
James Feist3cb5fec2018-01-23 14:41:51 -0800920 auto productNameFind = formattedFru.find("BOARD_PRODUCT_NAME");
921 std::string productName;
Patrick Venture96cdaef2019-07-30 13:30:52 -0700922 // Not found under Board section or an empty string.
923 if (productNameFind == formattedFru.end() ||
924 productNameFind->second.empty())
James Feist3cb5fec2018-01-23 14:41:51 -0800925 {
926 productNameFind = formattedFru.find("PRODUCT_PRODUCT_NAME");
927 }
Patrick Venture96cdaef2019-07-30 13:30:52 -0700928 // Found under Product section and not an empty string.
929 if (productNameFind != formattedFru.end() &&
930 !productNameFind->second.empty())
James Feist3cb5fec2018-01-23 14:41:51 -0800931 {
932 productName = productNameFind->second;
James Feist3f8a2782018-02-12 09:24:42 -0800933 std::regex illegalObject("[^A-Za-z0-9_]");
934 productName = std::regex_replace(productName, illegalObject, "_");
James Feist3cb5fec2018-01-23 14:41:51 -0800935 }
936 else
937 {
938 productName = "UNKNOWN" + std::to_string(UNKNOWN_BUS_OBJECT_COUNT);
939 UNKNOWN_BUS_OBJECT_COUNT++;
940 }
941
James Feist918e18c2018-02-13 15:51:07 -0800942 productName = "/xyz/openbmc_project/FruDevice/" + productName;
James Feist918e18c2018-02-13 15:51:07 -0800943 // avoid duplicates by checking to see if on a mux
James Feist79e9c0b2018-03-15 15:21:17 -0700944 if (bus > 0)
James Feist918e18c2018-02-13 15:51:07 -0800945 {
Patrick Venture015fb0a2019-08-16 09:33:31 -0700946 int highest = -1;
947 bool found = false;
948
James Feista465ccc2019-02-08 12:51:01 -0800949 for (auto const& busIface : dbusInterfaceMap)
James Feist918e18c2018-02-13 15:51:07 -0800950 {
Patrick Venture015fb0a2019-08-16 09:33:31 -0700951 std::string path = busIface.second->get_object_path();
952 if (std::regex_match(path, std::regex(productName + "(_\\d+|)$")))
James Feist918e18c2018-02-13 15:51:07 -0800953 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700954 if (isMuxBus(bus) && address == busIface.first.second &&
James Feist98132792019-07-09 13:29:09 -0700955 (getFruInfo(static_cast<uint8_t>(busIface.first.first),
956 static_cast<uint8_t>(busIface.first.second)) ==
957 getFruInfo(static_cast<uint8_t>(bus),
958 static_cast<uint8_t>(address))))
James Feist79e9c0b2018-03-15 15:21:17 -0700959 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700960 // This device is already added to the lower numbered bus,
961 // do not replicate it.
962 return;
James Feist79e9c0b2018-03-15 15:21:17 -0700963 }
Patrick Venture015fb0a2019-08-16 09:33:31 -0700964
965 // Check if the match named has extra information.
966 found = true;
967 std::smatch base_match;
968
969 bool match = std::regex_match(
970 path, base_match, std::regex(productName + "_(\\d+)$"));
971 if (match)
James Feist79e9c0b2018-03-15 15:21:17 -0700972 {
Patrick Venture015fb0a2019-08-16 09:33:31 -0700973 if (base_match.size() == 2)
974 {
975 std::ssub_match base_sub_match = base_match[1];
976 std::string base = base_sub_match.str();
977
978 int value = std::stoi(base);
979 highest = (value > highest) ? value : highest;
980 }
James Feist79e9c0b2018-03-15 15:21:17 -0700981 }
James Feist918e18c2018-02-13 15:51:07 -0800982 }
Patrick Venture015fb0a2019-08-16 09:33:31 -0700983 } // end searching objects
984
985 if (found)
986 {
987 // We found something with the same name. If highest was still -1,
988 // it means this new entry will be _0.
989 productName += "_";
990 productName += std::to_string(++highest);
James Feist918e18c2018-02-13 15:51:07 -0800991 }
992 }
James Feist3cb5fec2018-01-23 14:41:51 -0800993
James Feist9eb0b582018-04-27 12:15:46 -0700994 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
995 objServer.add_interface(productName, "xyz.openbmc_project.FruDevice");
996 dbusInterfaceMap[std::pair<size_t, size_t>(bus, address)] = iface;
997
James Feista465ccc2019-02-08 12:51:01 -0800998 for (auto& property : formattedFru)
James Feist3cb5fec2018-01-23 14:41:51 -0800999 {
James Feist9eb0b582018-04-27 12:15:46 -07001000
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -07001001 std::regex_replace(property.second.begin(), property.second.begin(),
1002 property.second.end(), NON_ASCII_REGEX, "_");
James Feist9eb0b582018-04-27 12:15:46 -07001003 if (property.second.empty())
1004 {
1005 continue;
1006 }
1007 std::string key =
1008 std::regex_replace(property.first, NON_ASCII_REGEX, "_");
1009 if (!iface->register_property(key, property.second + '\0'))
1010 {
1011 std::cerr << "illegal key: " << key << "\n";
1012 }
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -07001013 if (DEBUG)
1014 {
1015 std::cout << property.first << ": " << property.second << "\n";
1016 }
James Feist3cb5fec2018-01-23 14:41:51 -08001017 }
James Feist2a9d6db2018-04-27 15:48:28 -07001018
1019 // baseboard will be 0, 0
James Feist13b86d62018-05-29 11:24:35 -07001020 iface->register_property("BUS", bus);
1021 iface->register_property("ADDRESS", address);
James Feist2a9d6db2018-04-27 15:48:28 -07001022
James Feist9eb0b582018-04-27 12:15:46 -07001023 iface->initialize();
James Feist3cb5fec2018-01-23 14:41:51 -08001024}
1025
James Feista465ccc2019-02-08 12:51:01 -08001026static bool readBaseboardFru(std::vector<char>& baseboardFru)
James Feist3cb5fec2018-01-23 14:41:51 -08001027{
1028 // try to read baseboard fru from file
1029 std::ifstream baseboardFruFile(BASEBOARD_FRU_LOCATION, std::ios::binary);
1030 if (baseboardFruFile.good())
1031 {
1032 baseboardFruFile.seekg(0, std::ios_base::end);
James Feist98132792019-07-09 13:29:09 -07001033 size_t fileSize = static_cast<size_t>(baseboardFruFile.tellg());
James Feist3cb5fec2018-01-23 14:41:51 -08001034 baseboardFru.resize(fileSize);
1035 baseboardFruFile.seekg(0, std::ios_base::beg);
1036 baseboardFruFile.read(baseboardFru.data(), fileSize);
1037 }
1038 else
1039 {
1040 return false;
1041 }
1042 return true;
1043}
1044
James Feista465ccc2019-02-08 12:51:01 -08001045bool writeFru(uint8_t bus, uint8_t address, const std::vector<uint8_t>& fru)
James Feistb49ffc32018-05-02 11:10:43 -07001046{
1047 boost::container::flat_map<std::string, std::string> tmp;
1048 if (fru.size() > MAX_FRU_SIZE)
1049 {
1050 std::cerr << "Invalid fru.size() during writeFru\n";
1051 return false;
1052 }
1053 // verify legal fru by running it through fru parsing logic
James Feista465ccc2019-02-08 12:51:01 -08001054 if (!formatFru(reinterpret_cast<const std::vector<char>&>(fru), tmp))
James Feistb49ffc32018-05-02 11:10:43 -07001055 {
1056 std::cerr << "Invalid fru format during writeFru\n";
1057 return false;
1058 }
1059 // baseboard fru
1060 if (bus == 0 && address == 0)
1061 {
1062 std::ofstream file(BASEBOARD_FRU_LOCATION, std::ios_base::binary);
1063 if (!file.good())
1064 {
1065 std::cerr << "Error opening file " << BASEBOARD_FRU_LOCATION
1066 << "\n";
James Feistddb78302018-09-06 11:45:42 -07001067 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001068 return false;
1069 }
James Feista465ccc2019-02-08 12:51:01 -08001070 file.write(reinterpret_cast<const char*>(fru.data()), fru.size());
James Feistb49ffc32018-05-02 11:10:43 -07001071 return file.good();
1072 }
1073 else
1074 {
Patrick Venturec50e1ff2019-08-06 10:22:28 -07001075 if (hasEepromFile(bus, address))
1076 {
1077 auto path = getEepromPath(bus, address);
1078 int eeprom = open(path.c_str(), O_RDWR | O_CLOEXEC);
1079 if (eeprom < 0)
1080 {
1081 std::cerr << "unable to open i2c device " << path << "\n";
1082 throw DBusInternalError();
1083 return false;
1084 }
1085
1086 ssize_t writtenBytes = write(eeprom, fru.data(), fru.size());
1087 if (writtenBytes < 0)
1088 {
1089 std::cerr << "unable to write to i2c device " << path << "\n";
1090 close(eeprom);
1091 throw DBusInternalError();
1092 return false;
1093 }
1094
1095 close(eeprom);
1096 return true;
1097 }
1098
James Feistb49ffc32018-05-02 11:10:43 -07001099 std::string i2cBus = "/dev/i2c-" + std::to_string(bus);
1100
1101 int file = open(i2cBus.c_str(), O_RDWR | O_CLOEXEC);
1102 if (file < 0)
1103 {
1104 std::cerr << "unable to open i2c device " << i2cBus << "\n";
James Feistddb78302018-09-06 11:45:42 -07001105 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001106 return false;
1107 }
1108 if (ioctl(file, I2C_SLAVE_FORCE, address) < 0)
1109 {
1110 std::cerr << "unable to set device address\n";
1111 close(file);
James Feistddb78302018-09-06 11:45:42 -07001112 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001113 return false;
1114 }
1115
1116 constexpr const size_t RETRY_MAX = 2;
1117 uint16_t index = 0;
1118 size_t retries = RETRY_MAX;
1119 while (index < fru.size())
1120 {
1121 if ((index && ((index % (MAX_EEPROM_PAGE_INDEX + 1)) == 0)) &&
1122 (retries == RETRY_MAX))
1123 {
1124 // The 4K EEPROM only uses the A2 and A1 device address bits
1125 // with the third bit being a memory page address bit.
1126 if (ioctl(file, I2C_SLAVE_FORCE, ++address) < 0)
1127 {
1128 std::cerr << "unable to set device address\n";
1129 close(file);
James Feistddb78302018-09-06 11:45:42 -07001130 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001131 return false;
1132 }
1133 }
1134
James Feist98132792019-07-09 13:29:09 -07001135 if (i2c_smbus_write_byte_data(file, static_cast<uint8_t>(index),
1136 fru[index]) < 0)
James Feistb49ffc32018-05-02 11:10:43 -07001137 {
1138 if (!retries--)
1139 {
1140 std::cerr << "error writing fru: " << strerror(errno)
1141 << "\n";
1142 close(file);
James Feistddb78302018-09-06 11:45:42 -07001143 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001144 return false;
1145 }
1146 }
1147 else
1148 {
1149 retries = RETRY_MAX;
1150 index++;
1151 }
1152 // most eeproms require 5-10ms between writes
1153 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1154 }
1155 close(file);
1156 return true;
1157 }
1158}
1159
James Feist9eb0b582018-04-27 12:15:46 -07001160void rescanBusses(
James Feist5cb06082019-10-24 17:11:13 -07001161 BusMap& busmap,
James Feista465ccc2019-02-08 12:51:01 -08001162 boost::container::flat_map<
1163 std::pair<size_t, size_t>,
James Feist5cb06082019-10-24 17:11:13 -07001164 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap)
James Feist918e18c2018-02-13 15:51:07 -08001165{
James Feist6ebf9de2018-05-15 15:01:17 -07001166 static boost::asio::deadline_timer timer(io);
1167 timer.expires_from_now(boost::posix_time::seconds(1));
James Feist918e18c2018-02-13 15:51:07 -08001168
Gunnar Mills6f0ae942018-08-31 12:38:03 -05001169 // setup an async wait in case we get flooded with requests
James Feist98132792019-07-09 13:29:09 -07001170 timer.async_wait([&](const boost::system::error_code&) {
James Feist4131aea2018-03-09 09:47:30 -08001171 auto devDir = fs::path("/dev/");
James Feist4131aea2018-03-09 09:47:30 -08001172 std::vector<fs::path> i2cBuses;
James Feist918e18c2018-02-13 15:51:07 -08001173
Nikhil Potaded8884f12019-03-27 13:27:13 -07001174 boost::container::flat_map<size_t, fs::path> busPaths;
1175 if (!getI2cDevicePaths(devDir, busPaths))
James Feist918e18c2018-02-13 15:51:07 -08001176 {
James Feist4131aea2018-03-09 09:47:30 -08001177 std::cerr << "unable to find i2c devices\n";
1178 return;
James Feist918e18c2018-02-13 15:51:07 -08001179 }
Nikhil Potaded8884f12019-03-27 13:27:13 -07001180
1181 for (auto busPath : busPaths)
1182 {
1183 i2cBuses.emplace_back(busPath.second);
1184 }
James Feist4131aea2018-03-09 09:47:30 -08001185
James Feist98132792019-07-09 13:29:09 -07001186 busmap.clear();
James Feist8a983922019-10-24 17:11:56 -07001187 for (auto& [pair, interface] : foundDevices)
1188 {
1189 objServer.remove_interface(interface);
1190 }
1191 foundDevices.clear();
1192
James Feist5cb06082019-10-24 17:11:13 -07001193 auto scan =
1194 std::make_shared<FindDevicesWithCallback>(i2cBuses, busmap, [&]() {
James Feista465ccc2019-02-08 12:51:01 -08001195 for (auto& busIface : dbusInterfaceMap)
James Feist6ebf9de2018-05-15 15:01:17 -07001196 {
1197 objServer.remove_interface(busIface.second);
1198 }
James Feist4131aea2018-03-09 09:47:30 -08001199
James Feist6ebf9de2018-05-15 15:01:17 -07001200 dbusInterfaceMap.clear();
1201 UNKNOWN_BUS_OBJECT_COUNT = 0;
James Feist4131aea2018-03-09 09:47:30 -08001202
James Feist6ebf9de2018-05-15 15:01:17 -07001203 // todo, get this from a more sensable place
1204 std::vector<char> baseboardFru;
1205 if (readBaseboardFru(baseboardFru))
1206 {
1207 boost::container::flat_map<int, std::vector<char>>
1208 baseboardDev;
1209 baseboardDev.emplace(0, baseboardFru);
James Feist98132792019-07-09 13:29:09 -07001210 busmap[0] = std::make_shared<DeviceMap>(baseboardDev);
James Feist6ebf9de2018-05-15 15:01:17 -07001211 }
James Feist98132792019-07-09 13:29:09 -07001212 for (auto& devicemap : busmap)
James Feist6ebf9de2018-05-15 15:01:17 -07001213 {
James Feista465ccc2019-02-08 12:51:01 -08001214 for (auto& device : *devicemap.second)
James Feist6ebf9de2018-05-15 15:01:17 -07001215 {
James Feist5cb06082019-10-24 17:11:13 -07001216 AddFruObjectToDbus(device.second, dbusInterfaceMap,
1217 devicemap.first, device.first);
James Feist6ebf9de2018-05-15 15:01:17 -07001218 }
1219 }
1220 });
1221 scan->run();
1222 });
James Feist918e18c2018-02-13 15:51:07 -08001223}
1224
James Feist98132792019-07-09 13:29:09 -07001225int main()
James Feist3cb5fec2018-01-23 14:41:51 -08001226{
1227 auto devDir = fs::path("/dev/");
James Feistc9dff1b2019-02-13 13:33:13 -08001228 auto matchString = std::string(R"(i2c-\d+$)");
James Feist3cb5fec2018-01-23 14:41:51 -08001229 std::vector<fs::path> i2cBuses;
1230
James Feista3c180a2018-08-09 16:06:04 -07001231 if (!findFiles(devDir, matchString, i2cBuses))
James Feist3cb5fec2018-01-23 14:41:51 -08001232 {
1233 std::cerr << "unable to find i2c devices\n";
1234 return 1;
1235 }
James Feist3cb5fec2018-01-23 14:41:51 -08001236
Patrick Venture11f1ff42019-08-01 10:42:12 -07001237 // check for and load blacklist with initial buses.
1238 loadBlacklist(blacklistPath);
1239
Vijay Khemka065f6d92018-12-18 10:37:47 -08001240 systemBus->request_name("xyz.openbmc_project.FruDevice");
James Feist3cb5fec2018-01-23 14:41:51 -08001241
James Feist6ebf9de2018-05-15 15:01:17 -07001242 // this is a map with keys of pair(bus number, address) and values of
1243 // the object on dbus
James Feist3cb5fec2018-01-23 14:41:51 -08001244 boost::container::flat_map<std::pair<size_t, size_t>,
James Feist9eb0b582018-04-27 12:15:46 -07001245 std::shared_ptr<sdbusplus::asio::dbus_interface>>
1246 dbusInterfaceMap;
James Feist3cb5fec2018-01-23 14:41:51 -08001247
James Feist9eb0b582018-04-27 12:15:46 -07001248 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
1249 objServer.add_interface("/xyz/openbmc_project/FruDevice",
1250 "xyz.openbmc_project.FruDeviceManager");
James Feist3cb5fec2018-01-23 14:41:51 -08001251
James Feist5cb06082019-10-24 17:11:13 -07001252 iface->register_method("ReScan",
1253 [&]() { rescanBusses(busMap, dbusInterfaceMap); });
James Feist2a9d6db2018-04-27 15:48:28 -07001254
Nikhil Potaded8884f12019-03-27 13:27:13 -07001255 iface->register_method("GetRawFru", getFruInfo);
James Feistb49ffc32018-05-02 11:10:43 -07001256
1257 iface->register_method("WriteFru", [&](const uint8_t bus,
1258 const uint8_t address,
James Feista465ccc2019-02-08 12:51:01 -08001259 const std::vector<uint8_t>& data) {
James Feistb49ffc32018-05-02 11:10:43 -07001260 if (!writeFru(bus, address, data))
1261 {
James Feistddb78302018-09-06 11:45:42 -07001262 throw std::invalid_argument("Invalid Arguments.");
James Feistb49ffc32018-05-02 11:10:43 -07001263 return;
1264 }
1265 // schedule rescan on success
James Feist5cb06082019-10-24 17:11:13 -07001266 rescanBusses(busMap, dbusInterfaceMap);
James Feistb49ffc32018-05-02 11:10:43 -07001267 });
James Feist9eb0b582018-04-27 12:15:46 -07001268 iface->initialize();
James Feist3cb5fec2018-01-23 14:41:51 -08001269
James Feist9eb0b582018-04-27 12:15:46 -07001270 std::function<void(sdbusplus::message::message & message)> eventHandler =
James Feista465ccc2019-02-08 12:51:01 -08001271 [&](sdbusplus::message::message& message) {
James Feist918e18c2018-02-13 15:51:07 -08001272 std::string objectName;
James Feist9eb0b582018-04-27 12:15:46 -07001273 boost::container::flat_map<
James Feista465ccc2019-02-08 12:51:01 -08001274 std::string,
1275 std::variant<std::string, bool, int64_t, uint64_t, double>>
James Feist9eb0b582018-04-27 12:15:46 -07001276 values;
1277 message.read(objectName, values);
James Feist0eeb79c2019-10-09 13:35:29 -07001278 auto findState = values.find("CurrentHostState");
1279 bool on = false;
1280 if (findState != values.end())
James Feist918e18c2018-02-13 15:51:07 -08001281 {
James Feist0eeb79c2019-10-09 13:35:29 -07001282 on = boost::ends_with(std::get<std::string>(findState->second),
1283 "Running");
1284 }
James Feist6ebf9de2018-05-15 15:01:17 -07001285
James Feist0eeb79c2019-10-09 13:35:29 -07001286 if (on)
1287 {
James Feist5cb06082019-10-24 17:11:13 -07001288 rescanBusses(busMap, dbusInterfaceMap);
James Feist918e18c2018-02-13 15:51:07 -08001289 }
James Feist918e18c2018-02-13 15:51:07 -08001290 };
James Feist9eb0b582018-04-27 12:15:46 -07001291
1292 sdbusplus::bus::match::match powerMatch = sdbusplus::bus::match::match(
James Feista465ccc2019-02-08 12:51:01 -08001293 static_cast<sdbusplus::bus::bus&>(*systemBus),
James Feist7bcd3f22019-03-18 16:04:04 -07001294 "type='signal',interface='org.freedesktop.DBus.Properties',path='/xyz/"
James Feist0eeb79c2019-10-09 13:35:29 -07001295 "openbmc_project/state/"
1296 "host0',arg0='xyz.openbmc_project.State.Host'",
James Feist9eb0b582018-04-27 12:15:46 -07001297 eventHandler);
1298
James Feist4131aea2018-03-09 09:47:30 -08001299 int fd = inotify_init();
James Feist0eb40352019-04-09 14:44:04 -07001300 inotify_add_watch(fd, I2C_DEV_LOCATION,
1301 IN_CREATE | IN_MOVED_TO | IN_DELETE);
James Feist4131aea2018-03-09 09:47:30 -08001302 std::array<char, 4096> readBuffer;
1303 std::string pendingBuffer;
1304 // monitor for new i2c devices
1305 boost::asio::posix::stream_descriptor dirWatch(io, fd);
1306 std::function<void(const boost::system::error_code, std::size_t)>
James Feista465ccc2019-02-08 12:51:01 -08001307 watchI2cBusses = [&](const boost::system::error_code& ec,
James Feist4131aea2018-03-09 09:47:30 -08001308 std::size_t bytes_transferred) {
1309 if (ec)
1310 {
1311 std::cout << "Callback Error " << ec << "\n";
1312 return;
1313 }
1314 pendingBuffer += std::string(readBuffer.data(), bytes_transferred);
1315 bool devChange = false;
1316 while (pendingBuffer.size() > sizeof(inotify_event))
1317 {
James Feista465ccc2019-02-08 12:51:01 -08001318 const inotify_event* iEvent =
1319 reinterpret_cast<const inotify_event*>(
James Feist4131aea2018-03-09 09:47:30 -08001320 pendingBuffer.data());
1321 switch (iEvent->mask)
1322 {
James Feist9eb0b582018-04-27 12:15:46 -07001323 case IN_CREATE:
1324 case IN_MOVED_TO:
1325 case IN_DELETE:
1326 if (boost::starts_with(std::string(iEvent->name),
1327 "i2c"))
1328 {
1329 devChange = true;
1330 }
James Feist4131aea2018-03-09 09:47:30 -08001331 }
1332
1333 pendingBuffer.erase(0, sizeof(inotify_event) + iEvent->len);
1334 }
James Feist6ebf9de2018-05-15 15:01:17 -07001335 if (devChange)
James Feist4131aea2018-03-09 09:47:30 -08001336 {
James Feist5cb06082019-10-24 17:11:13 -07001337 rescanBusses(busMap, dbusInterfaceMap);
James Feist4131aea2018-03-09 09:47:30 -08001338 }
James Feist6ebf9de2018-05-15 15:01:17 -07001339
James Feist4131aea2018-03-09 09:47:30 -08001340 dirWatch.async_read_some(boost::asio::buffer(readBuffer),
1341 watchI2cBusses);
1342 };
1343
1344 dirWatch.async_read_some(boost::asio::buffer(readBuffer), watchI2cBusses);
Gunnar Millsb3e42fe2018-06-13 15:48:27 -05001345 // run the initial scan
James Feist5cb06082019-10-24 17:11:13 -07001346 rescanBusses(busMap, dbusInterfaceMap);
James Feist918e18c2018-02-13 15:51:07 -08001347
James Feist3cb5fec2018-01-23 14:41:51 -08001348 io.run();
1349 return 0;
1350}