blob: 48cec11ae5a15c24d8665299f3a4e352b0599fc9 [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 Feist5cb06082019-10-24 17:11:13 -070081boost::asio::io_service io;
82auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
83auto objServer = sdbusplus::asio::object_server(systemBus);
84
Patrick Venturebaba7672019-10-26 09:26:41 -070085bool validateHeader(const std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData);
86
87using ReadBlockFunc = std::function<int64_t(int flag, int file, uint16_t offset,
88 uint8_t length, uint8_t* outBuf)>;
89
90// Read and validate FRU contents, given the flag required for raw i2c, the open
91// file handle, a read method, and a helper string for failures.
92std::vector<char> readFruContents(int flag, int file, ReadBlockFunc readBlock,
93 const std::string& errorHelp)
94{
95 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData;
96
97 if (readBlock(flag, file, 0x0, 0x8, blockData.data()) < 0)
98 {
99 std::cerr << "failed to read " << errorHelp << "\n";
100 return {};
101 }
102
103 // check the header checksum
104 if (!validateHeader(blockData))
105 {
106 if (DEBUG)
107 {
108 std::cerr << "Illegal header " << errorHelp << "\n";
109 }
110
111 return {};
112 }
113
114 std::vector<char> device;
115 device.insert(device.end(), blockData.begin(), blockData.begin() + 8);
116
Patrick Venturee26395d2019-10-29 14:05:16 -0700117 bool hasMultiRecords = false;
Patrick Venturebaba7672019-10-26 09:26:41 -0700118 int fruLength = 0;
119 for (size_t jj = 1; jj <= FRU_AREAS.size(); jj++)
120 {
Patrick Venturef2ad76b2019-10-30 08:49:27 -0700121 // Offset value can be 255.
122 int areaOffset = static_cast<uint8_t>(device[jj]);
Patrick Venturebaba7672019-10-26 09:26:41 -0700123 if (areaOffset == 0)
124 {
125 continue;
126 }
127
Patrick Venturee26395d2019-10-29 14:05:16 -0700128 // MultiRecords are different. jj is not tracking section, it's walking
129 // the common header.
130 if (std::string(FRU_AREAS[jj - 1]) == std::string("MULTIRECORD"))
131 {
132 hasMultiRecords = true;
133 break;
134 }
135
Patrick Venturebaba7672019-10-26 09:26:41 -0700136 areaOffset *= 8;
137
138 if (readBlock(flag, file, static_cast<uint16_t>(areaOffset), 0x2,
139 blockData.data()) < 0)
140 {
141 std::cerr << "failed to read " << errorHelp << "\n";
142 return {};
143 }
144
Patrick Venturef2ad76b2019-10-30 08:49:27 -0700145 // Ignore data type (blockData is already unsigned).
Patrick Venturebaba7672019-10-26 09:26:41 -0700146 int length = blockData[1] * 8;
147 areaOffset += length;
148 fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
149 }
150
Patrick Venturee26395d2019-10-29 14:05:16 -0700151 if (hasMultiRecords)
152 {
153 // device[area count] is the index to the last area because the 0th
154 // entry is not an offset in the common header.
Patrick Venturef2ad76b2019-10-30 08:49:27 -0700155 int areaOffset = static_cast<uint8_t>(device[FRU_AREAS.size()]);
Patrick Venturee26395d2019-10-29 14:05:16 -0700156 areaOffset *= 8;
157
158 // the multi-area record header is 5 bytes long.
159 constexpr int multiRecordHeaderSize = 5;
160 constexpr int multiRecordEndOfList = 0x80;
161
162 // Sanity hard-limit to 64KB.
163 while (areaOffset < std::numeric_limits<uint16_t>::max())
164 {
165 // In multi-area, the area offset points to the 0th record, each
166 // record has 3 bytes of the header we care about.
167 if (readBlock(flag, file, static_cast<uint16_t>(areaOffset), 0x3,
168 blockData.data()) < 0)
169 {
170 std::cerr << "failed to read " << errorHelp << "\n";
171 return {};
172 }
173
174 // Ok, let's check the record length, which is in bytes (unsigned,
175 // up to 255, so blockData should hold uint8_t not char)
176 int recordLength = blockData[2];
177 areaOffset += (recordLength + multiRecordHeaderSize);
178 fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
179
180 // If this is the end of the list bail.
181 if ((blockData[1] & multiRecordEndOfList))
182 {
183 break;
184 }
185 }
186 }
187
Patrick Venturebaba7672019-10-26 09:26:41 -0700188 // You already copied these first 8 bytes (the ipmi fru header size)
189 fruLength -= 8;
190
191 int readOffset = 8;
192
193 while (fruLength > 0)
194 {
195 int requestLength = std::min(I2C_SMBUS_BLOCK_MAX, fruLength);
196
197 if (readBlock(flag, file, static_cast<uint16_t>(readOffset),
198 static_cast<uint8_t>(requestLength),
199 blockData.data()) < 0)
200 {
201 std::cerr << "failed to read " << errorHelp << "\n";
202 return {};
203 }
204
205 device.insert(device.end(), blockData.begin(),
206 blockData.begin() + requestLength);
207
208 readOffset += requestLength;
209 fruLength -= requestLength;
210 }
211
212 return device;
213}
214
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700215// Given a bus/address, produce the path in sysfs for an eeprom.
216static std::string getEepromPath(size_t bus, size_t address)
217{
218 std::stringstream output;
219 output << "/sys/bus/i2c/devices/" << bus << "-" << std::right
220 << std::setfill('0') << std::setw(4) << std::hex << address
221 << "/eeprom";
222 return output.str();
223}
224
225static bool hasEepromFile(size_t bus, size_t address)
226{
227 auto path = getEepromPath(bus, address);
228 try
229 {
230 return fs::exists(path);
231 }
232 catch (...)
233 {
234 return false;
235 }
236}
237
Patrick Venture3ac8e4f2019-10-28 13:07:21 -0700238static int64_t readFromEeprom(int flag __attribute__((unused)), int fd,
239 uint16_t offset, uint8_t len, uint8_t* buf)
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700240{
241 auto result = lseek(fd, offset, SEEK_SET);
242 if (result < 0)
243 {
244 std::cerr << "failed to seek\n";
245 return -1;
246 }
247
Patrick Venture3ac8e4f2019-10-28 13:07:21 -0700248 return read(fd, buf, len);
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700249}
250
James Feistc95cb142018-02-26 10:41:42 -0800251static bool isMuxBus(size_t bus)
252{
Ed Tanous072e25d2018-12-16 21:45:20 -0800253 return is_symlink(std::filesystem::path(
James Feistc95cb142018-02-26 10:41:42 -0800254 "/sys/bus/i2c/devices/i2c-" + std::to_string(bus) + "/mux_device"));
255}
256
James Feist8a983922019-10-24 17:11:56 -0700257static void makeProbeInterface(size_t bus, size_t address)
258{
259 if (isMuxBus(bus))
260 {
261 return; // the mux buses are random, no need to publish
262 }
263 auto [it, success] = foundDevices.emplace(
264 std::make_pair(bus, address),
265 objServer.add_interface(
266 "/xyz/openbmc_project/FruDevice/" + std::to_string(bus) + "_" +
267 std::to_string(address),
268 "xyz.openbmc_project.Inventory.Item.I2CDevice"));
269 if (!success)
270 {
271 return; // already added
272 }
273 it->second->register_property("Bus", bus);
274 it->second->register_property("Address", address);
275 it->second->initialize();
276}
277
Vijay Khemka2d681f62018-11-06 15:51:00 -0800278static int isDevice16Bit(int file)
279{
280 /* Get first byte */
281 int byte1 = i2c_smbus_read_byte_data(file, 0);
282 if (byte1 < 0)
283 {
284 return byte1;
285 }
286 /* Read 7 more bytes, it will read same first byte in case of
287 * 8 bit but it will read next byte in case of 16 bit
288 */
289 for (int i = 0; i < 7; i++)
290 {
291 int byte2 = i2c_smbus_read_byte_data(file, 0);
292 if (byte2 < 0)
293 {
294 return byte2;
295 }
296 if (byte2 != byte1)
297 {
298 return 1;
299 }
300 }
301 return 0;
302}
303
Patrick Venture3ac8e4f2019-10-28 13:07:21 -0700304static int64_t readBlockData(int flag, int file, uint16_t offset, uint8_t len,
305 uint8_t* buf)
Vijay Khemka2d681f62018-11-06 15:51:00 -0800306{
Patrick Venture4f47fe62019-08-08 16:30:38 -0700307 uint8_t lowAddr = static_cast<uint8_t>(offset);
308 uint8_t highAddr = static_cast<uint8_t>(offset >> 8);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800309
310 if (flag == 0)
311 {
Patrick Venture4f47fe62019-08-08 16:30:38 -0700312 return i2c_smbus_read_i2c_block_data(file, lowAddr, len, buf);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800313 }
314
315 /* This is for 16 bit addressing EEPROM device. First an offset
316 * needs to be written before read data from a offset
317 */
Patrick Venture4f47fe62019-08-08 16:30:38 -0700318 int ret = i2c_smbus_write_byte_data(file, 0, lowAddr);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800319 if (ret < 0)
320 {
321 return ret;
322 }
323
Patrick Venture4f47fe62019-08-08 16:30:38 -0700324 return i2c_smbus_read_i2c_block_data(file, highAddr, len, buf);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800325}
326
James Feist24bae7a2019-04-03 09:50:56 -0700327bool validateHeader(const std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData)
328{
329 // ipmi spec format version number is currently at 1, verify it
330 if (blockData[0] != 0x1)
331 {
332 return false;
333 }
334
335 // verify pad is set to 0
336 if (blockData[6] != 0x0)
337 {
338 return false;
339 }
340
341 // verify offsets are 0, or don't point to another offset
342 std::set<uint8_t> foundOffsets;
343 for (int ii = 1; ii < 6; ii++)
344 {
345 if (blockData[ii] == 0)
346 {
347 continue;
348 }
James Feist0eb40352019-04-09 14:44:04 -0700349 auto inserted = foundOffsets.insert(blockData[ii]);
350 if (!inserted.second)
James Feist24bae7a2019-04-03 09:50:56 -0700351 {
352 return false;
353 }
354 }
355
356 // validate checksum
357 size_t sum = 0;
358 for (int jj = 0; jj < 7; jj++)
359 {
360 sum += blockData[jj];
361 }
362 sum = (256 - sum) & 0xFF;
363
364 if (sum != blockData[7])
365 {
366 return false;
367 }
368 return true;
369}
370
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700371// TODO: This code is very similar to the non-eeprom version and can be merged
372// with some tweaks.
373static std::vector<char> processEeprom(int bus, int address)
374{
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700375 auto path = getEepromPath(bus, address);
376
377 int file = open(path.c_str(), O_RDONLY);
378 if (file < 0)
379 {
380 std::cerr << "Unable to open eeprom file: " << path << "\n";
Patrick Venturebaba7672019-10-26 09:26:41 -0700381 return {};
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700382 }
383
Patrick Venturebaba7672019-10-26 09:26:41 -0700384 std::string errorMessage = "eeprom at " + std::to_string(bus) +
385 " address " + std::to_string(address);
386 std::vector<char> device =
387 readFruContents(0, file, readFromEeprom, errorMessage);
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700388
389 close(file);
390 return device;
391}
392
393std::set<int> findI2CEeproms(int i2cBus, std::shared_ptr<DeviceMap> devices)
394{
395 std::set<int> foundList;
396
397 std::string path = "/sys/bus/i2c/devices/i2c-" + std::to_string(i2cBus);
398
399 // For each file listed under the i2c device
400 // NOTE: This should be faster than just checking for each possible address
401 // path.
402 for (const auto& p : fs::directory_iterator(path))
403 {
404 const std::string node = p.path().string();
405 std::smatch m;
406 bool found =
407 std::regex_match(node, m, std::regex(".+\\d+-([0-9abcdef]+$)"));
408
409 if (!found)
410 {
411 continue;
412 }
413 if (m.size() != 2)
414 {
415 std::cerr << "regex didn't capture\n";
416 continue;
417 }
418
419 std::ssub_match subMatch = m[1];
420 std::string addressString = subMatch.str();
421
422 std::size_t ignored;
423 const int hexBase = 16;
424 int address = std::stoi(addressString, &ignored, hexBase);
425
426 const std::string eeprom = node + "/eeprom";
427
428 try
429 {
430 if (!fs::exists(eeprom))
431 {
432 continue;
433 }
434 }
435 catch (...)
436 {
437 continue;
438 }
439
440 // There is an eeprom file at this address, it may have invalid
441 // contents, but we found it.
442 foundList.insert(address);
443
444 std::vector<char> device = processEeprom(i2cBus, address);
445 if (!device.empty())
446 {
447 devices->emplace(address, device);
448 }
449 }
450
451 return foundList;
452}
453
Patrick Venture4f47fe62019-08-08 16:30:38 -0700454int getBusFrus(int file, int first, int last, int bus,
455 std::shared_ptr<DeviceMap> devices)
James Feist3cb5fec2018-01-23 14:41:51 -0800456{
James Feist3cb5fec2018-01-23 14:41:51 -0800457
James Feist26c27ad2018-07-25 15:09:40 -0700458 std::future<int> future = std::async(std::launch::async, [&]() {
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700459 // NOTE: When reading the devices raw on the bus, it can interfere with
460 // the driver's ability to operate, therefore read eeproms first before
461 // scanning for devices without drivers. Several experiments were run
462 // and it was determined that if there were any devices on the bus
463 // before the eeprom was hit and read, the eeprom driver wouldn't open
464 // while the bus device was open. An experiment was not performed to see
465 // if this issue was resolved if the i2c bus device was closed, but
466 // hexdumps of the eeprom later were successful.
467
468 // Scan for i2c eeproms loaded on this bus.
469 std::set<int> skipList = findI2CEeproms(bus, devices);
470
James Feist26c27ad2018-07-25 15:09:40 -0700471 for (int ii = first; ii <= last; ii++)
James Feist3cb5fec2018-01-23 14:41:51 -0800472 {
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700473 if (skipList.find(ii) != skipList.end())
474 {
475 continue;
476 }
James Feist3cb5fec2018-01-23 14:41:51 -0800477
James Feist26c27ad2018-07-25 15:09:40 -0700478 // Set slave address
479 if (ioctl(file, I2C_SLAVE_FORCE, ii) < 0)
James Feist3cb5fec2018-01-23 14:41:51 -0800480 {
Patrick Venture98e0cf32019-08-02 11:11:03 -0700481 std::cerr << "device at bus " << bus << " register " << ii
Patrick Venture5d8b61d2019-08-06 12:36:10 -0700482 << " busy\n";
James Feist26c27ad2018-07-25 15:09:40 -0700483 continue;
484 }
485 // probe
486 else if (i2c_smbus_read_byte(file) < 0)
487 {
488 continue;
489 }
James Feist3cb5fec2018-01-23 14:41:51 -0800490
James Feist26c27ad2018-07-25 15:09:40 -0700491 if (DEBUG)
492 {
Patrick Venture98e0cf32019-08-02 11:11:03 -0700493 std::cout << "something at bus " << bus << " addr " << ii
James Feist26c27ad2018-07-25 15:09:40 -0700494 << "\n";
495 }
Vijay Khemka2d681f62018-11-06 15:51:00 -0800496
James Feist8a983922019-10-24 17:11:56 -0700497 makeProbeInterface(bus, ii);
498
Vijay Khemka2d681f62018-11-06 15:51:00 -0800499 /* Check for Device type if it is 8 bit or 16 bit */
500 int flag = isDevice16Bit(file);
501 if (flag < 0)
502 {
503 std::cerr << "failed to read bus " << bus << " address " << ii
504 << "\n";
505 continue;
506 }
507
Patrick Venturebaba7672019-10-26 09:26:41 -0700508 std::string errorMessage =
509 "bus " + std::to_string(bus) + " address " + std::to_string(ii);
510 std::vector<char> device =
511 readFruContents(flag, file, readBlockData, errorMessage);
512 if (device.empty())
James Feist26c27ad2018-07-25 15:09:40 -0700513 {
James Feist26c27ad2018-07-25 15:09:40 -0700514 continue;
515 }
James Feist26c27ad2018-07-25 15:09:40 -0700516
Patrick Venture786f1792019-08-05 16:33:44 -0700517 devices->emplace(ii, device);
James Feist3cb5fec2018-01-23 14:41:51 -0800518 }
James Feist26c27ad2018-07-25 15:09:40 -0700519 return 1;
520 });
521 std::future_status status =
522 future.wait_for(std::chrono::seconds(busTimeoutSeconds));
523 if (status == std::future_status::timeout)
524 {
525 std::cerr << "Error reading bus " << bus << "\n";
James Feist444830e2019-04-05 08:38:16 -0700526 busBlacklist.insert(bus);
527 close(file);
James Feist26c27ad2018-07-25 15:09:40 -0700528 return -1;
James Feist3cb5fec2018-01-23 14:41:51 -0800529 }
530
James Feist444830e2019-04-05 08:38:16 -0700531 close(file);
James Feist26c27ad2018-07-25 15:09:40 -0700532 return future.get();
James Feist3cb5fec2018-01-23 14:41:51 -0800533}
534
Patrick Venture11f1ff42019-08-01 10:42:12 -0700535void loadBlacklist(const char* path)
536{
537 std::ifstream blacklistStream(path);
538 if (!blacklistStream.good())
539 {
540 // File is optional.
541 std::cerr << "Cannot open blacklist file.\n\n";
542 return;
543 }
544
545 nlohmann::json data =
546 nlohmann::json::parse(blacklistStream, nullptr, false);
547 if (data.is_discarded())
548 {
549 std::cerr << "Illegal blacklist file detected, cannot validate JSON, "
550 "exiting\n";
551 std::exit(EXIT_FAILURE);
Patrick Venture11f1ff42019-08-01 10:42:12 -0700552 }
553
554 // It's expected to have at least one field, "buses" that is an array of the
555 // buses by integer. Allow for future options to exclude further aspects,
556 // such as specific addresses or ranges.
557 if (data.type() != nlohmann::json::value_t::object)
558 {
559 std::cerr << "Illegal blacklist, expected to read dictionary\n";
560 std::exit(EXIT_FAILURE);
Patrick Venture11f1ff42019-08-01 10:42:12 -0700561 }
562
563 // If buses field is missing, that's fine.
564 if (data.count("buses") == 1)
565 {
566 // Parse the buses array after a little validation.
567 auto buses = data.at("buses");
568 if (buses.type() != nlohmann::json::value_t::array)
569 {
570 // Buses field present but invalid, therefore this is an error.
571 std::cerr << "Invalid contents for blacklist buses field\n";
572 std::exit(EXIT_FAILURE);
Patrick Venture11f1ff42019-08-01 10:42:12 -0700573 }
574
575 // Catch exception here for type mis-match.
576 try
577 {
578 for (const auto& bus : buses)
579 {
580 busBlacklist.insert(bus.get<size_t>());
581 }
582 }
583 catch (const nlohmann::detail::type_error& e)
584 {
585 // Type mis-match is a critical error.
586 std::cerr << "Invalid bus type: " << e.what() << "\n";
587 std::exit(EXIT_FAILURE);
Patrick Venture11f1ff42019-08-01 10:42:12 -0700588 }
589 }
590
591 return;
592}
593
James Feista465ccc2019-02-08 12:51:01 -0800594static void FindI2CDevices(const std::vector<fs::path>& i2cBuses,
James Feist98132792019-07-09 13:29:09 -0700595 BusMap& busmap)
James Feist3cb5fec2018-01-23 14:41:51 -0800596{
James Feista465ccc2019-02-08 12:51:01 -0800597 for (auto& i2cBus : i2cBuses)
James Feist3cb5fec2018-01-23 14:41:51 -0800598 {
599 auto busnum = i2cBus.string();
600 auto lastDash = busnum.rfind(std::string("-"));
601 // delete everything before dash inclusive
602 if (lastDash != std::string::npos)
603 {
604 busnum.erase(0, lastDash + 1);
605 }
606 auto bus = std::stoi(busnum);
James Feist444830e2019-04-05 08:38:16 -0700607 if (busBlacklist.find(bus) != busBlacklist.end())
608 {
609 continue; // skip previously failed busses
610 }
James Feist3cb5fec2018-01-23 14:41:51 -0800611
612 auto file = open(i2cBus.c_str(), O_RDWR);
613 if (file < 0)
614 {
615 std::cerr << "unable to open i2c device " << i2cBus.string()
616 << "\n";
617 continue;
618 }
619 unsigned long funcs = 0;
620
621 if (ioctl(file, I2C_FUNCS, &funcs) < 0)
622 {
623 std::cerr
Patrick Venture98e0cf32019-08-02 11:11:03 -0700624 << "Error: Could not get the adapter functionality matrix bus "
James Feist3cb5fec2018-01-23 14:41:51 -0800625 << bus << "\n";
626 continue;
627 }
628 if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE) ||
629 !(I2C_FUNC_SMBUS_READ_I2C_BLOCK))
630 {
631 std::cerr << "Error: Can't use SMBus Receive Byte command bus "
632 << bus << "\n";
633 continue;
634 }
James Feist98132792019-07-09 13:29:09 -0700635 auto& device = busmap[bus];
James Feist3cb5fec2018-01-23 14:41:51 -0800636 device = std::make_shared<DeviceMap>();
637
Nikhil Potaded8884f12019-03-27 13:27:13 -0700638 // i2cdetect by default uses the range 0x03 to 0x77, as
639 // this is what we have tested with, use this range. Could be
640 // changed in future.
641 if (DEBUG)
James Feistc95cb142018-02-26 10:41:42 -0800642 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700643 std::cerr << "Scanning bus " << bus << "\n";
James Feistc95cb142018-02-26 10:41:42 -0800644 }
Nikhil Potaded8884f12019-03-27 13:27:13 -0700645
646 // fd is closed in this function in case the bus locks up
Patrick Venture4f47fe62019-08-08 16:30:38 -0700647 getBusFrus(file, 0x03, 0x77, bus, device);
Nikhil Potaded8884f12019-03-27 13:27:13 -0700648
649 if (DEBUG)
James Feistc95cb142018-02-26 10:41:42 -0800650 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700651 std::cerr << "Done scanning bus " << bus << "\n";
James Feistc95cb142018-02-26 10:41:42 -0800652 }
James Feist3cb5fec2018-01-23 14:41:51 -0800653 }
James Feist3cb5fec2018-01-23 14:41:51 -0800654}
655
James Feist6ebf9de2018-05-15 15:01:17 -0700656// this class allows an async response after all i2c devices are discovered
657struct FindDevicesWithCallback
658 : std::enable_shared_from_this<FindDevicesWithCallback>
659{
James Feista465ccc2019-02-08 12:51:01 -0800660 FindDevicesWithCallback(const std::vector<fs::path>& i2cBuses,
James Feist5cb06082019-10-24 17:11:13 -0700661 BusMap& busmap,
James Feista465ccc2019-02-08 12:51:01 -0800662 std::function<void(void)>&& callback) :
James Feist6ebf9de2018-05-15 15:01:17 -0700663 _i2cBuses(i2cBuses),
James Feist5cb06082019-10-24 17:11:13 -0700664 _busMap(busmap), _callback(std::move(callback))
James Feist6ebf9de2018-05-15 15:01:17 -0700665 {
666 }
667 ~FindDevicesWithCallback()
668 {
669 _callback();
670 }
671 void run()
672 {
James Feist98132792019-07-09 13:29:09 -0700673 FindI2CDevices(_i2cBuses, _busMap);
James Feist6ebf9de2018-05-15 15:01:17 -0700674 }
675
James Feista465ccc2019-02-08 12:51:01 -0800676 const std::vector<fs::path>& _i2cBuses;
James Feista465ccc2019-02-08 12:51:01 -0800677 BusMap& _busMap;
James Feist6ebf9de2018-05-15 15:01:17 -0700678 std::function<void(void)> _callback;
679};
680
James Feist3cb5fec2018-01-23 14:41:51 -0800681static const std::tm intelEpoch(void)
682{
James Feist98132792019-07-09 13:29:09 -0700683 std::tm val = {};
James Feist3cb5fec2018-01-23 14:41:51 -0800684 val.tm_year = 1996 - 1900;
685 return val;
686}
687
James Feista465ccc2019-02-08 12:51:01 -0800688bool formatFru(const std::vector<char>& fruBytes,
689 boost::container::flat_map<std::string, std::string>& result)
James Feist3cb5fec2018-01-23 14:41:51 -0800690{
James Feista465ccc2019-02-08 12:51:01 -0800691 static const std::vector<const char*> CHASSIS_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800692 "PART_NUMBER", "SERIAL_NUMBER", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800693
James Feista465ccc2019-02-08 12:51:01 -0800694 static const std::vector<const char*> BOARD_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800695 "MANUFACTURER", "PRODUCT_NAME", "SERIAL_NUMBER", "PART_NUMBER",
696 "FRU_VERSION_ID", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800697
James Feista465ccc2019-02-08 12:51:01 -0800698 static const std::vector<const char*> PRODUCT_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800699 "MANUFACTURER", "PRODUCT_NAME", "PART_NUMBER",
700 "VERSION", "SERIAL_NUMBER", "ASSET_TAG",
701 "FRU_VERSION_ID", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800702
James Feistd068e932018-09-20 10:53:07 -0700703 if (fruBytes.size() <= 8)
James Feist3cb5fec2018-01-23 14:41:51 -0800704 {
705 return false;
706 }
707 std::vector<char>::const_iterator fruAreaOffsetField = fruBytes.begin();
James Feist9eb0b582018-04-27 12:15:46 -0700708 result["Common_Format_Version"] =
James Feist3cb5fec2018-01-23 14:41:51 -0800709 std::to_string(static_cast<int>(*fruAreaOffsetField));
710
James Feista465ccc2019-02-08 12:51:01 -0800711 const std::vector<const char*>* fieldData;
James Feist3cb5fec2018-01-23 14:41:51 -0800712
James Feist0eb40352019-04-09 14:44:04 -0700713 for (const std::string& area : FRU_AREAS)
James Feist3cb5fec2018-01-23 14:41:51 -0800714 {
Patrick Venture4b7a7452019-10-28 08:41:02 -0700715 ++fruAreaOffsetField;
James Feist3cb5fec2018-01-23 14:41:51 -0800716 if (fruAreaOffsetField >= fruBytes.end())
717 {
718 return false;
719 }
720 size_t offset = (*fruAreaOffsetField) * 8;
721
722 if (offset > 1)
723 {
724 // +2 to skip format and length
725 std::vector<char>::const_iterator fruBytesIter =
726 fruBytes.begin() + offset + 2;
727
728 if (fruBytesIter >= fruBytes.end())
729 {
730 return false;
731 }
732
733 if (area == "CHASSIS")
734 {
735 result["CHASSIS_TYPE"] =
736 std::to_string(static_cast<int>(*fruBytesIter));
737 fruBytesIter += 1;
738 fieldData = &CHASSIS_FRU_AREAS;
739 }
740 else if (area == "BOARD")
741 {
742 result["BOARD_LANGUAGE_CODE"] =
743 std::to_string(static_cast<int>(*fruBytesIter));
744 fruBytesIter += 1;
745 if (fruBytesIter >= fruBytes.end())
746 {
747 return false;
748 }
749
750 unsigned int minutes = *fruBytesIter |
751 *(fruBytesIter + 1) << 8 |
752 *(fruBytesIter + 2) << 16;
753 std::tm fruTime = intelEpoch();
Patrick Venturee0e6f5f2019-08-12 19:00:11 -0700754 std::time_t timeValue = std::mktime(&fruTime);
James Feist3cb5fec2018-01-23 14:41:51 -0800755 timeValue += minutes * 60;
Patrick Venturee0e6f5f2019-08-12 19:00:11 -0700756 fruTime = *std::gmtime(&timeValue);
James Feist3cb5fec2018-01-23 14:41:51 -0800757
Patrick Venturee0e6f5f2019-08-12 19:00:11 -0700758 // Tue Nov 20 23:08:00 2018
759 char timeString[32] = {0};
760 auto bytes = std::strftime(timeString, sizeof(timeString),
Patrick Venturefff050a2019-08-13 11:44:30 -0700761 "%Y-%m-%d - %H:%M:%S", &fruTime);
Patrick Venturee0e6f5f2019-08-12 19:00:11 -0700762 if (bytes == 0)
763 {
764 std::cerr << "invalid time string encountered\n";
765 return false;
766 }
767
768 result["BOARD_MANUFACTURE_DATE"] = std::string(timeString);
James Feist3cb5fec2018-01-23 14:41:51 -0800769 fruBytesIter += 3;
770 fieldData = &BOARD_FRU_AREAS;
771 }
772 else if (area == "PRODUCT")
773 {
774 result["PRODUCT_LANGUAGE_CODE"] =
775 std::to_string(static_cast<int>(*fruBytesIter));
776 fruBytesIter += 1;
777 fieldData = &PRODUCT_FRU_AREAS;
778 }
779 else
780 {
781 continue;
782 }
James Feista465ccc2019-02-08 12:51:01 -0800783 for (auto& field : *fieldData)
James Feist3cb5fec2018-01-23 14:41:51 -0800784 {
785 if (fruBytesIter >= fruBytes.end())
786 {
787 return false;
788 }
789
Vijay Khemka5d5de442018-11-07 10:51:25 -0800790 /* Checking for last byte C1 to indicate that no more
791 * field to be read */
James Feist98132792019-07-09 13:29:09 -0700792 if (static_cast<uint8_t>(*fruBytesIter) == 0xC1)
Vijay Khemka5d5de442018-11-07 10:51:25 -0800793 {
794 break;
795 }
796
Ed Tanous2147e672019-02-27 13:59:56 -0800797 size_t length = *fruBytesIter & 0x3f;
798 fruBytesIter += 1;
799
James Feist3cb5fec2018-01-23 14:41:51 -0800800 if (fruBytesIter >= fruBytes.end())
801 {
802 return false;
803 }
Ed Tanous2147e672019-02-27 13:59:56 -0800804 std::string value(fruBytesIter, fruBytesIter + length);
James Feist3cb5fec2018-01-23 14:41:51 -0800805
Ed Tanous2147e672019-02-27 13:59:56 -0800806 // Strip non null characters from the end
807 value.erase(std::find_if(value.rbegin(), value.rend(),
808 [](char ch) { return ch != 0; })
809 .base(),
810 value.end());
811
James Feist0eb40352019-04-09 14:44:04 -0700812 result[area + "_" + field] = std::move(value);
Ed Tanous2147e672019-02-27 13:59:56 -0800813
James Feist3cb5fec2018-01-23 14:41:51 -0800814 fruBytesIter += length;
815 if (fruBytesIter >= fruBytes.end())
816 {
817 std::cerr << "Warning Fru Length Mismatch:\n ";
James Feista465ccc2019-02-08 12:51:01 -0800818 for (auto& c : fruBytes)
James Feist3cb5fec2018-01-23 14:41:51 -0800819 {
820 std::cerr << c;
821 }
822 std::cerr << "\n";
823 if (DEBUG)
824 {
James Feista465ccc2019-02-08 12:51:01 -0800825 for (auto& keyPair : result)
James Feist3cb5fec2018-01-23 14:41:51 -0800826 {
827 std::cerr << keyPair.first << " : "
828 << keyPair.second << "\n";
829 }
830 }
831 return false;
832 }
833 }
834 }
835 }
836
837 return true;
838}
839
Nikhil Potaded8884f12019-03-27 13:27:13 -0700840std::vector<uint8_t>& getFruInfo(const uint8_t& bus, const uint8_t& address)
841{
842 auto deviceMap = busMap.find(bus);
843 if (deviceMap == busMap.end())
844 {
845 throw std::invalid_argument("Invalid Bus.");
846 }
847 auto device = deviceMap->second->find(address);
848 if (device == deviceMap->second->end())
849 {
850 throw std::invalid_argument("Invalid Address.");
851 }
852 std::vector<uint8_t>& ret =
853 reinterpret_cast<std::vector<uint8_t>&>(device->second);
854
855 return ret;
856}
857
James Feist3cb5fec2018-01-23 14:41:51 -0800858void AddFruObjectToDbus(
James Feist5cb06082019-10-24 17:11:13 -0700859 std::vector<char>& device,
James Feista465ccc2019-02-08 12:51:01 -0800860 boost::container::flat_map<
861 std::pair<size_t, size_t>,
862 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
James Feist13b86d62018-05-29 11:24:35 -0700863 uint32_t bus, uint32_t address)
James Feist3cb5fec2018-01-23 14:41:51 -0800864{
865 boost::container::flat_map<std::string, std::string> formattedFru;
866 if (!formatFru(device, formattedFru))
867 {
Patrick Ventureb755c832019-08-07 11:09:14 -0700868 std::cerr << "failed to format fru for device at bus " << bus
869 << " address " << address << "\n";
James Feist3cb5fec2018-01-23 14:41:51 -0800870 return;
871 }
Patrick Venture96cdaef2019-07-30 13:30:52 -0700872
James Feist3cb5fec2018-01-23 14:41:51 -0800873 auto productNameFind = formattedFru.find("BOARD_PRODUCT_NAME");
874 std::string productName;
Patrick Venture96cdaef2019-07-30 13:30:52 -0700875 // Not found under Board section or an empty string.
876 if (productNameFind == formattedFru.end() ||
877 productNameFind->second.empty())
James Feist3cb5fec2018-01-23 14:41:51 -0800878 {
879 productNameFind = formattedFru.find("PRODUCT_PRODUCT_NAME");
880 }
Patrick Venture96cdaef2019-07-30 13:30:52 -0700881 // Found under Product section and not an empty string.
882 if (productNameFind != formattedFru.end() &&
883 !productNameFind->second.empty())
James Feist3cb5fec2018-01-23 14:41:51 -0800884 {
885 productName = productNameFind->second;
James Feist3f8a2782018-02-12 09:24:42 -0800886 std::regex illegalObject("[^A-Za-z0-9_]");
887 productName = std::regex_replace(productName, illegalObject, "_");
James Feist3cb5fec2018-01-23 14:41:51 -0800888 }
889 else
890 {
891 productName = "UNKNOWN" + std::to_string(UNKNOWN_BUS_OBJECT_COUNT);
892 UNKNOWN_BUS_OBJECT_COUNT++;
893 }
894
James Feist918e18c2018-02-13 15:51:07 -0800895 productName = "/xyz/openbmc_project/FruDevice/" + productName;
James Feist918e18c2018-02-13 15:51:07 -0800896 // avoid duplicates by checking to see if on a mux
James Feist79e9c0b2018-03-15 15:21:17 -0700897 if (bus > 0)
James Feist918e18c2018-02-13 15:51:07 -0800898 {
Patrick Venture015fb0a2019-08-16 09:33:31 -0700899 int highest = -1;
900 bool found = false;
901
James Feista465ccc2019-02-08 12:51:01 -0800902 for (auto const& busIface : dbusInterfaceMap)
James Feist918e18c2018-02-13 15:51:07 -0800903 {
Patrick Venture015fb0a2019-08-16 09:33:31 -0700904 std::string path = busIface.second->get_object_path();
905 if (std::regex_match(path, std::regex(productName + "(_\\d+|)$")))
James Feist918e18c2018-02-13 15:51:07 -0800906 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700907 if (isMuxBus(bus) && address == busIface.first.second &&
James Feist98132792019-07-09 13:29:09 -0700908 (getFruInfo(static_cast<uint8_t>(busIface.first.first),
909 static_cast<uint8_t>(busIface.first.second)) ==
910 getFruInfo(static_cast<uint8_t>(bus),
911 static_cast<uint8_t>(address))))
James Feist79e9c0b2018-03-15 15:21:17 -0700912 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700913 // This device is already added to the lower numbered bus,
914 // do not replicate it.
915 return;
James Feist79e9c0b2018-03-15 15:21:17 -0700916 }
Patrick Venture015fb0a2019-08-16 09:33:31 -0700917
918 // Check if the match named has extra information.
919 found = true;
920 std::smatch base_match;
921
922 bool match = std::regex_match(
923 path, base_match, std::regex(productName + "_(\\d+)$"));
924 if (match)
James Feist79e9c0b2018-03-15 15:21:17 -0700925 {
Patrick Venture015fb0a2019-08-16 09:33:31 -0700926 if (base_match.size() == 2)
927 {
928 std::ssub_match base_sub_match = base_match[1];
929 std::string base = base_sub_match.str();
930
931 int value = std::stoi(base);
932 highest = (value > highest) ? value : highest;
933 }
James Feist79e9c0b2018-03-15 15:21:17 -0700934 }
James Feist918e18c2018-02-13 15:51:07 -0800935 }
Patrick Venture015fb0a2019-08-16 09:33:31 -0700936 } // end searching objects
937
938 if (found)
939 {
940 // We found something with the same name. If highest was still -1,
941 // it means this new entry will be _0.
942 productName += "_";
943 productName += std::to_string(++highest);
James Feist918e18c2018-02-13 15:51:07 -0800944 }
945 }
James Feist3cb5fec2018-01-23 14:41:51 -0800946
James Feist9eb0b582018-04-27 12:15:46 -0700947 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
948 objServer.add_interface(productName, "xyz.openbmc_project.FruDevice");
949 dbusInterfaceMap[std::pair<size_t, size_t>(bus, address)] = iface;
950
James Feista465ccc2019-02-08 12:51:01 -0800951 for (auto& property : formattedFru)
James Feist3cb5fec2018-01-23 14:41:51 -0800952 {
James Feist9eb0b582018-04-27 12:15:46 -0700953
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -0700954 std::regex_replace(property.second.begin(), property.second.begin(),
955 property.second.end(), NON_ASCII_REGEX, "_");
James Feist9eb0b582018-04-27 12:15:46 -0700956 if (property.second.empty())
957 {
958 continue;
959 }
960 std::string key =
961 std::regex_replace(property.first, NON_ASCII_REGEX, "_");
962 if (!iface->register_property(key, property.second + '\0'))
963 {
964 std::cerr << "illegal key: " << key << "\n";
965 }
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -0700966 if (DEBUG)
967 {
968 std::cout << property.first << ": " << property.second << "\n";
969 }
James Feist3cb5fec2018-01-23 14:41:51 -0800970 }
James Feist2a9d6db2018-04-27 15:48:28 -0700971
972 // baseboard will be 0, 0
James Feist13b86d62018-05-29 11:24:35 -0700973 iface->register_property("BUS", bus);
974 iface->register_property("ADDRESS", address);
James Feist2a9d6db2018-04-27 15:48:28 -0700975
James Feist9eb0b582018-04-27 12:15:46 -0700976 iface->initialize();
James Feist3cb5fec2018-01-23 14:41:51 -0800977}
978
James Feista465ccc2019-02-08 12:51:01 -0800979static bool readBaseboardFru(std::vector<char>& baseboardFru)
James Feist3cb5fec2018-01-23 14:41:51 -0800980{
981 // try to read baseboard fru from file
982 std::ifstream baseboardFruFile(BASEBOARD_FRU_LOCATION, std::ios::binary);
983 if (baseboardFruFile.good())
984 {
985 baseboardFruFile.seekg(0, std::ios_base::end);
James Feist98132792019-07-09 13:29:09 -0700986 size_t fileSize = static_cast<size_t>(baseboardFruFile.tellg());
James Feist3cb5fec2018-01-23 14:41:51 -0800987 baseboardFru.resize(fileSize);
988 baseboardFruFile.seekg(0, std::ios_base::beg);
989 baseboardFruFile.read(baseboardFru.data(), fileSize);
990 }
991 else
992 {
993 return false;
994 }
995 return true;
996}
997
James Feista465ccc2019-02-08 12:51:01 -0800998bool writeFru(uint8_t bus, uint8_t address, const std::vector<uint8_t>& fru)
James Feistb49ffc32018-05-02 11:10:43 -0700999{
1000 boost::container::flat_map<std::string, std::string> tmp;
1001 if (fru.size() > MAX_FRU_SIZE)
1002 {
1003 std::cerr << "Invalid fru.size() during writeFru\n";
1004 return false;
1005 }
1006 // verify legal fru by running it through fru parsing logic
James Feista465ccc2019-02-08 12:51:01 -08001007 if (!formatFru(reinterpret_cast<const std::vector<char>&>(fru), tmp))
James Feistb49ffc32018-05-02 11:10:43 -07001008 {
1009 std::cerr << "Invalid fru format during writeFru\n";
1010 return false;
1011 }
1012 // baseboard fru
1013 if (bus == 0 && address == 0)
1014 {
1015 std::ofstream file(BASEBOARD_FRU_LOCATION, std::ios_base::binary);
1016 if (!file.good())
1017 {
1018 std::cerr << "Error opening file " << BASEBOARD_FRU_LOCATION
1019 << "\n";
James Feistddb78302018-09-06 11:45:42 -07001020 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001021 return false;
1022 }
James Feista465ccc2019-02-08 12:51:01 -08001023 file.write(reinterpret_cast<const char*>(fru.data()), fru.size());
James Feistb49ffc32018-05-02 11:10:43 -07001024 return file.good();
1025 }
1026 else
1027 {
Patrick Venturec50e1ff2019-08-06 10:22:28 -07001028 if (hasEepromFile(bus, address))
1029 {
1030 auto path = getEepromPath(bus, address);
1031 int eeprom = open(path.c_str(), O_RDWR | O_CLOEXEC);
1032 if (eeprom < 0)
1033 {
1034 std::cerr << "unable to open i2c device " << path << "\n";
1035 throw DBusInternalError();
1036 return false;
1037 }
1038
1039 ssize_t writtenBytes = write(eeprom, fru.data(), fru.size());
1040 if (writtenBytes < 0)
1041 {
1042 std::cerr << "unable to write to i2c device " << path << "\n";
1043 close(eeprom);
1044 throw DBusInternalError();
1045 return false;
1046 }
1047
1048 close(eeprom);
1049 return true;
1050 }
1051
James Feistb49ffc32018-05-02 11:10:43 -07001052 std::string i2cBus = "/dev/i2c-" + std::to_string(bus);
1053
1054 int file = open(i2cBus.c_str(), O_RDWR | O_CLOEXEC);
1055 if (file < 0)
1056 {
1057 std::cerr << "unable to open i2c device " << i2cBus << "\n";
James Feistddb78302018-09-06 11:45:42 -07001058 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001059 return false;
1060 }
1061 if (ioctl(file, I2C_SLAVE_FORCE, address) < 0)
1062 {
1063 std::cerr << "unable to set device address\n";
1064 close(file);
James Feistddb78302018-09-06 11:45:42 -07001065 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001066 return false;
1067 }
1068
1069 constexpr const size_t RETRY_MAX = 2;
1070 uint16_t index = 0;
1071 size_t retries = RETRY_MAX;
1072 while (index < fru.size())
1073 {
1074 if ((index && ((index % (MAX_EEPROM_PAGE_INDEX + 1)) == 0)) &&
1075 (retries == RETRY_MAX))
1076 {
1077 // The 4K EEPROM only uses the A2 and A1 device address bits
1078 // with the third bit being a memory page address bit.
1079 if (ioctl(file, I2C_SLAVE_FORCE, ++address) < 0)
1080 {
1081 std::cerr << "unable to set device address\n";
1082 close(file);
James Feistddb78302018-09-06 11:45:42 -07001083 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001084 return false;
1085 }
1086 }
1087
James Feist98132792019-07-09 13:29:09 -07001088 if (i2c_smbus_write_byte_data(file, static_cast<uint8_t>(index),
1089 fru[index]) < 0)
James Feistb49ffc32018-05-02 11:10:43 -07001090 {
1091 if (!retries--)
1092 {
1093 std::cerr << "error writing fru: " << strerror(errno)
1094 << "\n";
1095 close(file);
James Feistddb78302018-09-06 11:45:42 -07001096 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001097 return false;
1098 }
1099 }
1100 else
1101 {
1102 retries = RETRY_MAX;
1103 index++;
1104 }
1105 // most eeproms require 5-10ms between writes
1106 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1107 }
1108 close(file);
1109 return true;
1110 }
1111}
1112
James Feist9eb0b582018-04-27 12:15:46 -07001113void rescanBusses(
James Feist5cb06082019-10-24 17:11:13 -07001114 BusMap& busmap,
James Feista465ccc2019-02-08 12:51:01 -08001115 boost::container::flat_map<
1116 std::pair<size_t, size_t>,
James Feist5cb06082019-10-24 17:11:13 -07001117 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap)
James Feist918e18c2018-02-13 15:51:07 -08001118{
James Feist6ebf9de2018-05-15 15:01:17 -07001119 static boost::asio::deadline_timer timer(io);
1120 timer.expires_from_now(boost::posix_time::seconds(1));
James Feist918e18c2018-02-13 15:51:07 -08001121
Gunnar Mills6f0ae942018-08-31 12:38:03 -05001122 // setup an async wait in case we get flooded with requests
James Feist98132792019-07-09 13:29:09 -07001123 timer.async_wait([&](const boost::system::error_code&) {
James Feist4131aea2018-03-09 09:47:30 -08001124 auto devDir = fs::path("/dev/");
James Feist4131aea2018-03-09 09:47:30 -08001125 std::vector<fs::path> i2cBuses;
James Feist918e18c2018-02-13 15:51:07 -08001126
Nikhil Potaded8884f12019-03-27 13:27:13 -07001127 boost::container::flat_map<size_t, fs::path> busPaths;
1128 if (!getI2cDevicePaths(devDir, busPaths))
James Feist918e18c2018-02-13 15:51:07 -08001129 {
James Feist4131aea2018-03-09 09:47:30 -08001130 std::cerr << "unable to find i2c devices\n";
1131 return;
James Feist918e18c2018-02-13 15:51:07 -08001132 }
Nikhil Potaded8884f12019-03-27 13:27:13 -07001133
1134 for (auto busPath : busPaths)
1135 {
1136 i2cBuses.emplace_back(busPath.second);
1137 }
James Feist4131aea2018-03-09 09:47:30 -08001138
James Feist98132792019-07-09 13:29:09 -07001139 busmap.clear();
James Feist8a983922019-10-24 17:11:56 -07001140 for (auto& [pair, interface] : foundDevices)
1141 {
1142 objServer.remove_interface(interface);
1143 }
1144 foundDevices.clear();
1145
James Feist5cb06082019-10-24 17:11:13 -07001146 auto scan =
1147 std::make_shared<FindDevicesWithCallback>(i2cBuses, busmap, [&]() {
James Feista465ccc2019-02-08 12:51:01 -08001148 for (auto& busIface : dbusInterfaceMap)
James Feist6ebf9de2018-05-15 15:01:17 -07001149 {
1150 objServer.remove_interface(busIface.second);
1151 }
James Feist4131aea2018-03-09 09:47:30 -08001152
James Feist6ebf9de2018-05-15 15:01:17 -07001153 dbusInterfaceMap.clear();
1154 UNKNOWN_BUS_OBJECT_COUNT = 0;
James Feist4131aea2018-03-09 09:47:30 -08001155
James Feist6ebf9de2018-05-15 15:01:17 -07001156 // todo, get this from a more sensable place
1157 std::vector<char> baseboardFru;
1158 if (readBaseboardFru(baseboardFru))
1159 {
1160 boost::container::flat_map<int, std::vector<char>>
1161 baseboardDev;
1162 baseboardDev.emplace(0, baseboardFru);
James Feist98132792019-07-09 13:29:09 -07001163 busmap[0] = std::make_shared<DeviceMap>(baseboardDev);
James Feist6ebf9de2018-05-15 15:01:17 -07001164 }
James Feist98132792019-07-09 13:29:09 -07001165 for (auto& devicemap : busmap)
James Feist6ebf9de2018-05-15 15:01:17 -07001166 {
James Feista465ccc2019-02-08 12:51:01 -08001167 for (auto& device : *devicemap.second)
James Feist6ebf9de2018-05-15 15:01:17 -07001168 {
James Feist5cb06082019-10-24 17:11:13 -07001169 AddFruObjectToDbus(device.second, dbusInterfaceMap,
1170 devicemap.first, device.first);
James Feist6ebf9de2018-05-15 15:01:17 -07001171 }
1172 }
1173 });
1174 scan->run();
1175 });
James Feist918e18c2018-02-13 15:51:07 -08001176}
1177
James Feist98132792019-07-09 13:29:09 -07001178int main()
James Feist3cb5fec2018-01-23 14:41:51 -08001179{
1180 auto devDir = fs::path("/dev/");
James Feistc9dff1b2019-02-13 13:33:13 -08001181 auto matchString = std::string(R"(i2c-\d+$)");
James Feist3cb5fec2018-01-23 14:41:51 -08001182 std::vector<fs::path> i2cBuses;
1183
James Feista3c180a2018-08-09 16:06:04 -07001184 if (!findFiles(devDir, matchString, i2cBuses))
James Feist3cb5fec2018-01-23 14:41:51 -08001185 {
1186 std::cerr << "unable to find i2c devices\n";
1187 return 1;
1188 }
James Feist3cb5fec2018-01-23 14:41:51 -08001189
Patrick Venture11f1ff42019-08-01 10:42:12 -07001190 // check for and load blacklist with initial buses.
1191 loadBlacklist(blacklistPath);
1192
Vijay Khemka065f6d92018-12-18 10:37:47 -08001193 systemBus->request_name("xyz.openbmc_project.FruDevice");
James Feist3cb5fec2018-01-23 14:41:51 -08001194
James Feist6ebf9de2018-05-15 15:01:17 -07001195 // this is a map with keys of pair(bus number, address) and values of
1196 // the object on dbus
James Feist3cb5fec2018-01-23 14:41:51 -08001197 boost::container::flat_map<std::pair<size_t, size_t>,
James Feist9eb0b582018-04-27 12:15:46 -07001198 std::shared_ptr<sdbusplus::asio::dbus_interface>>
1199 dbusInterfaceMap;
James Feist3cb5fec2018-01-23 14:41:51 -08001200
James Feist9eb0b582018-04-27 12:15:46 -07001201 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
1202 objServer.add_interface("/xyz/openbmc_project/FruDevice",
1203 "xyz.openbmc_project.FruDeviceManager");
James Feist3cb5fec2018-01-23 14:41:51 -08001204
James Feist5cb06082019-10-24 17:11:13 -07001205 iface->register_method("ReScan",
1206 [&]() { rescanBusses(busMap, dbusInterfaceMap); });
James Feist2a9d6db2018-04-27 15:48:28 -07001207
Nikhil Potaded8884f12019-03-27 13:27:13 -07001208 iface->register_method("GetRawFru", getFruInfo);
James Feistb49ffc32018-05-02 11:10:43 -07001209
1210 iface->register_method("WriteFru", [&](const uint8_t bus,
1211 const uint8_t address,
James Feista465ccc2019-02-08 12:51:01 -08001212 const std::vector<uint8_t>& data) {
James Feistb49ffc32018-05-02 11:10:43 -07001213 if (!writeFru(bus, address, data))
1214 {
James Feistddb78302018-09-06 11:45:42 -07001215 throw std::invalid_argument("Invalid Arguments.");
James Feistb49ffc32018-05-02 11:10:43 -07001216 return;
1217 }
1218 // schedule rescan on success
James Feist5cb06082019-10-24 17:11:13 -07001219 rescanBusses(busMap, dbusInterfaceMap);
James Feistb49ffc32018-05-02 11:10:43 -07001220 });
James Feist9eb0b582018-04-27 12:15:46 -07001221 iface->initialize();
James Feist3cb5fec2018-01-23 14:41:51 -08001222
James Feist9eb0b582018-04-27 12:15:46 -07001223 std::function<void(sdbusplus::message::message & message)> eventHandler =
James Feista465ccc2019-02-08 12:51:01 -08001224 [&](sdbusplus::message::message& message) {
James Feist918e18c2018-02-13 15:51:07 -08001225 std::string objectName;
James Feist9eb0b582018-04-27 12:15:46 -07001226 boost::container::flat_map<
James Feista465ccc2019-02-08 12:51:01 -08001227 std::string,
1228 std::variant<std::string, bool, int64_t, uint64_t, double>>
James Feist9eb0b582018-04-27 12:15:46 -07001229 values;
1230 message.read(objectName, values);
James Feist0eeb79c2019-10-09 13:35:29 -07001231 auto findState = values.find("CurrentHostState");
1232 bool on = false;
1233 if (findState != values.end())
James Feist918e18c2018-02-13 15:51:07 -08001234 {
James Feist0eeb79c2019-10-09 13:35:29 -07001235 on = boost::ends_with(std::get<std::string>(findState->second),
1236 "Running");
1237 }
James Feist6ebf9de2018-05-15 15:01:17 -07001238
James Feist0eeb79c2019-10-09 13:35:29 -07001239 if (on)
1240 {
James Feist5cb06082019-10-24 17:11:13 -07001241 rescanBusses(busMap, dbusInterfaceMap);
James Feist918e18c2018-02-13 15:51:07 -08001242 }
James Feist918e18c2018-02-13 15:51:07 -08001243 };
James Feist9eb0b582018-04-27 12:15:46 -07001244
1245 sdbusplus::bus::match::match powerMatch = sdbusplus::bus::match::match(
James Feista465ccc2019-02-08 12:51:01 -08001246 static_cast<sdbusplus::bus::bus&>(*systemBus),
James Feist7bcd3f22019-03-18 16:04:04 -07001247 "type='signal',interface='org.freedesktop.DBus.Properties',path='/xyz/"
James Feist0eeb79c2019-10-09 13:35:29 -07001248 "openbmc_project/state/"
1249 "host0',arg0='xyz.openbmc_project.State.Host'",
James Feist9eb0b582018-04-27 12:15:46 -07001250 eventHandler);
1251
James Feist4131aea2018-03-09 09:47:30 -08001252 int fd = inotify_init();
James Feist0eb40352019-04-09 14:44:04 -07001253 inotify_add_watch(fd, I2C_DEV_LOCATION,
1254 IN_CREATE | IN_MOVED_TO | IN_DELETE);
James Feist4131aea2018-03-09 09:47:30 -08001255 std::array<char, 4096> readBuffer;
1256 std::string pendingBuffer;
1257 // monitor for new i2c devices
1258 boost::asio::posix::stream_descriptor dirWatch(io, fd);
1259 std::function<void(const boost::system::error_code, std::size_t)>
James Feista465ccc2019-02-08 12:51:01 -08001260 watchI2cBusses = [&](const boost::system::error_code& ec,
James Feist4131aea2018-03-09 09:47:30 -08001261 std::size_t bytes_transferred) {
1262 if (ec)
1263 {
1264 std::cout << "Callback Error " << ec << "\n";
1265 return;
1266 }
1267 pendingBuffer += std::string(readBuffer.data(), bytes_transferred);
1268 bool devChange = false;
1269 while (pendingBuffer.size() > sizeof(inotify_event))
1270 {
James Feista465ccc2019-02-08 12:51:01 -08001271 const inotify_event* iEvent =
1272 reinterpret_cast<const inotify_event*>(
James Feist4131aea2018-03-09 09:47:30 -08001273 pendingBuffer.data());
1274 switch (iEvent->mask)
1275 {
James Feist9eb0b582018-04-27 12:15:46 -07001276 case IN_CREATE:
1277 case IN_MOVED_TO:
1278 case IN_DELETE:
1279 if (boost::starts_with(std::string(iEvent->name),
1280 "i2c"))
1281 {
1282 devChange = true;
1283 }
James Feist4131aea2018-03-09 09:47:30 -08001284 }
1285
1286 pendingBuffer.erase(0, sizeof(inotify_event) + iEvent->len);
1287 }
James Feist6ebf9de2018-05-15 15:01:17 -07001288 if (devChange)
James Feist4131aea2018-03-09 09:47:30 -08001289 {
James Feist5cb06082019-10-24 17:11:13 -07001290 rescanBusses(busMap, dbusInterfaceMap);
James Feist4131aea2018-03-09 09:47:30 -08001291 }
James Feist6ebf9de2018-05-15 15:01:17 -07001292
James Feist4131aea2018-03-09 09:47:30 -08001293 dirWatch.async_read_some(boost::asio::buffer(readBuffer),
1294 watchI2cBusses);
1295 };
1296
1297 dirWatch.async_read_some(boost::asio::buffer(readBuffer), watchI2cBusses);
Gunnar Millsb3e42fe2018-06-13 15:48:27 -05001298 // run the initial scan
James Feist5cb06082019-10-24 17:11:13 -07001299 rescanBusses(busMap, dbusInterfaceMap);
James Feist918e18c2018-02-13 15:51:07 -08001300
James Feist3cb5fec2018-01-23 14:41:51 -08001301 io.run();
1302 return 0;
1303}