blob: 7e37dd20a51e23db1c26cc4b7d41ab4d0af2365f [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
James Feist3b860982018-10-02 14:34:07 -070017#include <errno.h>
James Feist3cb5fec2018-01-23 14:41:51 -080018#include <fcntl.h>
James Feist3b860982018-10-02 14:34:07 -070019#include <sys/inotify.h>
20#include <sys/ioctl.h>
21
22#include <Utils.hpp>
23#include <boost/algorithm/string/predicate.hpp>
24#include <boost/container/flat_map.hpp>
25#include <chrono>
26#include <ctime>
Patrick Venturee3754002019-08-06 09:39:12 -070027#include <filesystem>
James Feist3cb5fec2018-01-23 14:41:51 -080028#include <fstream>
29#include <future>
Patrick Venturec50e1ff2019-08-06 10:22:28 -070030#include <iomanip>
James Feist3cb5fec2018-01-23 14:41:51 -080031#include <iostream>
Patrick Venture11f1ff42019-08-01 10:42:12 -070032#include <nlohmann/json.hpp>
James Feist3f8a2782018-02-12 09:24:42 -080033#include <regex>
James Feist3b860982018-10-02 14:34:07 -070034#include <sdbusplus/asio/connection.hpp>
35#include <sdbusplus/asio/object_server.hpp>
Patrick Venturec50e1ff2019-08-06 10:22:28 -070036#include <set>
37#include <sstream>
Patrick Venture11f1ff42019-08-01 10:42:12 -070038#include <string>
James Feist3b860982018-10-02 14:34:07 -070039#include <thread>
James Feista465ccc2019-02-08 12:51:01 -080040#include <variant>
James Feist3b860982018-10-02 14:34:07 -070041
42extern "C" {
43#include <i2c/smbus.h>
44#include <linux/i2c-dev.h>
45}
James Feist3cb5fec2018-01-23 14:41:51 -080046
Ed Tanous072e25d2018-12-16 21:45:20 -080047namespace fs = std::filesystem;
James Feist3cb5fec2018-01-23 14:41:51 -080048static constexpr bool DEBUG = false;
49static size_t UNKNOWN_BUS_OBJECT_COUNT = 0;
James Feistb49ffc32018-05-02 11:10:43 -070050constexpr size_t MAX_FRU_SIZE = 512;
51constexpr size_t MAX_EEPROM_PAGE_INDEX = 255;
James Feist26c27ad2018-07-25 15:09:40 -070052constexpr size_t busTimeoutSeconds = 5;
James Feist3cb5fec2018-01-23 14:41:51 -080053
Patrick Venture11f1ff42019-08-01 10:42:12 -070054constexpr const char* blacklistPath = PACKAGE_DIR "blacklist.json";
55
James Feista465ccc2019-02-08 12:51:01 -080056const static constexpr char* BASEBOARD_FRU_LOCATION =
James Feist3cb5fec2018-01-23 14:41:51 -080057 "/etc/fru/baseboard.fru.bin";
58
James Feista465ccc2019-02-08 12:51:01 -080059const static constexpr char* I2C_DEV_LOCATION = "/dev";
James Feist4131aea2018-03-09 09:47:30 -080060
James Feista465ccc2019-02-08 12:51:01 -080061static constexpr std::array<const char*, 5> FRU_AREAS = {
James Feist3cb5fec2018-01-23 14:41:51 -080062 "INTERNAL", "CHASSIS", "BOARD", "PRODUCT", "MULTIRECORD"};
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -070063const static std::regex NON_ASCII_REGEX("[^\x01-\x7f]");
James Feist3cb5fec2018-01-23 14:41:51 -080064using DeviceMap = boost::container::flat_map<int, std::vector<char>>;
65using BusMap = boost::container::flat_map<int, std::shared_ptr<DeviceMap>>;
66
James Feist444830e2019-04-05 08:38:16 -070067static std::set<size_t> busBlacklist;
James Feist6ebf9de2018-05-15 15:01:17 -070068struct FindDevicesWithCallback;
69
Nikhil Potaded8884f12019-03-27 13:27:13 -070070static BusMap busMap;
71
Patrick Venturec50e1ff2019-08-06 10:22:28 -070072// Given a bus/address, produce the path in sysfs for an eeprom.
73static std::string getEepromPath(size_t bus, size_t address)
74{
75 std::stringstream output;
76 output << "/sys/bus/i2c/devices/" << bus << "-" << std::right
77 << std::setfill('0') << std::setw(4) << std::hex << address
78 << "/eeprom";
79 return output.str();
80}
81
82static bool hasEepromFile(size_t bus, size_t address)
83{
84 auto path = getEepromPath(bus, address);
85 try
86 {
87 return fs::exists(path);
88 }
89 catch (...)
90 {
91 return false;
92 }
93}
94
95static ssize_t readFromEeprom(int fd, uint16_t offset, uint8_t len,
96 uint8_t* buf)
97{
98 auto result = lseek(fd, offset, SEEK_SET);
99 if (result < 0)
100 {
101 std::cerr << "failed to seek\n";
102 return -1;
103 }
104
105 return read(fd, buf, len);
106}
107
James Feistc95cb142018-02-26 10:41:42 -0800108static bool isMuxBus(size_t bus)
109{
Ed Tanous072e25d2018-12-16 21:45:20 -0800110 return is_symlink(std::filesystem::path(
James Feistc95cb142018-02-26 10:41:42 -0800111 "/sys/bus/i2c/devices/i2c-" + std::to_string(bus) + "/mux_device"));
112}
113
Vijay Khemka2d681f62018-11-06 15:51:00 -0800114static int isDevice16Bit(int file)
115{
116 /* Get first byte */
117 int byte1 = i2c_smbus_read_byte_data(file, 0);
118 if (byte1 < 0)
119 {
120 return byte1;
121 }
122 /* Read 7 more bytes, it will read same first byte in case of
123 * 8 bit but it will read next byte in case of 16 bit
124 */
125 for (int i = 0; i < 7; i++)
126 {
127 int byte2 = i2c_smbus_read_byte_data(file, 0);
128 if (byte2 < 0)
129 {
130 return byte2;
131 }
132 if (byte2 != byte1)
133 {
134 return 1;
135 }
136 }
137 return 0;
138}
139
Patrick Venture4f47fe62019-08-08 16:30:38 -0700140static int readBlockData(int flag, int file, uint16_t offset, uint8_t len,
141 uint8_t* buf)
Vijay Khemka2d681f62018-11-06 15:51:00 -0800142{
Patrick Venture4f47fe62019-08-08 16:30:38 -0700143 uint8_t lowAddr = static_cast<uint8_t>(offset);
144 uint8_t highAddr = static_cast<uint8_t>(offset >> 8);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800145
146 if (flag == 0)
147 {
Patrick Venture4f47fe62019-08-08 16:30:38 -0700148 return i2c_smbus_read_i2c_block_data(file, lowAddr, len, buf);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800149 }
150
151 /* This is for 16 bit addressing EEPROM device. First an offset
152 * needs to be written before read data from a offset
153 */
Patrick Venture4f47fe62019-08-08 16:30:38 -0700154 int ret = i2c_smbus_write_byte_data(file, 0, lowAddr);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800155 if (ret < 0)
156 {
157 return ret;
158 }
159
Patrick Venture4f47fe62019-08-08 16:30:38 -0700160 return i2c_smbus_read_i2c_block_data(file, highAddr, len, buf);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800161}
162
James Feist24bae7a2019-04-03 09:50:56 -0700163bool validateHeader(const std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData)
164{
165 // ipmi spec format version number is currently at 1, verify it
166 if (blockData[0] != 0x1)
167 {
168 return false;
169 }
170
171 // verify pad is set to 0
172 if (blockData[6] != 0x0)
173 {
174 return false;
175 }
176
177 // verify offsets are 0, or don't point to another offset
178 std::set<uint8_t> foundOffsets;
179 for (int ii = 1; ii < 6; ii++)
180 {
181 if (blockData[ii] == 0)
182 {
183 continue;
184 }
James Feist0eb40352019-04-09 14:44:04 -0700185 auto inserted = foundOffsets.insert(blockData[ii]);
186 if (!inserted.second)
James Feist24bae7a2019-04-03 09:50:56 -0700187 {
188 return false;
189 }
190 }
191
192 // validate checksum
193 size_t sum = 0;
194 for (int jj = 0; jj < 7; jj++)
195 {
196 sum += blockData[jj];
197 }
198 sum = (256 - sum) & 0xFF;
199
200 if (sum != blockData[7])
201 {
202 return false;
203 }
204 return true;
205}
206
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700207// TODO: This code is very similar to the non-eeprom version and can be merged
208// with some tweaks.
209static std::vector<char> processEeprom(int bus, int address)
210{
211 std::vector<char> device;
Patrick Venture4f47fe62019-08-08 16:30:38 -0700212 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData;
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700213
214 auto path = getEepromPath(bus, address);
215
216 int file = open(path.c_str(), O_RDONLY);
217 if (file < 0)
218 {
219 std::cerr << "Unable to open eeprom file: " << path << "\n";
220 return device;
221 }
222
Patrick Venture4f47fe62019-08-08 16:30:38 -0700223 ssize_t readBytes = readFromEeprom(file, 0, 0x8, blockData.data());
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700224 if (readBytes < 0)
225 {
226 std::cerr << "failed to read eeprom at " << bus << " address "
227 << address << "\n";
228 close(file);
229 return device;
230 }
231
Patrick Venture4f47fe62019-08-08 16:30:38 -0700232 if (!validateHeader(blockData))
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700233 {
234 if (DEBUG)
235 {
236 std::cerr << "Illegal header at bus " << bus << " address "
237 << address << "\n";
238 }
239
240 close(file);
241 return device;
242 }
243
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700244 // Copy the IPMI Fru Header
Patrick Venture4f47fe62019-08-08 16:30:38 -0700245 device.insert(device.end(), blockData.begin(), blockData.begin() + 8);
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700246
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700247 int fruLength = 0;
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700248 for (size_t jj = 1; jj <= FRU_AREAS.size(); jj++)
249 {
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700250 // TODO: offset can be 255, device is holding "chars" that's not good.
Patrick Venture4f47fe62019-08-08 16:30:38 -0700251 int areaOffset = device[jj];
252 if (areaOffset == 0)
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700253 {
254 continue;
255 }
256
Patrick Venture4f47fe62019-08-08 16:30:38 -0700257 areaOffset *= 8;
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700258
Patrick Venture4f47fe62019-08-08 16:30:38 -0700259 if (readFromEeprom(file, static_cast<uint16_t>(areaOffset), 0x2,
260 blockData.data()) < 0)
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700261 {
262 std::cerr << "failed to read bus " << bus << " address " << address
263 << "\n";
264 device.clear();
265 close(file);
266 return device;
267 }
268
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700269 // Ignore data type.
Patrick Venture4f47fe62019-08-08 16:30:38 -0700270 int length = blockData[1] * 8;
271 areaOffset += length;
272 fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700273 }
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700274
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700275 // You already copied these first 8 bytes (the ipmi fru header size)
276 fruLength -= 8;
277
278 int readOffset = 8;
279
280 while (fruLength > 0)
281 {
Patrick Venture4f47fe62019-08-08 16:30:38 -0700282 int requestLength = std::min(I2C_SMBUS_BLOCK_MAX, fruLength);
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700283
284 if (readFromEeprom(file, static_cast<uint16_t>(readOffset),
Patrick Venture4f47fe62019-08-08 16:30:38 -0700285 static_cast<uint8_t>(requestLength),
286 blockData.data()) < 0)
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700287 {
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700288 std::cerr << "failed to read bus " << bus << " address " << address
289 << "\n";
290 device.clear();
291 close(file);
292 return device;
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700293 }
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700294
Patrick Venture4f47fe62019-08-08 16:30:38 -0700295 device.insert(device.end(), blockData.begin(),
296 blockData.begin() + requestLength);
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700297
Patrick Venture4f47fe62019-08-08 16:30:38 -0700298 readOffset += requestLength;
299 fruLength -= requestLength;
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700300 }
301
302 close(file);
303 return device;
304}
305
306std::set<int> findI2CEeproms(int i2cBus, std::shared_ptr<DeviceMap> devices)
307{
308 std::set<int> foundList;
309
310 std::string path = "/sys/bus/i2c/devices/i2c-" + std::to_string(i2cBus);
311
312 // For each file listed under the i2c device
313 // NOTE: This should be faster than just checking for each possible address
314 // path.
315 for (const auto& p : fs::directory_iterator(path))
316 {
317 const std::string node = p.path().string();
318 std::smatch m;
319 bool found =
320 std::regex_match(node, m, std::regex(".+\\d+-([0-9abcdef]+$)"));
321
322 if (!found)
323 {
324 continue;
325 }
326 if (m.size() != 2)
327 {
328 std::cerr << "regex didn't capture\n";
329 continue;
330 }
331
332 std::ssub_match subMatch = m[1];
333 std::string addressString = subMatch.str();
334
335 std::size_t ignored;
336 const int hexBase = 16;
337 int address = std::stoi(addressString, &ignored, hexBase);
338
339 const std::string eeprom = node + "/eeprom";
340
341 try
342 {
343 if (!fs::exists(eeprom))
344 {
345 continue;
346 }
347 }
348 catch (...)
349 {
350 continue;
351 }
352
353 // There is an eeprom file at this address, it may have invalid
354 // contents, but we found it.
355 foundList.insert(address);
356
357 std::vector<char> device = processEeprom(i2cBus, address);
358 if (!device.empty())
359 {
360 devices->emplace(address, device);
361 }
362 }
363
364 return foundList;
365}
366
Patrick Venture4f47fe62019-08-08 16:30:38 -0700367int getBusFrus(int file, int first, int last, int bus,
368 std::shared_ptr<DeviceMap> devices)
James Feist3cb5fec2018-01-23 14:41:51 -0800369{
James Feist3cb5fec2018-01-23 14:41:51 -0800370
James Feist26c27ad2018-07-25 15:09:40 -0700371 std::future<int> future = std::async(std::launch::async, [&]() {
Patrick Venture4f47fe62019-08-08 16:30:38 -0700372 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData;
Vijay Khemka2d681f62018-11-06 15:51:00 -0800373
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700374 // NOTE: When reading the devices raw on the bus, it can interfere with
375 // the driver's ability to operate, therefore read eeproms first before
376 // scanning for devices without drivers. Several experiments were run
377 // and it was determined that if there were any devices on the bus
378 // before the eeprom was hit and read, the eeprom driver wouldn't open
379 // while the bus device was open. An experiment was not performed to see
380 // if this issue was resolved if the i2c bus device was closed, but
381 // hexdumps of the eeprom later were successful.
382
383 // Scan for i2c eeproms loaded on this bus.
384 std::set<int> skipList = findI2CEeproms(bus, devices);
385
James Feist26c27ad2018-07-25 15:09:40 -0700386 for (int ii = first; ii <= last; ii++)
James Feist3cb5fec2018-01-23 14:41:51 -0800387 {
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700388 if (skipList.find(ii) != skipList.end())
389 {
390 continue;
391 }
James Feist3cb5fec2018-01-23 14:41:51 -0800392
James Feist26c27ad2018-07-25 15:09:40 -0700393 // Set slave address
394 if (ioctl(file, I2C_SLAVE_FORCE, ii) < 0)
James Feist3cb5fec2018-01-23 14:41:51 -0800395 {
Patrick Venture98e0cf32019-08-02 11:11:03 -0700396 std::cerr << "device at bus " << bus << " register " << ii
Patrick Venture5d8b61d2019-08-06 12:36:10 -0700397 << " busy\n";
James Feist26c27ad2018-07-25 15:09:40 -0700398 continue;
399 }
400 // probe
401 else if (i2c_smbus_read_byte(file) < 0)
402 {
403 continue;
404 }
James Feist3cb5fec2018-01-23 14:41:51 -0800405
James Feist26c27ad2018-07-25 15:09:40 -0700406 if (DEBUG)
407 {
Patrick Venture98e0cf32019-08-02 11:11:03 -0700408 std::cout << "something at bus " << bus << " addr " << ii
James Feist26c27ad2018-07-25 15:09:40 -0700409 << "\n";
410 }
Vijay Khemka2d681f62018-11-06 15:51:00 -0800411
412 /* Check for Device type if it is 8 bit or 16 bit */
413 int flag = isDevice16Bit(file);
414 if (flag < 0)
415 {
416 std::cerr << "failed to read bus " << bus << " address " << ii
417 << "\n";
418 continue;
419 }
420
Patrick Venture4f47fe62019-08-08 16:30:38 -0700421 if (readBlockData(flag, file, 0x0, 0x8, blockData.data()) < 0)
James Feist26c27ad2018-07-25 15:09:40 -0700422 {
423 std::cerr << "failed to read bus " << bus << " address " << ii
424 << "\n";
425 continue;
426 }
James Feist26c27ad2018-07-25 15:09:40 -0700427
428 // check the header checksum
Patrick Venture4f47fe62019-08-08 16:30:38 -0700429 if (!validateHeader(blockData))
James Feist26c27ad2018-07-25 15:09:40 -0700430 {
Patrick Venture786f1792019-08-05 16:33:44 -0700431 if (DEBUG)
James Feist26c27ad2018-07-25 15:09:40 -0700432 {
Patrick Venture786f1792019-08-05 16:33:44 -0700433 std::cerr << "Illegal header at bus " << bus << " address "
434 << ii << "\n";
435 }
436 continue;
437 }
438
439 std::vector<char> device;
Patrick Venture4f47fe62019-08-08 16:30:38 -0700440 device.insert(device.end(), blockData.begin(),
441 blockData.begin() + 8);
Patrick Venture786f1792019-08-05 16:33:44 -0700442
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700443 int fruLength = 0;
Patrick Venture786f1792019-08-05 16:33:44 -0700444 for (size_t jj = 1; jj <= FRU_AREAS.size(); jj++)
445 {
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700446 // TODO: offset can be 255, device is holding "chars" that's not
447 // good.
Patrick Venture4f47fe62019-08-08 16:30:38 -0700448 int areaOffset = device[jj];
449 if (areaOffset == 0)
Patrick Venture786f1792019-08-05 16:33:44 -0700450 {
Patrick Venture64fd7e22019-08-05 16:38:20 -0700451 continue;
452 }
453
Patrick Venture4f47fe62019-08-08 16:30:38 -0700454 areaOffset *= 8;
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700455
Patrick Venture4f47fe62019-08-08 16:30:38 -0700456 if (readBlockData(flag, file, static_cast<uint16_t>(areaOffset),
457 0x2, blockData.data()) < 0)
Patrick Venture64fd7e22019-08-05 16:38:20 -0700458 {
459 std::cerr << "failed to read bus " << bus << " address "
460 << ii << "\n";
461 return -1;
462 }
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700463
464 // Ignore data type.
Patrick Venture4f47fe62019-08-08 16:30:38 -0700465 int length = blockData[1] * 8;
466 areaOffset += length;
467 fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700468 }
Patrick Venture64fd7e22019-08-05 16:38:20 -0700469
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700470 // You already copied these first 8 bytes (the ipmi fru header size)
471 fruLength -= 8;
472
473 int readOffset = 8;
474
475 while (fruLength > 0)
476 {
Patrick Venture4f47fe62019-08-08 16:30:38 -0700477 int requestLength = std::min(I2C_SMBUS_BLOCK_MAX, fruLength);
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700478
Patrick Venture4f47fe62019-08-08 16:30:38 -0700479 if (readBlockData(flag, file, static_cast<uint16_t>(readOffset),
480 static_cast<uint8_t>(requestLength),
481 blockData.data()) < 0)
Patrick Venture64fd7e22019-08-05 16:38:20 -0700482 {
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700483 std::cerr << "failed to read bus " << bus << " address "
484 << ii << "\n";
485 return -1;
James Feist3cb5fec2018-01-23 14:41:51 -0800486 }
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700487
Patrick Venture4f47fe62019-08-08 16:30:38 -0700488 device.insert(device.end(), blockData.begin(),
489 blockData.begin() + requestLength);
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700490
Patrick Venture4f47fe62019-08-08 16:30:38 -0700491 readOffset += requestLength;
492 fruLength -= requestLength;
James Feist3cb5fec2018-01-23 14:41:51 -0800493 }
Patrick Venture786f1792019-08-05 16:33:44 -0700494 devices->emplace(ii, device);
James Feist3cb5fec2018-01-23 14:41:51 -0800495 }
James Feist26c27ad2018-07-25 15:09:40 -0700496 return 1;
497 });
498 std::future_status status =
499 future.wait_for(std::chrono::seconds(busTimeoutSeconds));
500 if (status == std::future_status::timeout)
501 {
502 std::cerr << "Error reading bus " << bus << "\n";
James Feist444830e2019-04-05 08:38:16 -0700503 busBlacklist.insert(bus);
504 close(file);
James Feist26c27ad2018-07-25 15:09:40 -0700505 return -1;
James Feist3cb5fec2018-01-23 14:41:51 -0800506 }
507
James Feist444830e2019-04-05 08:38:16 -0700508 close(file);
James Feist26c27ad2018-07-25 15:09:40 -0700509 return future.get();
James Feist3cb5fec2018-01-23 14:41:51 -0800510}
511
Patrick Venture11f1ff42019-08-01 10:42:12 -0700512void loadBlacklist(const char* path)
513{
514 std::ifstream blacklistStream(path);
515 if (!blacklistStream.good())
516 {
517 // File is optional.
518 std::cerr << "Cannot open blacklist file.\n\n";
519 return;
520 }
521
522 nlohmann::json data =
523 nlohmann::json::parse(blacklistStream, nullptr, false);
524 if (data.is_discarded())
525 {
526 std::cerr << "Illegal blacklist file detected, cannot validate JSON, "
527 "exiting\n";
528 std::exit(EXIT_FAILURE);
Patrick Venture11f1ff42019-08-01 10:42:12 -0700529 }
530
531 // It's expected to have at least one field, "buses" that is an array of the
532 // buses by integer. Allow for future options to exclude further aspects,
533 // such as specific addresses or ranges.
534 if (data.type() != nlohmann::json::value_t::object)
535 {
536 std::cerr << "Illegal blacklist, expected to read dictionary\n";
537 std::exit(EXIT_FAILURE);
Patrick Venture11f1ff42019-08-01 10:42:12 -0700538 }
539
540 // If buses field is missing, that's fine.
541 if (data.count("buses") == 1)
542 {
543 // Parse the buses array after a little validation.
544 auto buses = data.at("buses");
545 if (buses.type() != nlohmann::json::value_t::array)
546 {
547 // Buses field present but invalid, therefore this is an error.
548 std::cerr << "Invalid contents for blacklist buses field\n";
549 std::exit(EXIT_FAILURE);
Patrick Venture11f1ff42019-08-01 10:42:12 -0700550 }
551
552 // Catch exception here for type mis-match.
553 try
554 {
555 for (const auto& bus : buses)
556 {
557 busBlacklist.insert(bus.get<size_t>());
558 }
559 }
560 catch (const nlohmann::detail::type_error& e)
561 {
562 // Type mis-match is a critical error.
563 std::cerr << "Invalid bus type: " << e.what() << "\n";
564 std::exit(EXIT_FAILURE);
Patrick Venture11f1ff42019-08-01 10:42:12 -0700565 }
566 }
567
568 return;
569}
570
James Feista465ccc2019-02-08 12:51:01 -0800571static void FindI2CDevices(const std::vector<fs::path>& i2cBuses,
James Feist98132792019-07-09 13:29:09 -0700572 BusMap& busmap)
James Feist3cb5fec2018-01-23 14:41:51 -0800573{
James Feista465ccc2019-02-08 12:51:01 -0800574 for (auto& i2cBus : i2cBuses)
James Feist3cb5fec2018-01-23 14:41:51 -0800575 {
576 auto busnum = i2cBus.string();
577 auto lastDash = busnum.rfind(std::string("-"));
578 // delete everything before dash inclusive
579 if (lastDash != std::string::npos)
580 {
581 busnum.erase(0, lastDash + 1);
582 }
583 auto bus = std::stoi(busnum);
James Feist444830e2019-04-05 08:38:16 -0700584 if (busBlacklist.find(bus) != busBlacklist.end())
585 {
586 continue; // skip previously failed busses
587 }
James Feist3cb5fec2018-01-23 14:41:51 -0800588
589 auto file = open(i2cBus.c_str(), O_RDWR);
590 if (file < 0)
591 {
592 std::cerr << "unable to open i2c device " << i2cBus.string()
593 << "\n";
594 continue;
595 }
596 unsigned long funcs = 0;
597
598 if (ioctl(file, I2C_FUNCS, &funcs) < 0)
599 {
600 std::cerr
Patrick Venture98e0cf32019-08-02 11:11:03 -0700601 << "Error: Could not get the adapter functionality matrix bus "
James Feist3cb5fec2018-01-23 14:41:51 -0800602 << bus << "\n";
603 continue;
604 }
605 if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE) ||
606 !(I2C_FUNC_SMBUS_READ_I2C_BLOCK))
607 {
608 std::cerr << "Error: Can't use SMBus Receive Byte command bus "
609 << bus << "\n";
610 continue;
611 }
James Feist98132792019-07-09 13:29:09 -0700612 auto& device = busmap[bus];
James Feist3cb5fec2018-01-23 14:41:51 -0800613 device = std::make_shared<DeviceMap>();
614
Nikhil Potaded8884f12019-03-27 13:27:13 -0700615 // i2cdetect by default uses the range 0x03 to 0x77, as
616 // this is what we have tested with, use this range. Could be
617 // changed in future.
618 if (DEBUG)
James Feistc95cb142018-02-26 10:41:42 -0800619 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700620 std::cerr << "Scanning bus " << bus << "\n";
James Feistc95cb142018-02-26 10:41:42 -0800621 }
Nikhil Potaded8884f12019-03-27 13:27:13 -0700622
623 // fd is closed in this function in case the bus locks up
Patrick Venture4f47fe62019-08-08 16:30:38 -0700624 getBusFrus(file, 0x03, 0x77, bus, device);
Nikhil Potaded8884f12019-03-27 13:27:13 -0700625
626 if (DEBUG)
James Feistc95cb142018-02-26 10:41:42 -0800627 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700628 std::cerr << "Done scanning bus " << bus << "\n";
James Feistc95cb142018-02-26 10:41:42 -0800629 }
James Feist3cb5fec2018-01-23 14:41:51 -0800630 }
James Feist3cb5fec2018-01-23 14:41:51 -0800631}
632
James Feist6ebf9de2018-05-15 15:01:17 -0700633// this class allows an async response after all i2c devices are discovered
634struct FindDevicesWithCallback
635 : std::enable_shared_from_this<FindDevicesWithCallback>
636{
James Feista465ccc2019-02-08 12:51:01 -0800637 FindDevicesWithCallback(const std::vector<fs::path>& i2cBuses,
James Feist98132792019-07-09 13:29:09 -0700638 boost::asio::io_service& io, BusMap& busmap,
James Feista465ccc2019-02-08 12:51:01 -0800639 std::function<void(void)>&& callback) :
James Feist6ebf9de2018-05-15 15:01:17 -0700640 _i2cBuses(i2cBuses),
James Feist98132792019-07-09 13:29:09 -0700641 _io(io), _busMap(busmap), _callback(std::move(callback))
James Feist6ebf9de2018-05-15 15:01:17 -0700642 {
643 }
644 ~FindDevicesWithCallback()
645 {
646 _callback();
647 }
648 void run()
649 {
James Feist98132792019-07-09 13:29:09 -0700650 FindI2CDevices(_i2cBuses, _busMap);
James Feist6ebf9de2018-05-15 15:01:17 -0700651 }
652
James Feista465ccc2019-02-08 12:51:01 -0800653 const std::vector<fs::path>& _i2cBuses;
654 boost::asio::io_service& _io;
655 BusMap& _busMap;
James Feist6ebf9de2018-05-15 15:01:17 -0700656 std::function<void(void)> _callback;
657};
658
James Feist3cb5fec2018-01-23 14:41:51 -0800659static const std::tm intelEpoch(void)
660{
James Feist98132792019-07-09 13:29:09 -0700661 std::tm val = {};
James Feist3cb5fec2018-01-23 14:41:51 -0800662 val.tm_year = 1996 - 1900;
663 return val;
664}
665
James Feista465ccc2019-02-08 12:51:01 -0800666bool formatFru(const std::vector<char>& fruBytes,
667 boost::container::flat_map<std::string, std::string>& result)
James Feist3cb5fec2018-01-23 14:41:51 -0800668{
James Feista465ccc2019-02-08 12:51:01 -0800669 static const std::vector<const char*> CHASSIS_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800670 "PART_NUMBER", "SERIAL_NUMBER", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800671
James Feista465ccc2019-02-08 12:51:01 -0800672 static const std::vector<const char*> BOARD_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800673 "MANUFACTURER", "PRODUCT_NAME", "SERIAL_NUMBER", "PART_NUMBER",
674 "FRU_VERSION_ID", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800675
James Feista465ccc2019-02-08 12:51:01 -0800676 static const std::vector<const char*> PRODUCT_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800677 "MANUFACTURER", "PRODUCT_NAME", "PART_NUMBER",
678 "VERSION", "SERIAL_NUMBER", "ASSET_TAG",
679 "FRU_VERSION_ID", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800680
James Feistd068e932018-09-20 10:53:07 -0700681 if (fruBytes.size() <= 8)
James Feist3cb5fec2018-01-23 14:41:51 -0800682 {
683 return false;
684 }
685 std::vector<char>::const_iterator fruAreaOffsetField = fruBytes.begin();
James Feist9eb0b582018-04-27 12:15:46 -0700686 result["Common_Format_Version"] =
James Feist3cb5fec2018-01-23 14:41:51 -0800687 std::to_string(static_cast<int>(*fruAreaOffsetField));
688
James Feista465ccc2019-02-08 12:51:01 -0800689 const std::vector<const char*>* fieldData;
James Feist3cb5fec2018-01-23 14:41:51 -0800690
James Feist0eb40352019-04-09 14:44:04 -0700691 for (const std::string& area : FRU_AREAS)
James Feist3cb5fec2018-01-23 14:41:51 -0800692 {
693 fruAreaOffsetField++;
694 if (fruAreaOffsetField >= fruBytes.end())
695 {
696 return false;
697 }
698 size_t offset = (*fruAreaOffsetField) * 8;
699
700 if (offset > 1)
701 {
702 // +2 to skip format and length
703 std::vector<char>::const_iterator fruBytesIter =
704 fruBytes.begin() + offset + 2;
705
706 if (fruBytesIter >= fruBytes.end())
707 {
708 return false;
709 }
710
711 if (area == "CHASSIS")
712 {
713 result["CHASSIS_TYPE"] =
714 std::to_string(static_cast<int>(*fruBytesIter));
715 fruBytesIter += 1;
716 fieldData = &CHASSIS_FRU_AREAS;
717 }
718 else if (area == "BOARD")
719 {
720 result["BOARD_LANGUAGE_CODE"] =
721 std::to_string(static_cast<int>(*fruBytesIter));
722 fruBytesIter += 1;
723 if (fruBytesIter >= fruBytes.end())
724 {
725 return false;
726 }
727
728 unsigned int minutes = *fruBytesIter |
729 *(fruBytesIter + 1) << 8 |
730 *(fruBytesIter + 2) << 16;
731 std::tm fruTime = intelEpoch();
Patrick Venturee0e6f5f2019-08-12 19:00:11 -0700732 std::time_t timeValue = std::mktime(&fruTime);
James Feist3cb5fec2018-01-23 14:41:51 -0800733 timeValue += minutes * 60;
Patrick Venturee0e6f5f2019-08-12 19:00:11 -0700734 fruTime = *std::gmtime(&timeValue);
James Feist3cb5fec2018-01-23 14:41:51 -0800735
Patrick Venturee0e6f5f2019-08-12 19:00:11 -0700736 // Tue Nov 20 23:08:00 2018
737 char timeString[32] = {0};
738 auto bytes = std::strftime(timeString, sizeof(timeString),
Patrick Venturefff050a2019-08-13 11:44:30 -0700739 "%Y-%m-%d - %H:%M:%S", &fruTime);
Patrick Venturee0e6f5f2019-08-12 19:00:11 -0700740 if (bytes == 0)
741 {
742 std::cerr << "invalid time string encountered\n";
743 return false;
744 }
745
746 result["BOARD_MANUFACTURE_DATE"] = std::string(timeString);
James Feist3cb5fec2018-01-23 14:41:51 -0800747 fruBytesIter += 3;
748 fieldData = &BOARD_FRU_AREAS;
749 }
750 else if (area == "PRODUCT")
751 {
752 result["PRODUCT_LANGUAGE_CODE"] =
753 std::to_string(static_cast<int>(*fruBytesIter));
754 fruBytesIter += 1;
755 fieldData = &PRODUCT_FRU_AREAS;
756 }
757 else
758 {
759 continue;
760 }
James Feista465ccc2019-02-08 12:51:01 -0800761 for (auto& field : *fieldData)
James Feist3cb5fec2018-01-23 14:41:51 -0800762 {
763 if (fruBytesIter >= fruBytes.end())
764 {
765 return false;
766 }
767
Vijay Khemka5d5de442018-11-07 10:51:25 -0800768 /* Checking for last byte C1 to indicate that no more
769 * field to be read */
James Feist98132792019-07-09 13:29:09 -0700770 if (static_cast<uint8_t>(*fruBytesIter) == 0xC1)
Vijay Khemka5d5de442018-11-07 10:51:25 -0800771 {
772 break;
773 }
774
Ed Tanous2147e672019-02-27 13:59:56 -0800775 size_t length = *fruBytesIter & 0x3f;
776 fruBytesIter += 1;
777
James Feist3cb5fec2018-01-23 14:41:51 -0800778 if (fruBytesIter >= fruBytes.end())
779 {
780 return false;
781 }
Ed Tanous2147e672019-02-27 13:59:56 -0800782 std::string value(fruBytesIter, fruBytesIter + length);
James Feist3cb5fec2018-01-23 14:41:51 -0800783
Ed Tanous2147e672019-02-27 13:59:56 -0800784 // Strip non null characters from the end
785 value.erase(std::find_if(value.rbegin(), value.rend(),
786 [](char ch) { return ch != 0; })
787 .base(),
788 value.end());
789
James Feist0eb40352019-04-09 14:44:04 -0700790 result[area + "_" + field] = std::move(value);
Ed Tanous2147e672019-02-27 13:59:56 -0800791
James Feist3cb5fec2018-01-23 14:41:51 -0800792 fruBytesIter += length;
793 if (fruBytesIter >= fruBytes.end())
794 {
795 std::cerr << "Warning Fru Length Mismatch:\n ";
James Feista465ccc2019-02-08 12:51:01 -0800796 for (auto& c : fruBytes)
James Feist3cb5fec2018-01-23 14:41:51 -0800797 {
798 std::cerr << c;
799 }
800 std::cerr << "\n";
801 if (DEBUG)
802 {
James Feista465ccc2019-02-08 12:51:01 -0800803 for (auto& keyPair : result)
James Feist3cb5fec2018-01-23 14:41:51 -0800804 {
805 std::cerr << keyPair.first << " : "
806 << keyPair.second << "\n";
807 }
808 }
809 return false;
810 }
811 }
812 }
813 }
814
815 return true;
816}
817
Nikhil Potaded8884f12019-03-27 13:27:13 -0700818std::vector<uint8_t>& getFruInfo(const uint8_t& bus, const uint8_t& address)
819{
820 auto deviceMap = busMap.find(bus);
821 if (deviceMap == busMap.end())
822 {
823 throw std::invalid_argument("Invalid Bus.");
824 }
825 auto device = deviceMap->second->find(address);
826 if (device == deviceMap->second->end())
827 {
828 throw std::invalid_argument("Invalid Address.");
829 }
830 std::vector<uint8_t>& ret =
831 reinterpret_cast<std::vector<uint8_t>&>(device->second);
832
833 return ret;
834}
835
James Feist3cb5fec2018-01-23 14:41:51 -0800836void AddFruObjectToDbus(
James Feista465ccc2019-02-08 12:51:01 -0800837 std::vector<char>& device, sdbusplus::asio::object_server& objServer,
838 boost::container::flat_map<
839 std::pair<size_t, size_t>,
840 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
James Feist13b86d62018-05-29 11:24:35 -0700841 uint32_t bus, uint32_t address)
James Feist3cb5fec2018-01-23 14:41:51 -0800842{
843 boost::container::flat_map<std::string, std::string> formattedFru;
844 if (!formatFru(device, formattedFru))
845 {
Patrick Ventureb755c832019-08-07 11:09:14 -0700846 std::cerr << "failed to format fru for device at bus " << bus
847 << " address " << address << "\n";
James Feist3cb5fec2018-01-23 14:41:51 -0800848 return;
849 }
Patrick Venture96cdaef2019-07-30 13:30:52 -0700850
James Feist3cb5fec2018-01-23 14:41:51 -0800851 auto productNameFind = formattedFru.find("BOARD_PRODUCT_NAME");
852 std::string productName;
Patrick Venture96cdaef2019-07-30 13:30:52 -0700853 // Not found under Board section or an empty string.
854 if (productNameFind == formattedFru.end() ||
855 productNameFind->second.empty())
James Feist3cb5fec2018-01-23 14:41:51 -0800856 {
857 productNameFind = formattedFru.find("PRODUCT_PRODUCT_NAME");
858 }
Patrick Venture96cdaef2019-07-30 13:30:52 -0700859 // Found under Product section and not an empty string.
860 if (productNameFind != formattedFru.end() &&
861 !productNameFind->second.empty())
James Feist3cb5fec2018-01-23 14:41:51 -0800862 {
863 productName = productNameFind->second;
James Feist3f8a2782018-02-12 09:24:42 -0800864 std::regex illegalObject("[^A-Za-z0-9_]");
865 productName = std::regex_replace(productName, illegalObject, "_");
James Feist3cb5fec2018-01-23 14:41:51 -0800866 }
867 else
868 {
869 productName = "UNKNOWN" + std::to_string(UNKNOWN_BUS_OBJECT_COUNT);
870 UNKNOWN_BUS_OBJECT_COUNT++;
871 }
872
James Feist918e18c2018-02-13 15:51:07 -0800873 productName = "/xyz/openbmc_project/FruDevice/" + productName;
James Feist918e18c2018-02-13 15:51:07 -0800874 // avoid duplicates by checking to see if on a mux
James Feist79e9c0b2018-03-15 15:21:17 -0700875 if (bus > 0)
James Feist918e18c2018-02-13 15:51:07 -0800876 {
Patrick Venture015fb0a2019-08-16 09:33:31 -0700877 int highest = -1;
878 bool found = false;
879
James Feista465ccc2019-02-08 12:51:01 -0800880 for (auto const& busIface : dbusInterfaceMap)
James Feist918e18c2018-02-13 15:51:07 -0800881 {
Patrick Venture015fb0a2019-08-16 09:33:31 -0700882 std::string path = busIface.second->get_object_path();
883 if (std::regex_match(path, std::regex(productName + "(_\\d+|)$")))
James Feist918e18c2018-02-13 15:51:07 -0800884 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700885 if (isMuxBus(bus) && address == busIface.first.second &&
James Feist98132792019-07-09 13:29:09 -0700886 (getFruInfo(static_cast<uint8_t>(busIface.first.first),
887 static_cast<uint8_t>(busIface.first.second)) ==
888 getFruInfo(static_cast<uint8_t>(bus),
889 static_cast<uint8_t>(address))))
James Feist79e9c0b2018-03-15 15:21:17 -0700890 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700891 // This device is already added to the lower numbered bus,
892 // do not replicate it.
893 return;
James Feist79e9c0b2018-03-15 15:21:17 -0700894 }
Patrick Venture015fb0a2019-08-16 09:33:31 -0700895
896 // Check if the match named has extra information.
897 found = true;
898 std::smatch base_match;
899
900 bool match = std::regex_match(
901 path, base_match, std::regex(productName + "_(\\d+)$"));
902 if (match)
James Feist79e9c0b2018-03-15 15:21:17 -0700903 {
Patrick Venture015fb0a2019-08-16 09:33:31 -0700904 if (base_match.size() == 2)
905 {
906 std::ssub_match base_sub_match = base_match[1];
907 std::string base = base_sub_match.str();
908
909 int value = std::stoi(base);
910 highest = (value > highest) ? value : highest;
911 }
James Feist79e9c0b2018-03-15 15:21:17 -0700912 }
James Feist918e18c2018-02-13 15:51:07 -0800913 }
Patrick Venture015fb0a2019-08-16 09:33:31 -0700914 } // end searching objects
915
916 if (found)
917 {
918 // We found something with the same name. If highest was still -1,
919 // it means this new entry will be _0.
920 productName += "_";
921 productName += std::to_string(++highest);
James Feist918e18c2018-02-13 15:51:07 -0800922 }
923 }
James Feist3cb5fec2018-01-23 14:41:51 -0800924
James Feist9eb0b582018-04-27 12:15:46 -0700925 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
926 objServer.add_interface(productName, "xyz.openbmc_project.FruDevice");
927 dbusInterfaceMap[std::pair<size_t, size_t>(bus, address)] = iface;
928
James Feista465ccc2019-02-08 12:51:01 -0800929 for (auto& property : formattedFru)
James Feist3cb5fec2018-01-23 14:41:51 -0800930 {
James Feist9eb0b582018-04-27 12:15:46 -0700931
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -0700932 std::regex_replace(property.second.begin(), property.second.begin(),
933 property.second.end(), NON_ASCII_REGEX, "_");
James Feist9eb0b582018-04-27 12:15:46 -0700934 if (property.second.empty())
935 {
936 continue;
937 }
938 std::string key =
939 std::regex_replace(property.first, NON_ASCII_REGEX, "_");
940 if (!iface->register_property(key, property.second + '\0'))
941 {
942 std::cerr << "illegal key: " << key << "\n";
943 }
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -0700944 if (DEBUG)
945 {
946 std::cout << property.first << ": " << property.second << "\n";
947 }
James Feist3cb5fec2018-01-23 14:41:51 -0800948 }
James Feist2a9d6db2018-04-27 15:48:28 -0700949
950 // baseboard will be 0, 0
James Feist13b86d62018-05-29 11:24:35 -0700951 iface->register_property("BUS", bus);
952 iface->register_property("ADDRESS", address);
James Feist2a9d6db2018-04-27 15:48:28 -0700953
James Feist9eb0b582018-04-27 12:15:46 -0700954 iface->initialize();
James Feist3cb5fec2018-01-23 14:41:51 -0800955}
956
James Feista465ccc2019-02-08 12:51:01 -0800957static bool readBaseboardFru(std::vector<char>& baseboardFru)
James Feist3cb5fec2018-01-23 14:41:51 -0800958{
959 // try to read baseboard fru from file
960 std::ifstream baseboardFruFile(BASEBOARD_FRU_LOCATION, std::ios::binary);
961 if (baseboardFruFile.good())
962 {
963 baseboardFruFile.seekg(0, std::ios_base::end);
James Feist98132792019-07-09 13:29:09 -0700964 size_t fileSize = static_cast<size_t>(baseboardFruFile.tellg());
James Feist3cb5fec2018-01-23 14:41:51 -0800965 baseboardFru.resize(fileSize);
966 baseboardFruFile.seekg(0, std::ios_base::beg);
967 baseboardFruFile.read(baseboardFru.data(), fileSize);
968 }
969 else
970 {
971 return false;
972 }
973 return true;
974}
975
James Feista465ccc2019-02-08 12:51:01 -0800976bool writeFru(uint8_t bus, uint8_t address, const std::vector<uint8_t>& fru)
James Feistb49ffc32018-05-02 11:10:43 -0700977{
978 boost::container::flat_map<std::string, std::string> tmp;
979 if (fru.size() > MAX_FRU_SIZE)
980 {
981 std::cerr << "Invalid fru.size() during writeFru\n";
982 return false;
983 }
984 // verify legal fru by running it through fru parsing logic
James Feista465ccc2019-02-08 12:51:01 -0800985 if (!formatFru(reinterpret_cast<const std::vector<char>&>(fru), tmp))
James Feistb49ffc32018-05-02 11:10:43 -0700986 {
987 std::cerr << "Invalid fru format during writeFru\n";
988 return false;
989 }
990 // baseboard fru
991 if (bus == 0 && address == 0)
992 {
993 std::ofstream file(BASEBOARD_FRU_LOCATION, std::ios_base::binary);
994 if (!file.good())
995 {
996 std::cerr << "Error opening file " << BASEBOARD_FRU_LOCATION
997 << "\n";
James Feistddb78302018-09-06 11:45:42 -0700998 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -0700999 return false;
1000 }
James Feista465ccc2019-02-08 12:51:01 -08001001 file.write(reinterpret_cast<const char*>(fru.data()), fru.size());
James Feistb49ffc32018-05-02 11:10:43 -07001002 return file.good();
1003 }
1004 else
1005 {
Patrick Venturec50e1ff2019-08-06 10:22:28 -07001006 if (hasEepromFile(bus, address))
1007 {
1008 auto path = getEepromPath(bus, address);
1009 int eeprom = open(path.c_str(), O_RDWR | O_CLOEXEC);
1010 if (eeprom < 0)
1011 {
1012 std::cerr << "unable to open i2c device " << path << "\n";
1013 throw DBusInternalError();
1014 return false;
1015 }
1016
1017 ssize_t writtenBytes = write(eeprom, fru.data(), fru.size());
1018 if (writtenBytes < 0)
1019 {
1020 std::cerr << "unable to write to i2c device " << path << "\n";
1021 close(eeprom);
1022 throw DBusInternalError();
1023 return false;
1024 }
1025
1026 close(eeprom);
1027 return true;
1028 }
1029
James Feistb49ffc32018-05-02 11:10:43 -07001030 std::string i2cBus = "/dev/i2c-" + std::to_string(bus);
1031
1032 int file = open(i2cBus.c_str(), O_RDWR | O_CLOEXEC);
1033 if (file < 0)
1034 {
1035 std::cerr << "unable to open i2c device " << i2cBus << "\n";
James Feistddb78302018-09-06 11:45:42 -07001036 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001037 return false;
1038 }
1039 if (ioctl(file, I2C_SLAVE_FORCE, address) < 0)
1040 {
1041 std::cerr << "unable to set device address\n";
1042 close(file);
James Feistddb78302018-09-06 11:45:42 -07001043 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001044 return false;
1045 }
1046
1047 constexpr const size_t RETRY_MAX = 2;
1048 uint16_t index = 0;
1049 size_t retries = RETRY_MAX;
1050 while (index < fru.size())
1051 {
1052 if ((index && ((index % (MAX_EEPROM_PAGE_INDEX + 1)) == 0)) &&
1053 (retries == RETRY_MAX))
1054 {
1055 // The 4K EEPROM only uses the A2 and A1 device address bits
1056 // with the third bit being a memory page address bit.
1057 if (ioctl(file, I2C_SLAVE_FORCE, ++address) < 0)
1058 {
1059 std::cerr << "unable to set device address\n";
1060 close(file);
James Feistddb78302018-09-06 11:45:42 -07001061 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001062 return false;
1063 }
1064 }
1065
James Feist98132792019-07-09 13:29:09 -07001066 if (i2c_smbus_write_byte_data(file, static_cast<uint8_t>(index),
1067 fru[index]) < 0)
James Feistb49ffc32018-05-02 11:10:43 -07001068 {
1069 if (!retries--)
1070 {
1071 std::cerr << "error writing fru: " << strerror(errno)
1072 << "\n";
1073 close(file);
James Feistddb78302018-09-06 11:45:42 -07001074 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001075 return false;
1076 }
1077 }
1078 else
1079 {
1080 retries = RETRY_MAX;
1081 index++;
1082 }
1083 // most eeproms require 5-10ms between writes
1084 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1085 }
1086 close(file);
1087 return true;
1088 }
1089}
1090
James Feist9eb0b582018-04-27 12:15:46 -07001091void rescanBusses(
James Feist98132792019-07-09 13:29:09 -07001092 boost::asio::io_service& io, BusMap& busmap,
James Feista465ccc2019-02-08 12:51:01 -08001093 boost::container::flat_map<
1094 std::pair<size_t, size_t>,
1095 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
James Feista465ccc2019-02-08 12:51:01 -08001096 sdbusplus::asio::object_server& objServer)
James Feist918e18c2018-02-13 15:51:07 -08001097{
James Feist6ebf9de2018-05-15 15:01:17 -07001098 static boost::asio::deadline_timer timer(io);
1099 timer.expires_from_now(boost::posix_time::seconds(1));
James Feist918e18c2018-02-13 15:51:07 -08001100
Gunnar Mills6f0ae942018-08-31 12:38:03 -05001101 // setup an async wait in case we get flooded with requests
James Feist98132792019-07-09 13:29:09 -07001102 timer.async_wait([&](const boost::system::error_code&) {
James Feist4131aea2018-03-09 09:47:30 -08001103 auto devDir = fs::path("/dev/");
James Feist4131aea2018-03-09 09:47:30 -08001104 std::vector<fs::path> i2cBuses;
James Feist918e18c2018-02-13 15:51:07 -08001105
Nikhil Potaded8884f12019-03-27 13:27:13 -07001106 boost::container::flat_map<size_t, fs::path> busPaths;
1107 if (!getI2cDevicePaths(devDir, busPaths))
James Feist918e18c2018-02-13 15:51:07 -08001108 {
James Feist4131aea2018-03-09 09:47:30 -08001109 std::cerr << "unable to find i2c devices\n";
1110 return;
James Feist918e18c2018-02-13 15:51:07 -08001111 }
Nikhil Potaded8884f12019-03-27 13:27:13 -07001112
1113 for (auto busPath : busPaths)
1114 {
1115 i2cBuses.emplace_back(busPath.second);
1116 }
James Feist4131aea2018-03-09 09:47:30 -08001117
James Feist98132792019-07-09 13:29:09 -07001118 busmap.clear();
James Feist6ebf9de2018-05-15 15:01:17 -07001119 auto scan = std::make_shared<FindDevicesWithCallback>(
James Feist98132792019-07-09 13:29:09 -07001120 i2cBuses, io, busmap, [&]() {
James Feista465ccc2019-02-08 12:51:01 -08001121 for (auto& busIface : dbusInterfaceMap)
James Feist6ebf9de2018-05-15 15:01:17 -07001122 {
1123 objServer.remove_interface(busIface.second);
1124 }
James Feist4131aea2018-03-09 09:47:30 -08001125
James Feist6ebf9de2018-05-15 15:01:17 -07001126 dbusInterfaceMap.clear();
1127 UNKNOWN_BUS_OBJECT_COUNT = 0;
James Feist4131aea2018-03-09 09:47:30 -08001128
James Feist6ebf9de2018-05-15 15:01:17 -07001129 // todo, get this from a more sensable place
1130 std::vector<char> baseboardFru;
1131 if (readBaseboardFru(baseboardFru))
1132 {
1133 boost::container::flat_map<int, std::vector<char>>
1134 baseboardDev;
1135 baseboardDev.emplace(0, baseboardFru);
James Feist98132792019-07-09 13:29:09 -07001136 busmap[0] = std::make_shared<DeviceMap>(baseboardDev);
James Feist6ebf9de2018-05-15 15:01:17 -07001137 }
James Feist98132792019-07-09 13:29:09 -07001138 for (auto& devicemap : busmap)
James Feist6ebf9de2018-05-15 15:01:17 -07001139 {
James Feista465ccc2019-02-08 12:51:01 -08001140 for (auto& device : *devicemap.second)
James Feist6ebf9de2018-05-15 15:01:17 -07001141 {
James Feist98132792019-07-09 13:29:09 -07001142 AddFruObjectToDbus(device.second, objServer,
James Feist6ebf9de2018-05-15 15:01:17 -07001143 dbusInterfaceMap, devicemap.first,
1144 device.first);
1145 }
1146 }
1147 });
1148 scan->run();
1149 });
James Feist918e18c2018-02-13 15:51:07 -08001150}
1151
James Feist98132792019-07-09 13:29:09 -07001152int main()
James Feist3cb5fec2018-01-23 14:41:51 -08001153{
1154 auto devDir = fs::path("/dev/");
James Feistc9dff1b2019-02-13 13:33:13 -08001155 auto matchString = std::string(R"(i2c-\d+$)");
James Feist3cb5fec2018-01-23 14:41:51 -08001156 std::vector<fs::path> i2cBuses;
1157
James Feista3c180a2018-08-09 16:06:04 -07001158 if (!findFiles(devDir, matchString, i2cBuses))
James Feist3cb5fec2018-01-23 14:41:51 -08001159 {
1160 std::cerr << "unable to find i2c devices\n";
1161 return 1;
1162 }
James Feist3cb5fec2018-01-23 14:41:51 -08001163
Patrick Venture11f1ff42019-08-01 10:42:12 -07001164 // check for and load blacklist with initial buses.
1165 loadBlacklist(blacklistPath);
1166
James Feist3cb5fec2018-01-23 14:41:51 -08001167 boost::asio::io_service io;
James Feist9eb0b582018-04-27 12:15:46 -07001168 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
1169 auto objServer = sdbusplus::asio::object_server(systemBus);
Vijay Khemka065f6d92018-12-18 10:37:47 -08001170 systemBus->request_name("xyz.openbmc_project.FruDevice");
James Feist3cb5fec2018-01-23 14:41:51 -08001171
James Feist6ebf9de2018-05-15 15:01:17 -07001172 // this is a map with keys of pair(bus number, address) and values of
1173 // the object on dbus
James Feist3cb5fec2018-01-23 14:41:51 -08001174 boost::container::flat_map<std::pair<size_t, size_t>,
James Feist9eb0b582018-04-27 12:15:46 -07001175 std::shared_ptr<sdbusplus::asio::dbus_interface>>
1176 dbusInterfaceMap;
James Feist3cb5fec2018-01-23 14:41:51 -08001177
James Feist9eb0b582018-04-27 12:15:46 -07001178 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
1179 objServer.add_interface("/xyz/openbmc_project/FruDevice",
1180 "xyz.openbmc_project.FruDeviceManager");
James Feist3cb5fec2018-01-23 14:41:51 -08001181
1182 iface->register_method("ReScan", [&]() {
James Feist98132792019-07-09 13:29:09 -07001183 rescanBusses(io, busMap, dbusInterfaceMap, objServer);
James Feist3cb5fec2018-01-23 14:41:51 -08001184 });
James Feist2a9d6db2018-04-27 15:48:28 -07001185
Nikhil Potaded8884f12019-03-27 13:27:13 -07001186 iface->register_method("GetRawFru", getFruInfo);
James Feistb49ffc32018-05-02 11:10:43 -07001187
1188 iface->register_method("WriteFru", [&](const uint8_t bus,
1189 const uint8_t address,
James Feista465ccc2019-02-08 12:51:01 -08001190 const std::vector<uint8_t>& data) {
James Feistb49ffc32018-05-02 11:10:43 -07001191 if (!writeFru(bus, address, data))
1192 {
James Feistddb78302018-09-06 11:45:42 -07001193 throw std::invalid_argument("Invalid Arguments.");
James Feistb49ffc32018-05-02 11:10:43 -07001194 return;
1195 }
1196 // schedule rescan on success
James Feist98132792019-07-09 13:29:09 -07001197 rescanBusses(io, busMap, dbusInterfaceMap, objServer);
James Feistb49ffc32018-05-02 11:10:43 -07001198 });
James Feist9eb0b582018-04-27 12:15:46 -07001199 iface->initialize();
James Feist3cb5fec2018-01-23 14:41:51 -08001200
James Feist9eb0b582018-04-27 12:15:46 -07001201 std::function<void(sdbusplus::message::message & message)> eventHandler =
James Feista465ccc2019-02-08 12:51:01 -08001202 [&](sdbusplus::message::message& message) {
James Feist918e18c2018-02-13 15:51:07 -08001203 std::string objectName;
James Feist9eb0b582018-04-27 12:15:46 -07001204 boost::container::flat_map<
James Feista465ccc2019-02-08 12:51:01 -08001205 std::string,
1206 std::variant<std::string, bool, int64_t, uint64_t, double>>
James Feist9eb0b582018-04-27 12:15:46 -07001207 values;
1208 message.read(objectName, values);
James Feist0eeb79c2019-10-09 13:35:29 -07001209 auto findState = values.find("CurrentHostState");
1210 bool on = false;
1211 if (findState != values.end())
James Feist918e18c2018-02-13 15:51:07 -08001212 {
James Feist0eeb79c2019-10-09 13:35:29 -07001213 on = boost::ends_with(std::get<std::string>(findState->second),
1214 "Running");
1215 }
James Feist6ebf9de2018-05-15 15:01:17 -07001216
James Feist0eeb79c2019-10-09 13:35:29 -07001217 if (on)
1218 {
James Feist98132792019-07-09 13:29:09 -07001219 rescanBusses(io, busMap, dbusInterfaceMap, objServer);
James Feist918e18c2018-02-13 15:51:07 -08001220 }
James Feist918e18c2018-02-13 15:51:07 -08001221 };
James Feist9eb0b582018-04-27 12:15:46 -07001222
1223 sdbusplus::bus::match::match powerMatch = sdbusplus::bus::match::match(
James Feista465ccc2019-02-08 12:51:01 -08001224 static_cast<sdbusplus::bus::bus&>(*systemBus),
James Feist7bcd3f22019-03-18 16:04:04 -07001225 "type='signal',interface='org.freedesktop.DBus.Properties',path='/xyz/"
James Feist0eeb79c2019-10-09 13:35:29 -07001226 "openbmc_project/state/"
1227 "host0',arg0='xyz.openbmc_project.State.Host'",
James Feist9eb0b582018-04-27 12:15:46 -07001228 eventHandler);
1229
James Feist4131aea2018-03-09 09:47:30 -08001230 int fd = inotify_init();
James Feist0eb40352019-04-09 14:44:04 -07001231 inotify_add_watch(fd, I2C_DEV_LOCATION,
1232 IN_CREATE | IN_MOVED_TO | IN_DELETE);
James Feist4131aea2018-03-09 09:47:30 -08001233 std::array<char, 4096> readBuffer;
1234 std::string pendingBuffer;
1235 // monitor for new i2c devices
1236 boost::asio::posix::stream_descriptor dirWatch(io, fd);
1237 std::function<void(const boost::system::error_code, std::size_t)>
James Feista465ccc2019-02-08 12:51:01 -08001238 watchI2cBusses = [&](const boost::system::error_code& ec,
James Feist4131aea2018-03-09 09:47:30 -08001239 std::size_t bytes_transferred) {
1240 if (ec)
1241 {
1242 std::cout << "Callback Error " << ec << "\n";
1243 return;
1244 }
1245 pendingBuffer += std::string(readBuffer.data(), bytes_transferred);
1246 bool devChange = false;
1247 while (pendingBuffer.size() > sizeof(inotify_event))
1248 {
James Feista465ccc2019-02-08 12:51:01 -08001249 const inotify_event* iEvent =
1250 reinterpret_cast<const inotify_event*>(
James Feist4131aea2018-03-09 09:47:30 -08001251 pendingBuffer.data());
1252 switch (iEvent->mask)
1253 {
James Feist9eb0b582018-04-27 12:15:46 -07001254 case IN_CREATE:
1255 case IN_MOVED_TO:
1256 case IN_DELETE:
1257 if (boost::starts_with(std::string(iEvent->name),
1258 "i2c"))
1259 {
1260 devChange = true;
1261 }
James Feist4131aea2018-03-09 09:47:30 -08001262 }
1263
1264 pendingBuffer.erase(0, sizeof(inotify_event) + iEvent->len);
1265 }
James Feist6ebf9de2018-05-15 15:01:17 -07001266 if (devChange)
James Feist4131aea2018-03-09 09:47:30 -08001267 {
James Feist98132792019-07-09 13:29:09 -07001268 rescanBusses(io, busMap, dbusInterfaceMap, objServer);
James Feist4131aea2018-03-09 09:47:30 -08001269 }
James Feist6ebf9de2018-05-15 15:01:17 -07001270
James Feist4131aea2018-03-09 09:47:30 -08001271 dirWatch.async_read_some(boost::asio::buffer(readBuffer),
1272 watchI2cBusses);
1273 };
1274
1275 dirWatch.async_read_some(boost::asio::buffer(readBuffer), watchI2cBusses);
Gunnar Millsb3e42fe2018-06-13 15:48:27 -05001276 // run the initial scan
James Feist98132792019-07-09 13:29:09 -07001277 rescanBusses(io, busMap, dbusInterfaceMap, objServer);
James Feist918e18c2018-02-13 15:51:07 -08001278
James Feist3cb5fec2018-01-23 14:41:51 -08001279 io.run();
1280 return 0;
1281}