blob: 005ca67bc7bd15dad1132c80a3ebd0e279be21fb [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
James Feist5cb06082019-10-24 17:11:13 -070072boost::asio::io_service io;
73auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
74auto objServer = sdbusplus::asio::object_server(systemBus);
75
Patrick Venturec50e1ff2019-08-06 10:22:28 -070076// Given a bus/address, produce the path in sysfs for an eeprom.
77static std::string getEepromPath(size_t bus, size_t address)
78{
79 std::stringstream output;
80 output << "/sys/bus/i2c/devices/" << bus << "-" << std::right
81 << std::setfill('0') << std::setw(4) << std::hex << address
82 << "/eeprom";
83 return output.str();
84}
85
86static bool hasEepromFile(size_t bus, size_t address)
87{
88 auto path = getEepromPath(bus, address);
89 try
90 {
91 return fs::exists(path);
92 }
93 catch (...)
94 {
95 return false;
96 }
97}
98
99static ssize_t readFromEeprom(int fd, uint16_t offset, uint8_t len,
100 uint8_t* buf)
101{
102 auto result = lseek(fd, offset, SEEK_SET);
103 if (result < 0)
104 {
105 std::cerr << "failed to seek\n";
106 return -1;
107 }
108
109 return read(fd, buf, len);
110}
111
James Feistc95cb142018-02-26 10:41:42 -0800112static bool isMuxBus(size_t bus)
113{
Ed Tanous072e25d2018-12-16 21:45:20 -0800114 return is_symlink(std::filesystem::path(
James Feistc95cb142018-02-26 10:41:42 -0800115 "/sys/bus/i2c/devices/i2c-" + std::to_string(bus) + "/mux_device"));
116}
117
Vijay Khemka2d681f62018-11-06 15:51:00 -0800118static int isDevice16Bit(int file)
119{
120 /* Get first byte */
121 int byte1 = i2c_smbus_read_byte_data(file, 0);
122 if (byte1 < 0)
123 {
124 return byte1;
125 }
126 /* Read 7 more bytes, it will read same first byte in case of
127 * 8 bit but it will read next byte in case of 16 bit
128 */
129 for (int i = 0; i < 7; i++)
130 {
131 int byte2 = i2c_smbus_read_byte_data(file, 0);
132 if (byte2 < 0)
133 {
134 return byte2;
135 }
136 if (byte2 != byte1)
137 {
138 return 1;
139 }
140 }
141 return 0;
142}
143
Patrick Venture4f47fe62019-08-08 16:30:38 -0700144static int readBlockData(int flag, int file, uint16_t offset, uint8_t len,
145 uint8_t* buf)
Vijay Khemka2d681f62018-11-06 15:51:00 -0800146{
Patrick Venture4f47fe62019-08-08 16:30:38 -0700147 uint8_t lowAddr = static_cast<uint8_t>(offset);
148 uint8_t highAddr = static_cast<uint8_t>(offset >> 8);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800149
150 if (flag == 0)
151 {
Patrick Venture4f47fe62019-08-08 16:30:38 -0700152 return i2c_smbus_read_i2c_block_data(file, lowAddr, len, buf);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800153 }
154
155 /* This is for 16 bit addressing EEPROM device. First an offset
156 * needs to be written before read data from a offset
157 */
Patrick Venture4f47fe62019-08-08 16:30:38 -0700158 int ret = i2c_smbus_write_byte_data(file, 0, lowAddr);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800159 if (ret < 0)
160 {
161 return ret;
162 }
163
Patrick Venture4f47fe62019-08-08 16:30:38 -0700164 return i2c_smbus_read_i2c_block_data(file, highAddr, len, buf);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800165}
166
James Feist24bae7a2019-04-03 09:50:56 -0700167bool validateHeader(const std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData)
168{
169 // ipmi spec format version number is currently at 1, verify it
170 if (blockData[0] != 0x1)
171 {
172 return false;
173 }
174
175 // verify pad is set to 0
176 if (blockData[6] != 0x0)
177 {
178 return false;
179 }
180
181 // verify offsets are 0, or don't point to another offset
182 std::set<uint8_t> foundOffsets;
183 for (int ii = 1; ii < 6; ii++)
184 {
185 if (blockData[ii] == 0)
186 {
187 continue;
188 }
James Feist0eb40352019-04-09 14:44:04 -0700189 auto inserted = foundOffsets.insert(blockData[ii]);
190 if (!inserted.second)
James Feist24bae7a2019-04-03 09:50:56 -0700191 {
192 return false;
193 }
194 }
195
196 // validate checksum
197 size_t sum = 0;
198 for (int jj = 0; jj < 7; jj++)
199 {
200 sum += blockData[jj];
201 }
202 sum = (256 - sum) & 0xFF;
203
204 if (sum != blockData[7])
205 {
206 return false;
207 }
208 return true;
209}
210
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700211// TODO: This code is very similar to the non-eeprom version and can be merged
212// with some tweaks.
213static std::vector<char> processEeprom(int bus, int address)
214{
215 std::vector<char> device;
Patrick Venture4f47fe62019-08-08 16:30:38 -0700216 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData;
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700217
218 auto path = getEepromPath(bus, address);
219
220 int file = open(path.c_str(), O_RDONLY);
221 if (file < 0)
222 {
223 std::cerr << "Unable to open eeprom file: " << path << "\n";
224 return device;
225 }
226
Patrick Venture4f47fe62019-08-08 16:30:38 -0700227 ssize_t readBytes = readFromEeprom(file, 0, 0x8, blockData.data());
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700228 if (readBytes < 0)
229 {
230 std::cerr << "failed to read eeprom at " << bus << " address "
231 << address << "\n";
232 close(file);
233 return device;
234 }
235
Patrick Venture4f47fe62019-08-08 16:30:38 -0700236 if (!validateHeader(blockData))
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700237 {
238 if (DEBUG)
239 {
240 std::cerr << "Illegal header at bus " << bus << " address "
241 << address << "\n";
242 }
243
244 close(file);
245 return device;
246 }
247
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700248 // Copy the IPMI Fru Header
Patrick Venture4f47fe62019-08-08 16:30:38 -0700249 device.insert(device.end(), blockData.begin(), blockData.begin() + 8);
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700250
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700251 int fruLength = 0;
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700252 for (size_t jj = 1; jj <= FRU_AREAS.size(); jj++)
253 {
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700254 // TODO: offset can be 255, device is holding "chars" that's not good.
Patrick Venture4f47fe62019-08-08 16:30:38 -0700255 int areaOffset = device[jj];
256 if (areaOffset == 0)
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700257 {
258 continue;
259 }
260
Patrick Venture4f47fe62019-08-08 16:30:38 -0700261 areaOffset *= 8;
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700262
Patrick Venture4f47fe62019-08-08 16:30:38 -0700263 if (readFromEeprom(file, static_cast<uint16_t>(areaOffset), 0x2,
264 blockData.data()) < 0)
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700265 {
266 std::cerr << "failed to read bus " << bus << " address " << address
267 << "\n";
268 device.clear();
269 close(file);
270 return device;
271 }
272
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700273 // Ignore data type.
Patrick Venture4f47fe62019-08-08 16:30:38 -0700274 int length = blockData[1] * 8;
275 areaOffset += length;
276 fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700277 }
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700278
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700279 // You already copied these first 8 bytes (the ipmi fru header size)
280 fruLength -= 8;
281
282 int readOffset = 8;
283
284 while (fruLength > 0)
285 {
Patrick Venture4f47fe62019-08-08 16:30:38 -0700286 int requestLength = std::min(I2C_SMBUS_BLOCK_MAX, fruLength);
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700287
288 if (readFromEeprom(file, static_cast<uint16_t>(readOffset),
Patrick Venture4f47fe62019-08-08 16:30:38 -0700289 static_cast<uint8_t>(requestLength),
290 blockData.data()) < 0)
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700291 {
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700292 std::cerr << "failed to read bus " << bus << " address " << address
293 << "\n";
294 device.clear();
295 close(file);
296 return device;
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700297 }
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700298
Patrick Venture4f47fe62019-08-08 16:30:38 -0700299 device.insert(device.end(), blockData.begin(),
300 blockData.begin() + requestLength);
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700301
Patrick Venture4f47fe62019-08-08 16:30:38 -0700302 readOffset += requestLength;
303 fruLength -= requestLength;
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700304 }
305
306 close(file);
307 return device;
308}
309
310std::set<int> findI2CEeproms(int i2cBus, std::shared_ptr<DeviceMap> devices)
311{
312 std::set<int> foundList;
313
314 std::string path = "/sys/bus/i2c/devices/i2c-" + std::to_string(i2cBus);
315
316 // For each file listed under the i2c device
317 // NOTE: This should be faster than just checking for each possible address
318 // path.
319 for (const auto& p : fs::directory_iterator(path))
320 {
321 const std::string node = p.path().string();
322 std::smatch m;
323 bool found =
324 std::regex_match(node, m, std::regex(".+\\d+-([0-9abcdef]+$)"));
325
326 if (!found)
327 {
328 continue;
329 }
330 if (m.size() != 2)
331 {
332 std::cerr << "regex didn't capture\n";
333 continue;
334 }
335
336 std::ssub_match subMatch = m[1];
337 std::string addressString = subMatch.str();
338
339 std::size_t ignored;
340 const int hexBase = 16;
341 int address = std::stoi(addressString, &ignored, hexBase);
342
343 const std::string eeprom = node + "/eeprom";
344
345 try
346 {
347 if (!fs::exists(eeprom))
348 {
349 continue;
350 }
351 }
352 catch (...)
353 {
354 continue;
355 }
356
357 // There is an eeprom file at this address, it may have invalid
358 // contents, but we found it.
359 foundList.insert(address);
360
361 std::vector<char> device = processEeprom(i2cBus, address);
362 if (!device.empty())
363 {
364 devices->emplace(address, device);
365 }
366 }
367
368 return foundList;
369}
370
Patrick Venture4f47fe62019-08-08 16:30:38 -0700371int getBusFrus(int file, int first, int last, int bus,
372 std::shared_ptr<DeviceMap> devices)
James Feist3cb5fec2018-01-23 14:41:51 -0800373{
James Feist3cb5fec2018-01-23 14:41:51 -0800374
James Feist26c27ad2018-07-25 15:09:40 -0700375 std::future<int> future = std::async(std::launch::async, [&]() {
Patrick Venture4f47fe62019-08-08 16:30:38 -0700376 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData;
Vijay Khemka2d681f62018-11-06 15:51:00 -0800377
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700378 // NOTE: When reading the devices raw on the bus, it can interfere with
379 // the driver's ability to operate, therefore read eeproms first before
380 // scanning for devices without drivers. Several experiments were run
381 // and it was determined that if there were any devices on the bus
382 // before the eeprom was hit and read, the eeprom driver wouldn't open
383 // while the bus device was open. An experiment was not performed to see
384 // if this issue was resolved if the i2c bus device was closed, but
385 // hexdumps of the eeprom later were successful.
386
387 // Scan for i2c eeproms loaded on this bus.
388 std::set<int> skipList = findI2CEeproms(bus, devices);
389
James Feist26c27ad2018-07-25 15:09:40 -0700390 for (int ii = first; ii <= last; ii++)
James Feist3cb5fec2018-01-23 14:41:51 -0800391 {
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700392 if (skipList.find(ii) != skipList.end())
393 {
394 continue;
395 }
James Feist3cb5fec2018-01-23 14:41:51 -0800396
James Feist26c27ad2018-07-25 15:09:40 -0700397 // Set slave address
398 if (ioctl(file, I2C_SLAVE_FORCE, ii) < 0)
James Feist3cb5fec2018-01-23 14:41:51 -0800399 {
Patrick Venture98e0cf32019-08-02 11:11:03 -0700400 std::cerr << "device at bus " << bus << " register " << ii
Patrick Venture5d8b61d2019-08-06 12:36:10 -0700401 << " busy\n";
James Feist26c27ad2018-07-25 15:09:40 -0700402 continue;
403 }
404 // probe
405 else if (i2c_smbus_read_byte(file) < 0)
406 {
407 continue;
408 }
James Feist3cb5fec2018-01-23 14:41:51 -0800409
James Feist26c27ad2018-07-25 15:09:40 -0700410 if (DEBUG)
411 {
Patrick Venture98e0cf32019-08-02 11:11:03 -0700412 std::cout << "something at bus " << bus << " addr " << ii
James Feist26c27ad2018-07-25 15:09:40 -0700413 << "\n";
414 }
Vijay Khemka2d681f62018-11-06 15:51:00 -0800415
416 /* Check for Device type if it is 8 bit or 16 bit */
417 int flag = isDevice16Bit(file);
418 if (flag < 0)
419 {
420 std::cerr << "failed to read bus " << bus << " address " << ii
421 << "\n";
422 continue;
423 }
424
Patrick Venture4f47fe62019-08-08 16:30:38 -0700425 if (readBlockData(flag, file, 0x0, 0x8, blockData.data()) < 0)
James Feist26c27ad2018-07-25 15:09:40 -0700426 {
427 std::cerr << "failed to read bus " << bus << " address " << ii
428 << "\n";
429 continue;
430 }
James Feist26c27ad2018-07-25 15:09:40 -0700431
432 // check the header checksum
Patrick Venture4f47fe62019-08-08 16:30:38 -0700433 if (!validateHeader(blockData))
James Feist26c27ad2018-07-25 15:09:40 -0700434 {
Patrick Venture786f1792019-08-05 16:33:44 -0700435 if (DEBUG)
James Feist26c27ad2018-07-25 15:09:40 -0700436 {
Patrick Venture786f1792019-08-05 16:33:44 -0700437 std::cerr << "Illegal header at bus " << bus << " address "
438 << ii << "\n";
439 }
440 continue;
441 }
442
443 std::vector<char> device;
Patrick Venture4f47fe62019-08-08 16:30:38 -0700444 device.insert(device.end(), blockData.begin(),
445 blockData.begin() + 8);
Patrick Venture786f1792019-08-05 16:33:44 -0700446
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700447 int fruLength = 0;
Patrick Venture786f1792019-08-05 16:33:44 -0700448 for (size_t jj = 1; jj <= FRU_AREAS.size(); jj++)
449 {
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700450 // TODO: offset can be 255, device is holding "chars" that's not
451 // good.
Patrick Venture4f47fe62019-08-08 16:30:38 -0700452 int areaOffset = device[jj];
453 if (areaOffset == 0)
Patrick Venture786f1792019-08-05 16:33:44 -0700454 {
Patrick Venture64fd7e22019-08-05 16:38:20 -0700455 continue;
456 }
457
Patrick Venture4f47fe62019-08-08 16:30:38 -0700458 areaOffset *= 8;
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700459
Patrick Venture4f47fe62019-08-08 16:30:38 -0700460 if (readBlockData(flag, file, static_cast<uint16_t>(areaOffset),
461 0x2, blockData.data()) < 0)
Patrick Venture64fd7e22019-08-05 16:38:20 -0700462 {
463 std::cerr << "failed to read bus " << bus << " address "
464 << ii << "\n";
465 return -1;
466 }
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700467
468 // Ignore data type.
Patrick Venture4f47fe62019-08-08 16:30:38 -0700469 int length = blockData[1] * 8;
470 areaOffset += length;
471 fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700472 }
Patrick Venture64fd7e22019-08-05 16:38:20 -0700473
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700474 // You already copied these first 8 bytes (the ipmi fru header size)
475 fruLength -= 8;
476
477 int readOffset = 8;
478
479 while (fruLength > 0)
480 {
Patrick Venture4f47fe62019-08-08 16:30:38 -0700481 int requestLength = std::min(I2C_SMBUS_BLOCK_MAX, fruLength);
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700482
Patrick Venture4f47fe62019-08-08 16:30:38 -0700483 if (readBlockData(flag, file, static_cast<uint16_t>(readOffset),
484 static_cast<uint8_t>(requestLength),
485 blockData.data()) < 0)
Patrick Venture64fd7e22019-08-05 16:38:20 -0700486 {
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700487 std::cerr << "failed to read bus " << bus << " address "
488 << ii << "\n";
489 return -1;
James Feist3cb5fec2018-01-23 14:41:51 -0800490 }
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700491
Patrick Venture4f47fe62019-08-08 16:30:38 -0700492 device.insert(device.end(), blockData.begin(),
493 blockData.begin() + requestLength);
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700494
Patrick Venture4f47fe62019-08-08 16:30:38 -0700495 readOffset += requestLength;
496 fruLength -= requestLength;
James Feist3cb5fec2018-01-23 14:41:51 -0800497 }
Patrick Venture786f1792019-08-05 16:33:44 -0700498 devices->emplace(ii, device);
James Feist3cb5fec2018-01-23 14:41:51 -0800499 }
James Feist26c27ad2018-07-25 15:09:40 -0700500 return 1;
501 });
502 std::future_status status =
503 future.wait_for(std::chrono::seconds(busTimeoutSeconds));
504 if (status == std::future_status::timeout)
505 {
506 std::cerr << "Error reading bus " << bus << "\n";
James Feist444830e2019-04-05 08:38:16 -0700507 busBlacklist.insert(bus);
508 close(file);
James Feist26c27ad2018-07-25 15:09:40 -0700509 return -1;
James Feist3cb5fec2018-01-23 14:41:51 -0800510 }
511
James Feist444830e2019-04-05 08:38:16 -0700512 close(file);
James Feist26c27ad2018-07-25 15:09:40 -0700513 return future.get();
James Feist3cb5fec2018-01-23 14:41:51 -0800514}
515
Patrick Venture11f1ff42019-08-01 10:42:12 -0700516void loadBlacklist(const char* path)
517{
518 std::ifstream blacklistStream(path);
519 if (!blacklistStream.good())
520 {
521 // File is optional.
522 std::cerr << "Cannot open blacklist file.\n\n";
523 return;
524 }
525
526 nlohmann::json data =
527 nlohmann::json::parse(blacklistStream, nullptr, false);
528 if (data.is_discarded())
529 {
530 std::cerr << "Illegal blacklist file detected, cannot validate JSON, "
531 "exiting\n";
532 std::exit(EXIT_FAILURE);
Patrick Venture11f1ff42019-08-01 10:42:12 -0700533 }
534
535 // It's expected to have at least one field, "buses" that is an array of the
536 // buses by integer. Allow for future options to exclude further aspects,
537 // such as specific addresses or ranges.
538 if (data.type() != nlohmann::json::value_t::object)
539 {
540 std::cerr << "Illegal blacklist, expected to read dictionary\n";
541 std::exit(EXIT_FAILURE);
Patrick Venture11f1ff42019-08-01 10:42:12 -0700542 }
543
544 // If buses field is missing, that's fine.
545 if (data.count("buses") == 1)
546 {
547 // Parse the buses array after a little validation.
548 auto buses = data.at("buses");
549 if (buses.type() != nlohmann::json::value_t::array)
550 {
551 // Buses field present but invalid, therefore this is an error.
552 std::cerr << "Invalid contents for blacklist buses field\n";
553 std::exit(EXIT_FAILURE);
Patrick Venture11f1ff42019-08-01 10:42:12 -0700554 }
555
556 // Catch exception here for type mis-match.
557 try
558 {
559 for (const auto& bus : buses)
560 {
561 busBlacklist.insert(bus.get<size_t>());
562 }
563 }
564 catch (const nlohmann::detail::type_error& e)
565 {
566 // Type mis-match is a critical error.
567 std::cerr << "Invalid bus type: " << e.what() << "\n";
568 std::exit(EXIT_FAILURE);
Patrick Venture11f1ff42019-08-01 10:42:12 -0700569 }
570 }
571
572 return;
573}
574
James Feista465ccc2019-02-08 12:51:01 -0800575static void FindI2CDevices(const std::vector<fs::path>& i2cBuses,
James Feist98132792019-07-09 13:29:09 -0700576 BusMap& busmap)
James Feist3cb5fec2018-01-23 14:41:51 -0800577{
James Feista465ccc2019-02-08 12:51:01 -0800578 for (auto& i2cBus : i2cBuses)
James Feist3cb5fec2018-01-23 14:41:51 -0800579 {
580 auto busnum = i2cBus.string();
581 auto lastDash = busnum.rfind(std::string("-"));
582 // delete everything before dash inclusive
583 if (lastDash != std::string::npos)
584 {
585 busnum.erase(0, lastDash + 1);
586 }
587 auto bus = std::stoi(busnum);
James Feist444830e2019-04-05 08:38:16 -0700588 if (busBlacklist.find(bus) != busBlacklist.end())
589 {
590 continue; // skip previously failed busses
591 }
James Feist3cb5fec2018-01-23 14:41:51 -0800592
593 auto file = open(i2cBus.c_str(), O_RDWR);
594 if (file < 0)
595 {
596 std::cerr << "unable to open i2c device " << i2cBus.string()
597 << "\n";
598 continue;
599 }
600 unsigned long funcs = 0;
601
602 if (ioctl(file, I2C_FUNCS, &funcs) < 0)
603 {
604 std::cerr
Patrick Venture98e0cf32019-08-02 11:11:03 -0700605 << "Error: Could not get the adapter functionality matrix bus "
James Feist3cb5fec2018-01-23 14:41:51 -0800606 << bus << "\n";
607 continue;
608 }
609 if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE) ||
610 !(I2C_FUNC_SMBUS_READ_I2C_BLOCK))
611 {
612 std::cerr << "Error: Can't use SMBus Receive Byte command bus "
613 << bus << "\n";
614 continue;
615 }
James Feist98132792019-07-09 13:29:09 -0700616 auto& device = busmap[bus];
James Feist3cb5fec2018-01-23 14:41:51 -0800617 device = std::make_shared<DeviceMap>();
618
Nikhil Potaded8884f12019-03-27 13:27:13 -0700619 // i2cdetect by default uses the range 0x03 to 0x77, as
620 // this is what we have tested with, use this range. Could be
621 // changed in future.
622 if (DEBUG)
James Feistc95cb142018-02-26 10:41:42 -0800623 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700624 std::cerr << "Scanning bus " << bus << "\n";
James Feistc95cb142018-02-26 10:41:42 -0800625 }
Nikhil Potaded8884f12019-03-27 13:27:13 -0700626
627 // fd is closed in this function in case the bus locks up
Patrick Venture4f47fe62019-08-08 16:30:38 -0700628 getBusFrus(file, 0x03, 0x77, bus, device);
Nikhil Potaded8884f12019-03-27 13:27:13 -0700629
630 if (DEBUG)
James Feistc95cb142018-02-26 10:41:42 -0800631 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700632 std::cerr << "Done scanning bus " << bus << "\n";
James Feistc95cb142018-02-26 10:41:42 -0800633 }
James Feist3cb5fec2018-01-23 14:41:51 -0800634 }
James Feist3cb5fec2018-01-23 14:41:51 -0800635}
636
James Feist6ebf9de2018-05-15 15:01:17 -0700637// this class allows an async response after all i2c devices are discovered
638struct FindDevicesWithCallback
639 : std::enable_shared_from_this<FindDevicesWithCallback>
640{
James Feista465ccc2019-02-08 12:51:01 -0800641 FindDevicesWithCallback(const std::vector<fs::path>& i2cBuses,
James Feist5cb06082019-10-24 17:11:13 -0700642 BusMap& busmap,
James Feista465ccc2019-02-08 12:51:01 -0800643 std::function<void(void)>&& callback) :
James Feist6ebf9de2018-05-15 15:01:17 -0700644 _i2cBuses(i2cBuses),
James Feist5cb06082019-10-24 17:11:13 -0700645 _busMap(busmap), _callback(std::move(callback))
James Feist6ebf9de2018-05-15 15:01:17 -0700646 {
647 }
648 ~FindDevicesWithCallback()
649 {
650 _callback();
651 }
652 void run()
653 {
James Feist98132792019-07-09 13:29:09 -0700654 FindI2CDevices(_i2cBuses, _busMap);
James Feist6ebf9de2018-05-15 15:01:17 -0700655 }
656
James Feista465ccc2019-02-08 12:51:01 -0800657 const std::vector<fs::path>& _i2cBuses;
James Feista465ccc2019-02-08 12:51:01 -0800658 BusMap& _busMap;
James Feist6ebf9de2018-05-15 15:01:17 -0700659 std::function<void(void)> _callback;
660};
661
James Feist3cb5fec2018-01-23 14:41:51 -0800662static const std::tm intelEpoch(void)
663{
James Feist98132792019-07-09 13:29:09 -0700664 std::tm val = {};
James Feist3cb5fec2018-01-23 14:41:51 -0800665 val.tm_year = 1996 - 1900;
666 return val;
667}
668
James Feista465ccc2019-02-08 12:51:01 -0800669bool formatFru(const std::vector<char>& fruBytes,
670 boost::container::flat_map<std::string, std::string>& result)
James Feist3cb5fec2018-01-23 14:41:51 -0800671{
James Feista465ccc2019-02-08 12:51:01 -0800672 static const std::vector<const char*> CHASSIS_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800673 "PART_NUMBER", "SERIAL_NUMBER", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800674
James Feista465ccc2019-02-08 12:51:01 -0800675 static const std::vector<const char*> BOARD_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800676 "MANUFACTURER", "PRODUCT_NAME", "SERIAL_NUMBER", "PART_NUMBER",
677 "FRU_VERSION_ID", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800678
James Feista465ccc2019-02-08 12:51:01 -0800679 static const std::vector<const char*> PRODUCT_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800680 "MANUFACTURER", "PRODUCT_NAME", "PART_NUMBER",
681 "VERSION", "SERIAL_NUMBER", "ASSET_TAG",
682 "FRU_VERSION_ID", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800683
James Feistd068e932018-09-20 10:53:07 -0700684 if (fruBytes.size() <= 8)
James Feist3cb5fec2018-01-23 14:41:51 -0800685 {
686 return false;
687 }
688 std::vector<char>::const_iterator fruAreaOffsetField = fruBytes.begin();
James Feist9eb0b582018-04-27 12:15:46 -0700689 result["Common_Format_Version"] =
James Feist3cb5fec2018-01-23 14:41:51 -0800690 std::to_string(static_cast<int>(*fruAreaOffsetField));
691
James Feista465ccc2019-02-08 12:51:01 -0800692 const std::vector<const char*>* fieldData;
James Feist3cb5fec2018-01-23 14:41:51 -0800693
James Feist0eb40352019-04-09 14:44:04 -0700694 for (const std::string& area : FRU_AREAS)
James Feist3cb5fec2018-01-23 14:41:51 -0800695 {
696 fruAreaOffsetField++;
697 if (fruAreaOffsetField >= fruBytes.end())
698 {
699 return false;
700 }
701 size_t offset = (*fruAreaOffsetField) * 8;
702
703 if (offset > 1)
704 {
705 // +2 to skip format and length
706 std::vector<char>::const_iterator fruBytesIter =
707 fruBytes.begin() + offset + 2;
708
709 if (fruBytesIter >= fruBytes.end())
710 {
711 return false;
712 }
713
714 if (area == "CHASSIS")
715 {
716 result["CHASSIS_TYPE"] =
717 std::to_string(static_cast<int>(*fruBytesIter));
718 fruBytesIter += 1;
719 fieldData = &CHASSIS_FRU_AREAS;
720 }
721 else if (area == "BOARD")
722 {
723 result["BOARD_LANGUAGE_CODE"] =
724 std::to_string(static_cast<int>(*fruBytesIter));
725 fruBytesIter += 1;
726 if (fruBytesIter >= fruBytes.end())
727 {
728 return false;
729 }
730
731 unsigned int minutes = *fruBytesIter |
732 *(fruBytesIter + 1) << 8 |
733 *(fruBytesIter + 2) << 16;
734 std::tm fruTime = intelEpoch();
Patrick Venturee0e6f5f2019-08-12 19:00:11 -0700735 std::time_t timeValue = std::mktime(&fruTime);
James Feist3cb5fec2018-01-23 14:41:51 -0800736 timeValue += minutes * 60;
Patrick Venturee0e6f5f2019-08-12 19:00:11 -0700737 fruTime = *std::gmtime(&timeValue);
James Feist3cb5fec2018-01-23 14:41:51 -0800738
Patrick Venturee0e6f5f2019-08-12 19:00:11 -0700739 // Tue Nov 20 23:08:00 2018
740 char timeString[32] = {0};
741 auto bytes = std::strftime(timeString, sizeof(timeString),
Patrick Venturefff050a2019-08-13 11:44:30 -0700742 "%Y-%m-%d - %H:%M:%S", &fruTime);
Patrick Venturee0e6f5f2019-08-12 19:00:11 -0700743 if (bytes == 0)
744 {
745 std::cerr << "invalid time string encountered\n";
746 return false;
747 }
748
749 result["BOARD_MANUFACTURE_DATE"] = std::string(timeString);
James Feist3cb5fec2018-01-23 14:41:51 -0800750 fruBytesIter += 3;
751 fieldData = &BOARD_FRU_AREAS;
752 }
753 else if (area == "PRODUCT")
754 {
755 result["PRODUCT_LANGUAGE_CODE"] =
756 std::to_string(static_cast<int>(*fruBytesIter));
757 fruBytesIter += 1;
758 fieldData = &PRODUCT_FRU_AREAS;
759 }
760 else
761 {
762 continue;
763 }
James Feista465ccc2019-02-08 12:51:01 -0800764 for (auto& field : *fieldData)
James Feist3cb5fec2018-01-23 14:41:51 -0800765 {
766 if (fruBytesIter >= fruBytes.end())
767 {
768 return false;
769 }
770
Vijay Khemka5d5de442018-11-07 10:51:25 -0800771 /* Checking for last byte C1 to indicate that no more
772 * field to be read */
James Feist98132792019-07-09 13:29:09 -0700773 if (static_cast<uint8_t>(*fruBytesIter) == 0xC1)
Vijay Khemka5d5de442018-11-07 10:51:25 -0800774 {
775 break;
776 }
777
Ed Tanous2147e672019-02-27 13:59:56 -0800778 size_t length = *fruBytesIter & 0x3f;
779 fruBytesIter += 1;
780
James Feist3cb5fec2018-01-23 14:41:51 -0800781 if (fruBytesIter >= fruBytes.end())
782 {
783 return false;
784 }
Ed Tanous2147e672019-02-27 13:59:56 -0800785 std::string value(fruBytesIter, fruBytesIter + length);
James Feist3cb5fec2018-01-23 14:41:51 -0800786
Ed Tanous2147e672019-02-27 13:59:56 -0800787 // Strip non null characters from the end
788 value.erase(std::find_if(value.rbegin(), value.rend(),
789 [](char ch) { return ch != 0; })
790 .base(),
791 value.end());
792
James Feist0eb40352019-04-09 14:44:04 -0700793 result[area + "_" + field] = std::move(value);
Ed Tanous2147e672019-02-27 13:59:56 -0800794
James Feist3cb5fec2018-01-23 14:41:51 -0800795 fruBytesIter += length;
796 if (fruBytesIter >= fruBytes.end())
797 {
798 std::cerr << "Warning Fru Length Mismatch:\n ";
James Feista465ccc2019-02-08 12:51:01 -0800799 for (auto& c : fruBytes)
James Feist3cb5fec2018-01-23 14:41:51 -0800800 {
801 std::cerr << c;
802 }
803 std::cerr << "\n";
804 if (DEBUG)
805 {
James Feista465ccc2019-02-08 12:51:01 -0800806 for (auto& keyPair : result)
James Feist3cb5fec2018-01-23 14:41:51 -0800807 {
808 std::cerr << keyPair.first << " : "
809 << keyPair.second << "\n";
810 }
811 }
812 return false;
813 }
814 }
815 }
816 }
817
818 return true;
819}
820
Nikhil Potaded8884f12019-03-27 13:27:13 -0700821std::vector<uint8_t>& getFruInfo(const uint8_t& bus, const uint8_t& address)
822{
823 auto deviceMap = busMap.find(bus);
824 if (deviceMap == busMap.end())
825 {
826 throw std::invalid_argument("Invalid Bus.");
827 }
828 auto device = deviceMap->second->find(address);
829 if (device == deviceMap->second->end())
830 {
831 throw std::invalid_argument("Invalid Address.");
832 }
833 std::vector<uint8_t>& ret =
834 reinterpret_cast<std::vector<uint8_t>&>(device->second);
835
836 return ret;
837}
838
James Feist3cb5fec2018-01-23 14:41:51 -0800839void AddFruObjectToDbus(
James Feist5cb06082019-10-24 17:11:13 -0700840 std::vector<char>& device,
James Feista465ccc2019-02-08 12:51:01 -0800841 boost::container::flat_map<
842 std::pair<size_t, size_t>,
843 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
James Feist13b86d62018-05-29 11:24:35 -0700844 uint32_t bus, uint32_t address)
James Feist3cb5fec2018-01-23 14:41:51 -0800845{
846 boost::container::flat_map<std::string, std::string> formattedFru;
847 if (!formatFru(device, formattedFru))
848 {
Patrick Ventureb755c832019-08-07 11:09:14 -0700849 std::cerr << "failed to format fru for device at bus " << bus
850 << " address " << address << "\n";
James Feist3cb5fec2018-01-23 14:41:51 -0800851 return;
852 }
Patrick Venture96cdaef2019-07-30 13:30:52 -0700853
James Feist3cb5fec2018-01-23 14:41:51 -0800854 auto productNameFind = formattedFru.find("BOARD_PRODUCT_NAME");
855 std::string productName;
Patrick Venture96cdaef2019-07-30 13:30:52 -0700856 // Not found under Board section or an empty string.
857 if (productNameFind == formattedFru.end() ||
858 productNameFind->second.empty())
James Feist3cb5fec2018-01-23 14:41:51 -0800859 {
860 productNameFind = formattedFru.find("PRODUCT_PRODUCT_NAME");
861 }
Patrick Venture96cdaef2019-07-30 13:30:52 -0700862 // Found under Product section and not an empty string.
863 if (productNameFind != formattedFru.end() &&
864 !productNameFind->second.empty())
James Feist3cb5fec2018-01-23 14:41:51 -0800865 {
866 productName = productNameFind->second;
James Feist3f8a2782018-02-12 09:24:42 -0800867 std::regex illegalObject("[^A-Za-z0-9_]");
868 productName = std::regex_replace(productName, illegalObject, "_");
James Feist3cb5fec2018-01-23 14:41:51 -0800869 }
870 else
871 {
872 productName = "UNKNOWN" + std::to_string(UNKNOWN_BUS_OBJECT_COUNT);
873 UNKNOWN_BUS_OBJECT_COUNT++;
874 }
875
James Feist918e18c2018-02-13 15:51:07 -0800876 productName = "/xyz/openbmc_project/FruDevice/" + productName;
James Feist918e18c2018-02-13 15:51:07 -0800877 // avoid duplicates by checking to see if on a mux
James Feist79e9c0b2018-03-15 15:21:17 -0700878 if (bus > 0)
James Feist918e18c2018-02-13 15:51:07 -0800879 {
Patrick Venture015fb0a2019-08-16 09:33:31 -0700880 int highest = -1;
881 bool found = false;
882
James Feista465ccc2019-02-08 12:51:01 -0800883 for (auto const& busIface : dbusInterfaceMap)
James Feist918e18c2018-02-13 15:51:07 -0800884 {
Patrick Venture015fb0a2019-08-16 09:33:31 -0700885 std::string path = busIface.second->get_object_path();
886 if (std::regex_match(path, std::regex(productName + "(_\\d+|)$")))
James Feist918e18c2018-02-13 15:51:07 -0800887 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700888 if (isMuxBus(bus) && address == busIface.first.second &&
James Feist98132792019-07-09 13:29:09 -0700889 (getFruInfo(static_cast<uint8_t>(busIface.first.first),
890 static_cast<uint8_t>(busIface.first.second)) ==
891 getFruInfo(static_cast<uint8_t>(bus),
892 static_cast<uint8_t>(address))))
James Feist79e9c0b2018-03-15 15:21:17 -0700893 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700894 // This device is already added to the lower numbered bus,
895 // do not replicate it.
896 return;
James Feist79e9c0b2018-03-15 15:21:17 -0700897 }
Patrick Venture015fb0a2019-08-16 09:33:31 -0700898
899 // Check if the match named has extra information.
900 found = true;
901 std::smatch base_match;
902
903 bool match = std::regex_match(
904 path, base_match, std::regex(productName + "_(\\d+)$"));
905 if (match)
James Feist79e9c0b2018-03-15 15:21:17 -0700906 {
Patrick Venture015fb0a2019-08-16 09:33:31 -0700907 if (base_match.size() == 2)
908 {
909 std::ssub_match base_sub_match = base_match[1];
910 std::string base = base_sub_match.str();
911
912 int value = std::stoi(base);
913 highest = (value > highest) ? value : highest;
914 }
James Feist79e9c0b2018-03-15 15:21:17 -0700915 }
James Feist918e18c2018-02-13 15:51:07 -0800916 }
Patrick Venture015fb0a2019-08-16 09:33:31 -0700917 } // end searching objects
918
919 if (found)
920 {
921 // We found something with the same name. If highest was still -1,
922 // it means this new entry will be _0.
923 productName += "_";
924 productName += std::to_string(++highest);
James Feist918e18c2018-02-13 15:51:07 -0800925 }
926 }
James Feist3cb5fec2018-01-23 14:41:51 -0800927
James Feist9eb0b582018-04-27 12:15:46 -0700928 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
929 objServer.add_interface(productName, "xyz.openbmc_project.FruDevice");
930 dbusInterfaceMap[std::pair<size_t, size_t>(bus, address)] = iface;
931
James Feista465ccc2019-02-08 12:51:01 -0800932 for (auto& property : formattedFru)
James Feist3cb5fec2018-01-23 14:41:51 -0800933 {
James Feist9eb0b582018-04-27 12:15:46 -0700934
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -0700935 std::regex_replace(property.second.begin(), property.second.begin(),
936 property.second.end(), NON_ASCII_REGEX, "_");
James Feist9eb0b582018-04-27 12:15:46 -0700937 if (property.second.empty())
938 {
939 continue;
940 }
941 std::string key =
942 std::regex_replace(property.first, NON_ASCII_REGEX, "_");
943 if (!iface->register_property(key, property.second + '\0'))
944 {
945 std::cerr << "illegal key: " << key << "\n";
946 }
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -0700947 if (DEBUG)
948 {
949 std::cout << property.first << ": " << property.second << "\n";
950 }
James Feist3cb5fec2018-01-23 14:41:51 -0800951 }
James Feist2a9d6db2018-04-27 15:48:28 -0700952
953 // baseboard will be 0, 0
James Feist13b86d62018-05-29 11:24:35 -0700954 iface->register_property("BUS", bus);
955 iface->register_property("ADDRESS", address);
James Feist2a9d6db2018-04-27 15:48:28 -0700956
James Feist9eb0b582018-04-27 12:15:46 -0700957 iface->initialize();
James Feist3cb5fec2018-01-23 14:41:51 -0800958}
959
James Feista465ccc2019-02-08 12:51:01 -0800960static bool readBaseboardFru(std::vector<char>& baseboardFru)
James Feist3cb5fec2018-01-23 14:41:51 -0800961{
962 // try to read baseboard fru from file
963 std::ifstream baseboardFruFile(BASEBOARD_FRU_LOCATION, std::ios::binary);
964 if (baseboardFruFile.good())
965 {
966 baseboardFruFile.seekg(0, std::ios_base::end);
James Feist98132792019-07-09 13:29:09 -0700967 size_t fileSize = static_cast<size_t>(baseboardFruFile.tellg());
James Feist3cb5fec2018-01-23 14:41:51 -0800968 baseboardFru.resize(fileSize);
969 baseboardFruFile.seekg(0, std::ios_base::beg);
970 baseboardFruFile.read(baseboardFru.data(), fileSize);
971 }
972 else
973 {
974 return false;
975 }
976 return true;
977}
978
James Feista465ccc2019-02-08 12:51:01 -0800979bool writeFru(uint8_t bus, uint8_t address, const std::vector<uint8_t>& fru)
James Feistb49ffc32018-05-02 11:10:43 -0700980{
981 boost::container::flat_map<std::string, std::string> tmp;
982 if (fru.size() > MAX_FRU_SIZE)
983 {
984 std::cerr << "Invalid fru.size() during writeFru\n";
985 return false;
986 }
987 // verify legal fru by running it through fru parsing logic
James Feista465ccc2019-02-08 12:51:01 -0800988 if (!formatFru(reinterpret_cast<const std::vector<char>&>(fru), tmp))
James Feistb49ffc32018-05-02 11:10:43 -0700989 {
990 std::cerr << "Invalid fru format during writeFru\n";
991 return false;
992 }
993 // baseboard fru
994 if (bus == 0 && address == 0)
995 {
996 std::ofstream file(BASEBOARD_FRU_LOCATION, std::ios_base::binary);
997 if (!file.good())
998 {
999 std::cerr << "Error opening file " << BASEBOARD_FRU_LOCATION
1000 << "\n";
James Feistddb78302018-09-06 11:45:42 -07001001 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001002 return false;
1003 }
James Feista465ccc2019-02-08 12:51:01 -08001004 file.write(reinterpret_cast<const char*>(fru.data()), fru.size());
James Feistb49ffc32018-05-02 11:10:43 -07001005 return file.good();
1006 }
1007 else
1008 {
Patrick Venturec50e1ff2019-08-06 10:22:28 -07001009 if (hasEepromFile(bus, address))
1010 {
1011 auto path = getEepromPath(bus, address);
1012 int eeprom = open(path.c_str(), O_RDWR | O_CLOEXEC);
1013 if (eeprom < 0)
1014 {
1015 std::cerr << "unable to open i2c device " << path << "\n";
1016 throw DBusInternalError();
1017 return false;
1018 }
1019
1020 ssize_t writtenBytes = write(eeprom, fru.data(), fru.size());
1021 if (writtenBytes < 0)
1022 {
1023 std::cerr << "unable to write to i2c device " << path << "\n";
1024 close(eeprom);
1025 throw DBusInternalError();
1026 return false;
1027 }
1028
1029 close(eeprom);
1030 return true;
1031 }
1032
James Feistb49ffc32018-05-02 11:10:43 -07001033 std::string i2cBus = "/dev/i2c-" + std::to_string(bus);
1034
1035 int file = open(i2cBus.c_str(), O_RDWR | O_CLOEXEC);
1036 if (file < 0)
1037 {
1038 std::cerr << "unable to open i2c device " << i2cBus << "\n";
James Feistddb78302018-09-06 11:45:42 -07001039 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001040 return false;
1041 }
1042 if (ioctl(file, I2C_SLAVE_FORCE, address) < 0)
1043 {
1044 std::cerr << "unable to set device address\n";
1045 close(file);
James Feistddb78302018-09-06 11:45:42 -07001046 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001047 return false;
1048 }
1049
1050 constexpr const size_t RETRY_MAX = 2;
1051 uint16_t index = 0;
1052 size_t retries = RETRY_MAX;
1053 while (index < fru.size())
1054 {
1055 if ((index && ((index % (MAX_EEPROM_PAGE_INDEX + 1)) == 0)) &&
1056 (retries == RETRY_MAX))
1057 {
1058 // The 4K EEPROM only uses the A2 and A1 device address bits
1059 // with the third bit being a memory page address bit.
1060 if (ioctl(file, I2C_SLAVE_FORCE, ++address) < 0)
1061 {
1062 std::cerr << "unable to set device address\n";
1063 close(file);
James Feistddb78302018-09-06 11:45:42 -07001064 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001065 return false;
1066 }
1067 }
1068
James Feist98132792019-07-09 13:29:09 -07001069 if (i2c_smbus_write_byte_data(file, static_cast<uint8_t>(index),
1070 fru[index]) < 0)
James Feistb49ffc32018-05-02 11:10:43 -07001071 {
1072 if (!retries--)
1073 {
1074 std::cerr << "error writing fru: " << strerror(errno)
1075 << "\n";
1076 close(file);
James Feistddb78302018-09-06 11:45:42 -07001077 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001078 return false;
1079 }
1080 }
1081 else
1082 {
1083 retries = RETRY_MAX;
1084 index++;
1085 }
1086 // most eeproms require 5-10ms between writes
1087 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1088 }
1089 close(file);
1090 return true;
1091 }
1092}
1093
James Feist9eb0b582018-04-27 12:15:46 -07001094void rescanBusses(
James Feist5cb06082019-10-24 17:11:13 -07001095 BusMap& busmap,
James Feista465ccc2019-02-08 12:51:01 -08001096 boost::container::flat_map<
1097 std::pair<size_t, size_t>,
James Feist5cb06082019-10-24 17:11:13 -07001098 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap)
James Feist918e18c2018-02-13 15:51:07 -08001099{
James Feist6ebf9de2018-05-15 15:01:17 -07001100 static boost::asio::deadline_timer timer(io);
1101 timer.expires_from_now(boost::posix_time::seconds(1));
James Feist918e18c2018-02-13 15:51:07 -08001102
Gunnar Mills6f0ae942018-08-31 12:38:03 -05001103 // setup an async wait in case we get flooded with requests
James Feist98132792019-07-09 13:29:09 -07001104 timer.async_wait([&](const boost::system::error_code&) {
James Feist4131aea2018-03-09 09:47:30 -08001105 auto devDir = fs::path("/dev/");
James Feist4131aea2018-03-09 09:47:30 -08001106 std::vector<fs::path> i2cBuses;
James Feist918e18c2018-02-13 15:51:07 -08001107
Nikhil Potaded8884f12019-03-27 13:27:13 -07001108 boost::container::flat_map<size_t, fs::path> busPaths;
1109 if (!getI2cDevicePaths(devDir, busPaths))
James Feist918e18c2018-02-13 15:51:07 -08001110 {
James Feist4131aea2018-03-09 09:47:30 -08001111 std::cerr << "unable to find i2c devices\n";
1112 return;
James Feist918e18c2018-02-13 15:51:07 -08001113 }
Nikhil Potaded8884f12019-03-27 13:27:13 -07001114
1115 for (auto busPath : busPaths)
1116 {
1117 i2cBuses.emplace_back(busPath.second);
1118 }
James Feist4131aea2018-03-09 09:47:30 -08001119
James Feist98132792019-07-09 13:29:09 -07001120 busmap.clear();
James Feist5cb06082019-10-24 17:11:13 -07001121 auto scan =
1122 std::make_shared<FindDevicesWithCallback>(i2cBuses, busmap, [&]() {
James Feista465ccc2019-02-08 12:51:01 -08001123 for (auto& busIface : dbusInterfaceMap)
James Feist6ebf9de2018-05-15 15:01:17 -07001124 {
1125 objServer.remove_interface(busIface.second);
1126 }
James Feist4131aea2018-03-09 09:47:30 -08001127
James Feist6ebf9de2018-05-15 15:01:17 -07001128 dbusInterfaceMap.clear();
1129 UNKNOWN_BUS_OBJECT_COUNT = 0;
James Feist4131aea2018-03-09 09:47:30 -08001130
James Feist6ebf9de2018-05-15 15:01:17 -07001131 // todo, get this from a more sensable place
1132 std::vector<char> baseboardFru;
1133 if (readBaseboardFru(baseboardFru))
1134 {
1135 boost::container::flat_map<int, std::vector<char>>
1136 baseboardDev;
1137 baseboardDev.emplace(0, baseboardFru);
James Feist98132792019-07-09 13:29:09 -07001138 busmap[0] = std::make_shared<DeviceMap>(baseboardDev);
James Feist6ebf9de2018-05-15 15:01:17 -07001139 }
James Feist98132792019-07-09 13:29:09 -07001140 for (auto& devicemap : busmap)
James Feist6ebf9de2018-05-15 15:01:17 -07001141 {
James Feista465ccc2019-02-08 12:51:01 -08001142 for (auto& device : *devicemap.second)
James Feist6ebf9de2018-05-15 15:01:17 -07001143 {
James Feist5cb06082019-10-24 17:11:13 -07001144 AddFruObjectToDbus(device.second, dbusInterfaceMap,
1145 devicemap.first, device.first);
James Feist6ebf9de2018-05-15 15:01:17 -07001146 }
1147 }
1148 });
1149 scan->run();
1150 });
James Feist918e18c2018-02-13 15:51:07 -08001151}
1152
James Feist98132792019-07-09 13:29:09 -07001153int main()
James Feist3cb5fec2018-01-23 14:41:51 -08001154{
1155 auto devDir = fs::path("/dev/");
James Feistc9dff1b2019-02-13 13:33:13 -08001156 auto matchString = std::string(R"(i2c-\d+$)");
James Feist3cb5fec2018-01-23 14:41:51 -08001157 std::vector<fs::path> i2cBuses;
1158
James Feista3c180a2018-08-09 16:06:04 -07001159 if (!findFiles(devDir, matchString, i2cBuses))
James Feist3cb5fec2018-01-23 14:41:51 -08001160 {
1161 std::cerr << "unable to find i2c devices\n";
1162 return 1;
1163 }
James Feist3cb5fec2018-01-23 14:41:51 -08001164
Patrick Venture11f1ff42019-08-01 10:42:12 -07001165 // check for and load blacklist with initial buses.
1166 loadBlacklist(blacklistPath);
1167
Vijay Khemka065f6d92018-12-18 10:37:47 -08001168 systemBus->request_name("xyz.openbmc_project.FruDevice");
James Feist3cb5fec2018-01-23 14:41:51 -08001169
James Feist6ebf9de2018-05-15 15:01:17 -07001170 // this is a map with keys of pair(bus number, address) and values of
1171 // the object on dbus
James Feist3cb5fec2018-01-23 14:41:51 -08001172 boost::container::flat_map<std::pair<size_t, size_t>,
James Feist9eb0b582018-04-27 12:15:46 -07001173 std::shared_ptr<sdbusplus::asio::dbus_interface>>
1174 dbusInterfaceMap;
James Feist3cb5fec2018-01-23 14:41:51 -08001175
James Feist9eb0b582018-04-27 12:15:46 -07001176 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
1177 objServer.add_interface("/xyz/openbmc_project/FruDevice",
1178 "xyz.openbmc_project.FruDeviceManager");
James Feist3cb5fec2018-01-23 14:41:51 -08001179
James Feist5cb06082019-10-24 17:11:13 -07001180 iface->register_method("ReScan",
1181 [&]() { rescanBusses(busMap, dbusInterfaceMap); });
James Feist2a9d6db2018-04-27 15:48:28 -07001182
Nikhil Potaded8884f12019-03-27 13:27:13 -07001183 iface->register_method("GetRawFru", getFruInfo);
James Feistb49ffc32018-05-02 11:10:43 -07001184
1185 iface->register_method("WriteFru", [&](const uint8_t bus,
1186 const uint8_t address,
James Feista465ccc2019-02-08 12:51:01 -08001187 const std::vector<uint8_t>& data) {
James Feistb49ffc32018-05-02 11:10:43 -07001188 if (!writeFru(bus, address, data))
1189 {
James Feistddb78302018-09-06 11:45:42 -07001190 throw std::invalid_argument("Invalid Arguments.");
James Feistb49ffc32018-05-02 11:10:43 -07001191 return;
1192 }
1193 // schedule rescan on success
James Feist5cb06082019-10-24 17:11:13 -07001194 rescanBusses(busMap, dbusInterfaceMap);
James Feistb49ffc32018-05-02 11:10:43 -07001195 });
James Feist9eb0b582018-04-27 12:15:46 -07001196 iface->initialize();
James Feist3cb5fec2018-01-23 14:41:51 -08001197
James Feist9eb0b582018-04-27 12:15:46 -07001198 std::function<void(sdbusplus::message::message & message)> eventHandler =
James Feista465ccc2019-02-08 12:51:01 -08001199 [&](sdbusplus::message::message& message) {
James Feist918e18c2018-02-13 15:51:07 -08001200 std::string objectName;
James Feist9eb0b582018-04-27 12:15:46 -07001201 boost::container::flat_map<
James Feista465ccc2019-02-08 12:51:01 -08001202 std::string,
1203 std::variant<std::string, bool, int64_t, uint64_t, double>>
James Feist9eb0b582018-04-27 12:15:46 -07001204 values;
1205 message.read(objectName, values);
James Feist0eeb79c2019-10-09 13:35:29 -07001206 auto findState = values.find("CurrentHostState");
1207 bool on = false;
1208 if (findState != values.end())
James Feist918e18c2018-02-13 15:51:07 -08001209 {
James Feist0eeb79c2019-10-09 13:35:29 -07001210 on = boost::ends_with(std::get<std::string>(findState->second),
1211 "Running");
1212 }
James Feist6ebf9de2018-05-15 15:01:17 -07001213
James Feist0eeb79c2019-10-09 13:35:29 -07001214 if (on)
1215 {
James Feist5cb06082019-10-24 17:11:13 -07001216 rescanBusses(busMap, dbusInterfaceMap);
James Feist918e18c2018-02-13 15:51:07 -08001217 }
James Feist918e18c2018-02-13 15:51:07 -08001218 };
James Feist9eb0b582018-04-27 12:15:46 -07001219
1220 sdbusplus::bus::match::match powerMatch = sdbusplus::bus::match::match(
James Feista465ccc2019-02-08 12:51:01 -08001221 static_cast<sdbusplus::bus::bus&>(*systemBus),
James Feist7bcd3f22019-03-18 16:04:04 -07001222 "type='signal',interface='org.freedesktop.DBus.Properties',path='/xyz/"
James Feist0eeb79c2019-10-09 13:35:29 -07001223 "openbmc_project/state/"
1224 "host0',arg0='xyz.openbmc_project.State.Host'",
James Feist9eb0b582018-04-27 12:15:46 -07001225 eventHandler);
1226
James Feist4131aea2018-03-09 09:47:30 -08001227 int fd = inotify_init();
James Feist0eb40352019-04-09 14:44:04 -07001228 inotify_add_watch(fd, I2C_DEV_LOCATION,
1229 IN_CREATE | IN_MOVED_TO | IN_DELETE);
James Feist4131aea2018-03-09 09:47:30 -08001230 std::array<char, 4096> readBuffer;
1231 std::string pendingBuffer;
1232 // monitor for new i2c devices
1233 boost::asio::posix::stream_descriptor dirWatch(io, fd);
1234 std::function<void(const boost::system::error_code, std::size_t)>
James Feista465ccc2019-02-08 12:51:01 -08001235 watchI2cBusses = [&](const boost::system::error_code& ec,
James Feist4131aea2018-03-09 09:47:30 -08001236 std::size_t bytes_transferred) {
1237 if (ec)
1238 {
1239 std::cout << "Callback Error " << ec << "\n";
1240 return;
1241 }
1242 pendingBuffer += std::string(readBuffer.data(), bytes_transferred);
1243 bool devChange = false;
1244 while (pendingBuffer.size() > sizeof(inotify_event))
1245 {
James Feista465ccc2019-02-08 12:51:01 -08001246 const inotify_event* iEvent =
1247 reinterpret_cast<const inotify_event*>(
James Feist4131aea2018-03-09 09:47:30 -08001248 pendingBuffer.data());
1249 switch (iEvent->mask)
1250 {
James Feist9eb0b582018-04-27 12:15:46 -07001251 case IN_CREATE:
1252 case IN_MOVED_TO:
1253 case IN_DELETE:
1254 if (boost::starts_with(std::string(iEvent->name),
1255 "i2c"))
1256 {
1257 devChange = true;
1258 }
James Feist4131aea2018-03-09 09:47:30 -08001259 }
1260
1261 pendingBuffer.erase(0, sizeof(inotify_event) + iEvent->len);
1262 }
James Feist6ebf9de2018-05-15 15:01:17 -07001263 if (devChange)
James Feist4131aea2018-03-09 09:47:30 -08001264 {
James Feist5cb06082019-10-24 17:11:13 -07001265 rescanBusses(busMap, dbusInterfaceMap);
James Feist4131aea2018-03-09 09:47:30 -08001266 }
James Feist6ebf9de2018-05-15 15:01:17 -07001267
James Feist4131aea2018-03-09 09:47:30 -08001268 dirWatch.async_read_some(boost::asio::buffer(readBuffer),
1269 watchI2cBusses);
1270 };
1271
1272 dirWatch.async_read_some(boost::asio::buffer(readBuffer), watchI2cBusses);
Gunnar Millsb3e42fe2018-06-13 15:48:27 -05001273 // run the initial scan
James Feist5cb06082019-10-24 17:11:13 -07001274 rescanBusses(busMap, dbusInterfaceMap);
James Feist918e18c2018-02-13 15:51:07 -08001275
James Feist3cb5fec2018-01-23 14:41:51 -08001276 io.run();
1277 return 0;
1278}